Timezone offset calculation

I’ve been struggling with setting date and times through automations. Since buttons and automations run on the server which works using UTC time, this creates problem if you are trying to set a date field based on some text/number input or to increment an existing date. I think the general advice has been to setup a variable to with the timezone offset and ensure that all local datetime is converted to UTC. However, when you are dealing with daylight saving, then the offsets can change.

Unfortunately, javascript doesn’t seem to have a default way of dealing with this. And you can’t load any third-party libraries in fibery scripts to take care of this for you (I wish that was possible).

I also looked at using an api. World Time API seems great for this. But they do mention that the api can be down from time to time so it is possible for this to fail. Nothing I’m doing is mission-critical but I would rather my automations don’t fail since there doesn’t seem to be a mechanism to tell them to wait and try again after a certain duration.

To get around all these issues, I created the following function. It is a hacky way of using core javascript date functions to convert a timezone db name (e.g. " America/New_York") into the current UTC offset:

// Function to calculate time zone offset
function getTZOffset(strTimeZone) {
    //get current datetime in UTC
    var dateUTC = new Date();
    dateUTC.setUTCMilliseconds(0);
    dateUTC.setSeconds(0);

    //convert UTC datetime to particular timezone
    const optionsLocal = {
        timeZone: strTimeZone,
        year: 'numeric',
        month: '2-digit',
        day: '2-digit',
        hour: 'numeric',
        minute: 'numeric',
        hour12: false
    };
    var arrTZDate = dateUTC.toLocaleDateString('en-CA', optionsLocal).split(",");
    var arrDate = arrTZDate[0].split("/").map(x => Number(x));
    var arrTime = arrTZDate[1].split(":").map(x => Number(x));
    var dateTZ = new Date(arrDate[2], arrDate[0] - 1, arrDate[1], arrTime[0], arrTime[1], 0, 0);

    //calculate the time difference & return in hour offset
    var tzDiff = dateTZ.getTime() - dateUTC.getTime();
    return tzDiff / (60 * 60 * 1000);
}

This seems to work but I wonder if there is a more elegant way of dynamically finding the offset based on just the timezone name?

Thought I share this in case it is helpful to someone else and also see if there is any advice on how to improve this. I also thought that the fibery team might be persuaded to add a solid and mature date library and make it accessible to all so we don’t have to do crazy things like this.

I also wanted to again support @Matt_Blais 's request for centralized code library/reusable modules because maintaining all these helper functions all over the place is a nightmare.

My one piece of advice is that dealing with time zones is a nightmare that’s almost never worth DIYing.

That being said I really do wish that Lodash and Moment.js and were libraries we could use in scripts.

Vote for the feature request here:

Fully agree. I am pretty sure that there is something that I didn’t think of at all. I voted for the moment library inclusion, thanks for posting the idea.

I think the biggest issue is that there is no such thing as ‘the timezone’ or ‘local datetime’ when a workspace may be utilised by users in different locations.
Whose timezone?

For datetime fields, then in principle there is no problem to localise the display for each user, but as soon as datetimes are converted to text (or vice versa) then there are challenges for which no agreeable solution exists.

You are right, I left out a critical point on how I use this. I think the use case for this is when you need to run some sort of automation based on a date time that you need to set in a particular time zone.

I had to develop this when I was setting up a generic recurrence entity so that I can set custom recurrence rules for different entity types (like meetings, tasks and events). In that setup I actually set the time, duration, timezone and a recurrence rule (i.e. recurs daily/weekly/monthly , every Tues, …). The timezone setting allows me to setup a weekly recurring meeting for a full year in whatever timezone I want without having to worry about changes in daylight saving. Here is what a recurrence rule entity looks like:

1 Like

I have dug into timezones a little, and concluded that it is possible to write a formula that will convert a DateTime value to a Date field based on a specific timezone, taking into account daylight saving.
In the case of Europe, the formula would be as follows:

DateTimeField +
  Hours(
    If(
      DateTimeField >=
        Date(Year(DateTimeField), 4, 1) -
          Days(
            If(
              WeekDayName(Date(Year(DateTimeField), 4, 1)) = "Monday",
              1,
              If(
                WeekDayName(Date(Year(DateTimeField), 4, 1)) = "Tuesday",
                2,
                If(
                  WeekDayName(Date(Year(DateTimeField), 4, 1)) = "Wednesday",
                  3,
                  If(
                    WeekDayName(Date(Year(DateTimeField), 4, 1)) = "Thursday",
                    4,
                    If(
                      WeekDayName(Date(Year(DateTimeField), 4, 1)) = "Friday",
                      5,
                      If(
                        WeekDayName(Date(Year(DateTimeField), 4, 1)) =
                          "Saturday",
                        6,
                        7
                      )
                    )
                  )
                )
              )
            )
          ) +
          Hours(1) and
        DateTimeField <
          Date(Year(DateTimeField), 11, 1) -
            Days(
              If(
                WeekDayName(Date(Year(DateTimeField), 11, 1)) = "Monday",
                1,
                If(
                  WeekDayName(Date(Year(DateTimeField), 11, 1)) = "Tuesday",
                  2,
                  If(
                    WeekDayName(Date(Year(DateTimeField), 11, 1)) =
                      "Wednesday",
                    3,
                    If(
                      WeekDayName(Date(Year(DateTimeField), 11, 1)) =
                        "Thursday",
                      4,
                      If(
                        WeekDayName(Date(Year(DateTimeField), 11, 1)) =
                          "Friday",
                        5,
                        If(
                          WeekDayName(Date(Year(DateTimeField), 11, 1)) =
                            "Saturday",
                          6,
                          7
                        )
                      )
                    )
                  )
                )
              )
            ) +
            Hours(1),
      2,
      1
    )
  )

where the last two numbers represent the offset in hours for summer and winter time respectively.

I haven’t exhaustively checked that it works correctly for every possible date, so please let me know if you find any bugs.

If you’re in North America, the formula is slightly different (since the dates when the clocks change is different and the time of day for the change is based on local time and not UTC):

DateTimeField -
  Hours(
    If(
      DateTimeField >=
        Date(Year(DateTimeField), 3, 1) +
          Days(
            If(
              WeekDayName(Date(Year(DateTimeField), 3, 1)) = "Monday",
              13,
              If(
                WeekDayName(Date(Year(DateTimeField), 3, 1)) = "Tuesday",
                12,
                If(
                  WeekDayName(Date(Year(DateTimeField), 3, 1)) = "Wednesday",
                  11,
                  If(
                    WeekDayName(Date(Year(DateTimeField), 3, 1)) = "Thursday",
                    10,
                    If(
                      WeekDayName(Date(Year(DateTimeField), 3, 1)) = "Friday",
                      9,
                      If(
                        WeekDayName(Date(Year(DateTimeField), 3, 1)) =
                          "Saturday",
                        8,
                        7
                      )
                    )
                  )
                )
              )
            )
          ) +
          Hours(Delta) and
        DateTimeField <
          Date(Year(DateTimeField), 11, 1) +
            Days(
              If(
                WeekDayName(Date(Year(DateTimeField), 11, 1)) = "Monday",
                6,
                If(
                  WeekDayName(Date(Year(DateTimeField), 11, 1)) = "Tuesday",
                  5,
                  If(
                    WeekDayName(Date(Year(DateTimeField), 11, 1)) =
                      "Wednesday",
                    4,
                    If(
                      WeekDayName(Date(Year(DateTimeField), 11, 1)) =
                        "Thursday",
                      3,
                      If(
                        WeekDayName(Date(Year(DateTimeField), 11, 1)) =
                          "Friday",
                        2,
                        If(
                          WeekDayName(Date(Year(DateTimeField), 11, 1)) =
                            "Saturday",
                          1,
                          0
                        )
                      )
                    )
                  )
                )
              )
            ) +
            Hours(Delta + 1),
      Delta - 1,
      Delta
    )
  )

In this case, you will need to replace Delta with a number representing the number of hours behind UTC you are in the winter period. So for example, I think in New York it would be 5, and in Los Angeles it would be 8.

Please let me know what you think.

3 Likes

This is awesome, thank you so much!

@ChrisG would it be also possible to convert a normal Date field (without time) to a DateTime field, including the offset for the timezone?

So input field “March 3 2024 - March 3 2024”
Desired outcome “Marc 3 2024 00:00 - March 3 2024 00:00”

My idea was to adjust your formula to a DateTimeRange format and simply do a -2 or -1 hour, but I get the same result whether I use -2/-1 or 2/1 as offset in the formula. Also tried 0-2 / 0-1 but it seems that a negative integer doesn’t work?

DateTimeRange(
  DateTime(
    Year(Datum.Start()),
    Month(Datum.Start()),
    Day(Datum.Start()),
    If(
      Datum.Start() >=
        Date(Year(Datum.Start()), 4, 1) -
          Days(
            If(
              WeekDayName(Date(Year(Datum.Start()), 4, 1)) = "Monday",
              1,
              If(
                WeekDayName(Date(Year(Datum.Start()), 4, 1)) = "Tuesday",
                2,
                If(
                  WeekDayName(Date(Year(Datum.Start()), 4, 1)) = "Wednesday",
                  3,
                  If(
                    WeekDayName(Date(Year(Datum.Start()), 4, 1)) = "Thursday",
                    4,
                    If(
                      WeekDayName(Date(Year(Datum.Start()), 4, 1)) = "Friday",
                      5,
                      If(
                        WeekDayName(Date(Year(Datum.Start()), 4, 1)) =
                          "Saturday",
                        6,
                        7
                      )
                    )
                  )
                )
              )
            )
          ) +
          Hours(1) and
        Datum.Start() <
          Date(Year(Datum.Start()), 11, 1) -
            Days(
              If(
                WeekDayName(Date(Year(Datum.Start()), 11, 1)) = "Monday",
                1,
                If(
                  WeekDayName(Date(Year(Datum.Start()), 11, 1)) = "Tuesday",
                  2,
                  If(
                    WeekDayName(Date(Year(Datum.Start()), 11, 1)) =
                      "Wednesday",
                    3,
                    If(
                      WeekDayName(Date(Year(Datum.Start()), 11, 1)) =
                        "Thursday",
                      4,
                      If(
                        WeekDayName(Date(Year(Datum.Start()), 11, 1)) =
                          "Friday",
                        5,
                        If(
                          WeekDayName(Date(Year(Datum.Start()), 11, 1)) =
                            "Saturday",
                          6,
                          7
                        )
                      )
                    )
                  )
                )
              )
            ) +
            Hours(1),
      -2,
      -1
    ),
    0,
    0
  ),
  DateTime(
    Year(Datum.End()),
    Month(Datum.End()),
    Day(Datum.End()),
    If(
      Datum.End() >=
        Date(Year(Datum.End()), 4, 1) -
          Days(
            If(
              WeekDayName(Date(Year(Datum.End()), 4, 1)) = "Monday",
              1,
              If(
                WeekDayName(Date(Year(Datum.End()), 4, 1)) = "Tuesday",
                2,
                If(
                  WeekDayName(Date(Year(Datum.End()), 4, 1)) = "Wednesday",
                  3,
                  If(
                    WeekDayName(Date(Year(Datum.End()), 4, 1)) = "Thursday",
                    4,
                    If(
                      WeekDayName(Date(Year(Datum.End()), 4, 1)) = "Friday",
                      5,
                      If(
                        WeekDayName(Date(Year(Datum.End()), 4, 1)) =
                          "Saturday",
                        6,
                        7
                      )
                    )
                  )
                )
              )
            )
          ) +
          Hours(1) and
        Datum.End() <
          Date(Year(Datum.End()), 11, 1) -
            Days(
              If(
                WeekDayName(Date(Year(Datum.End()), 11, 1)) = "Monday",
                1,
                If(
                  WeekDayName(Date(Year(Datum.End()), 11, 1)) = "Tuesday",
                  2,
                  If(
                    WeekDayName(Date(Year(Datum.End()), 11, 1)) = "Wednesday",
                    3,
                    If(
                      WeekDayName(Date(Year(Datum.End()), 11, 1)) = "Thursday",
                      4,
                      If(
                        WeekDayName(Date(Year(Datum.End()), 11, 1)) = "Friday",
                        5,
                        If(
                          WeekDayName(Date(Year(Datum.End()), 11, 1)) =
                            "Saturday",
                          6,
                          7
                        )
                      )
                    )
                  )
                )
              )
            ) +
            Hours(1),
      -2,
      -1
    ),
    0,
    0
  )
)

The DateTime function allows you to define a date-time value using number values for year, month, day, hour, minute and second, and it can’t take negative values, so
DateTime(2024,04,03,-1,0,0)
is not allowable.

However, if you want to achieve the same, you can just set to zero and then subtract an hour, e.g.
DateTime(2024,04,03,0,0,0) + Hours(-1)

but DateTime(2024,04,03,0,0,0) + Hours(-1) is actually equivalent to Date(2024,04,03) + Hours(-1)

(since the act of adding hours will automatically convert a date into a date-time, and when a date is converted to date-time, it always assumes midnight UTC).

Also, the original formula in this topic was checking for summer time taking into account the exact hour at which the clock changes, but since you are converting from a date to date-time, this fine detail is not relevant, and you can just look at the date itself.

Accordingly, I think your formula can be ‘simplified’ to this.

DateTimeRange(
  Datum.Start() +
    Hours(
      If(
        Datum.Start() >
          Date(Year(Datum.Start()), 4, 1) -
            Days(
              If(
                WeekDayName(Date(Year(Datum.Start()), 4, 1)) = "Monday",
                1,
                If(
                  WeekDayName(Date(Year(Datum.Start()), 4, 1)) = "Tuesday",
                  2,
                  If(
                    WeekDayName(Date(Year(Datum.Start()), 4, 1)) = "Wednesday",
                    3,
                    If(
                      WeekDayName(Date(Year(Datum.Start()), 4, 1)) =
                        "Thursday",
                      4,
                      If(
                        WeekDayName(Date(Year(Datum.Start()), 4, 1)) =
                          "Friday",
                        5,
                        If(
                          WeekDayName(Date(Year(Datum.Start()), 4, 1)) =
                            "Saturday",
                          6,
                          7
                        )
                      )
                    )
                  )
                )
              )
            ) and
          Datum.Start() <=
            Date(Year(Datum.Start()), 11, 1) -
              Days(
                If(
                  WeekDayName(Date(Year(Datum.Start()), 11, 1)) = "Monday",
                  1,
                  If(
                    WeekDayName(Date(Year(Datum.Start()), 11, 1)) = "Tuesday",
                    2,
                    If(
                      WeekDayName(Date(Year(Datum.Start()), 11, 1)) =
                        "Wednesday",
                      3,
                      If(
                        WeekDayName(Date(Year(Datum.Start()), 11, 1)) =
                          "Thursday",
                        4,
                        If(
                          WeekDayName(Date(Year(Datum.Start()), 11, 1)) =
                            "Friday",
                          5,
                          If(
                            WeekDayName(Date(Year(Datum.Start()), 11, 1)) =
                              "Saturday",
                            6,
                            7
                          )
                        )
                      )
                    )
                  )
                )
              ),
        -2,
        -1
      )
    ),
  Datum.End() +
    Hours(
      If(
        Datum.End() >
          Date(Year(Datum.End()), 4, 1) -
            Days(
              If(
                WeekDayName(Date(Year(Datum.End()), 4, 1)) = "Monday",
                1,
                If(
                  WeekDayName(Date(Year(Datum.End()), 4, 1)) = "Tuesday",
                  2,
                  If(
                    WeekDayName(Date(Year(Datum.End()), 4, 1)) = "Wednesday",
                    3,
                    If(
                      WeekDayName(Date(Year(Datum.End()), 4, 1)) = "Thursday",
                      4,
                      If(
                        WeekDayName(Date(Year(Datum.End()), 4, 1)) = "Friday",
                        5,
                        If(
                          WeekDayName(Date(Year(Datum.End()), 4, 1)) =
                            "Saturday",
                          6,
                          7
                        )
                      )
                    )
                  )
                )
              )
            ) and
          Datum.End() <=
            Date(Year(Datum.End()), 11, 1) -
              Days(
                If(
                  WeekDayName(Date(Year(Datum.End()), 11, 1)) = "Monday",
                  1,
                  If(
                    WeekDayName(Date(Year(Datum.End()), 11, 1)) = "Tuesday",
                    2,
                    If(
                      WeekDayName(Date(Year(Datum.End()), 11, 1)) =
                        "Wednesday",
                      3,
                      If(
                        WeekDayName(Date(Year(Datum.End()), 11, 1)) =
                          "Thursday",
                        4,
                        If(
                          WeekDayName(Date(Year(Datum.End()), 11, 1)) =
                            "Friday",
                          5,
                          If(
                            WeekDayName(Date(Year(Datum.End()), 11, 1)) =
                              "Saturday",
                            6,
                            7
                          )
                        )
                      )
                    )
                  )
                )
              ),
        -2,
        -1
      )
    )
)

Let me know if it works as expected.

1 Like

Ah, that’s it! I was trying - Hours(1) but this was the little hack that I was looking for.

Awesome

It works perfect! Thanks again for the help :smile: