Stream: beginners

Topic: How to get the current Timezone / zulu offset in a Roc app?


view this post on Zulip Ian McLerran (Apr 30 2024 at 02:27):

So I see that Utc.now provides time in Zulu time. Just wondering if there's a way to access the system's timezone?

view this post on Zulip Luke Boswell (Apr 30 2024 at 02:30):

There isn't currently, but we could add it.

view this post on Zulip Luke Boswell (Apr 30 2024 at 02:30):

How would you do this using rust?

view this post on Zulip Ian McLerran (Apr 30 2024 at 02:32):

Looks like chrono::offset::TimeZone

view this post on Zulip Luke Boswell (Apr 30 2024 at 02:34):

If we are happy adding chrono as a dependency I can add that.

view this post on Zulip Luke Boswell (Apr 30 2024 at 02:34):

Or happy to talk you through how to add another effect for the platform

view this post on Zulip Luke Boswell (Apr 30 2024 at 02:35):

It's pretty straightforward

view this post on Zulip Ian McLerran (Apr 30 2024 at 02:36):

I have never written any Rust, but been thinking its high time to learn as I participate more in the Roc community...

view this post on Zulip Ian McLerran (Apr 30 2024 at 02:38):

On a little more digging, looks like chrono doesn't actually give anything useable?
https://stackoverflow.com/questions/59603665/how-do-you-find-the-local-timezone-offset-in-rust

Looks like this will get the TZ offset, not sure yet if thats part of chrono or std lib, or something else.
Local.timestamp(0, 0).offset().fix().local_minus_utc()

view this post on Zulip Ian McLerran (Apr 30 2024 at 02:39):

Anyway, I'd love to go through the process of adding a new effect for the platform.

view this post on Zulip Ian McLerran (Apr 30 2024 at 02:40):

Can confirm Local is part of the chrono library.

view this post on Zulip Luke Boswell (Apr 30 2024 at 02:40):

I'm not sure how we want to represent an Offset... so this might need tweaking...

Add host<->platform interface in platform/Effect.roc, something like

offsetTime : Effect U128

Add platform<->app interface in platform/Utc.roc, something like

## Some offset thing...
offset : Task U128 *
offset =
    Effect.offsetTime
    |> Effect.map Ok
    |> InternalTask.fromEffect

Add the Fx implemenation in the host crates/host/src/lib.rs, something like

#[no_mangle]
pub extern "C" fn roc_fx_offsetTime() -> roc_std::U128 {

    let offset: u128 = 0;

    // implement using chrono::offset::TimeZone

    // return value to roc
    roc_std::U128::from(offset)
}

view this post on Zulip Ian McLerran (Apr 30 2024 at 02:41):

chrono is just giving offset in seconds from Zulu time.

view this post on Zulip Ian McLerran (Apr 30 2024 at 02:42):

So shouldn't exceed 60 * 60 * 14 which could be as small as an I16.

view this post on Zulip Ian McLerran (Apr 30 2024 at 02:43):

Offset can be +14 hours -12 hours I believe. Its either that or -14/+12.

view this post on Zulip Ian McLerran (Apr 30 2024 at 02:47):

Thanks for the pointers on adding an effect to the platform. I'll give it a go. Gonna have to learn the basics of setting up my rust dev environment, etc, before I can actually implement it lol.

view this post on Zulip Luke Boswell (Apr 30 2024 at 02:48):

This looks relevant
https://docs.rs/chrono/latest/chrono/offset/struct.FixedOffset.html

view this post on Zulip Luke Boswell (Apr 30 2024 at 02:49):

Yeah, I also picked up rust so I could contribute to roc :crab:

view this post on Zulip Richard Feldman (Apr 30 2024 at 02:50):

I'm not familiar with chrono, but at a glance I'd recommend not using FixedOffset for any reason :big_smile:

view this post on Zulip Richard Feldman (Apr 30 2024 at 02:51):

one of the things I've learned the hard way is that "fixed UTC offset" is the same thing as UTC, but it has a name that incorrectly suggests it's taking time zones into account when it isn't

view this post on Zulip Richard Feldman (Apr 30 2024 at 02:52):

for example, the information contained in "UTC time foo, with an offset of +2" is:

view this post on Zulip Ian McLerran (Apr 30 2024 at 02:52):

I'm seeing chrono::Local:

Local.timestamp(0, 0).offset().fix().local_minus_utc()

which returns 3600 seconds for one particular poster....

view this post on Zulip Richard Feldman (Apr 30 2024 at 02:54):

in contrast, a date plus a time zone (e.g. "time foo in the Chicago time zone") can be converted to/from an UTC time - and of course people often want to specify dates add times without the time zone, but you know what their time zone is and so can apply it to the time they specified to get UTC

view this post on Zulip Richard Feldman (Apr 30 2024 at 02:54):

but if the user gives you a particular date and time, and all you have recorded is their "UTC offset" - that doesn't tell you their time zone

view this post on Zulip Richard Feldman (Apr 30 2024 at 02:55):

many time zones have rules that are more complex than a fixed UTC offset that changes once or twice per year

view this post on Zulip Richard Feldman (Apr 30 2024 at 02:56):

and even in the common case in the US (for example), if all you have is a fixed UTC offset, you don't know whether that offset was recorded during daylight savings time or not, so you don't know which direction to adjust it in if you cross a DST boundary

view this post on Zulip Richard Feldman (Apr 30 2024 at 02:59):

to sum up:

view this post on Zulip Luke Boswell (Apr 30 2024 at 03:16):

Ok, this looks like the better solution then https://docs.rs/iana-time-zone/latest/iana_time_zone/fn.get_timezone.html

view this post on Zulip Ian McLerran (Apr 30 2024 at 03:18):

That all makes a lot of sense. Definitely don't want to do away with UTC time, but I think it would be useful to have a way to move between UTC time and Local time.

I can see that pure Zulu offset has some definite ambiguities, if you are trying to determine the timezone from the offset. Depending on your usecase, zulu offset may be exactly what you want. If you simply want to convert a zulu time to a local time to display to the user, offset is exactly what you're looking for. If you want to store a time longterm, and particularly if you're recording it without date info attached.

view this post on Zulip Luke Boswell (Apr 30 2024 at 03:19):

See also https://www.iana.org/time-zones

view this post on Zulip Ian McLerran (Apr 30 2024 at 03:20):

Seems to me that both offset and timezone are useful? If all the platform provides is timezone string, but you want to display a time in local time to the user, you still need a way to convert TZ string to an offset. Maybe that should be in a package rather than the platform, but still has its applications.

view this post on Zulip Luke Boswell (Apr 30 2024 at 03:21):

Screenshot-2024-04-30-at-13.20.28.png
So the solution is to build a Roc package that provides the tz data

view this post on Zulip Luke Boswell (Apr 30 2024 at 03:22):

The data files look like ^^ in a standardised format so they can be parsed. We could code gen the implementation similar to how we generate the mapping between codepoints and unicode properties like in the roc-land/unicode package.

view this post on Zulip Ian McLerran (Apr 30 2024 at 03:23):

Correct me if I'm wrong, I believe what we want is:

view this post on Zulip Luke Boswell (Apr 30 2024 at 03:23):

The Time Zone Database (called tz, tzdb or zoneinfo) contains code and
data that represent the history of local time for many representative
locations around the globe. It is updated periodically to reflect
changes made by political bodies to time zone boundaries, UTC offsets,
and daylight-saving rules.

view this post on Zulip Luke Boswell (Apr 30 2024 at 03:25):

Though, I'm not sure it's worth including the full history for every single time zone

view this post on Zulip Ian McLerran (Apr 30 2024 at 03:30):

Luke Boswell said:

Though, I'm not sure it's worth including the full history for every single time zone

Hmm... depending on how big the dataset is, could include the data as a CSV or similar and perform a query on that for historical dates, while hardcoding current info.

Alternatively it could be network dependent, but that seems sub optimal.

view this post on Zulip Luke Boswell (Apr 30 2024 at 03:33):

If it's a package then application and platform authors can opt out of using it if they dont need it. Once the source is compiled it's probably not that large. Maybe there is a more compact way of representing it in source code too? like encode into a data table and include that in the source code?

view this post on Zulip Ian McLerran (Apr 30 2024 at 03:35):

That makes sense… are you thinking like a large dict of TZ data? Or how would Roc store a data table like that?

view this post on Zulip Luke Boswell (Apr 30 2024 at 03:36):

I'm thinking we could hash the TZ string, and use that as a Dict key

view this post on Zulip Luke Boswell (Apr 30 2024 at 03:39):

Maybe for the value we have a List {utcFrom: U64, offset:U16} or something

view this post on Zulip Ian McLerran (Apr 30 2024 at 03:41):

Where from is the beginning date as offset from epoch when the offset came into use?

view this post on Zulip Ian McLerran (Apr 30 2024 at 03:42):

That would still require list iteration… maybe hash the from and TZ string for O(1) lookup

view this post on Zulip Luke Boswell (Apr 30 2024 at 03:43):

There's an implementation for the Hasher ability floating around somewhere. I don't think it needs to be cryptographically secure or anything. In crates/compiler/builtins/roc/Dict.roc we have the LowLevelHasher which is an implementation of wyhash. I'm not sure if there is any different using a Str key or hashing it first. @Brendan Hansknecht would know

view this post on Zulip Luke Boswell (Apr 30 2024 at 03:44):

Maybe there is a smarter data structure to put all this in? I'm not great with this stuff

view this post on Zulip Brendan Hansknecht (Apr 30 2024 at 03:54):

Just caught up here. How big is this database in practice and how often does it actually change. Are we talking 10s of timezones, 100s, other?

view this post on Zulip Brendan Hansknecht (Apr 30 2024 at 03:55):

Just trying to get a feel for the data before suggesting anything.

view this post on Zulip Luke Boswell (Apr 30 2024 at 03:55):

Thousands of TimeZones

view this post on Zulip Luke Boswell (Apr 30 2024 at 03:55):

Maybe between 5-20 rules per timezone

view this post on Zulip Luke Boswell (Apr 30 2024 at 03:56):

Maybe only hundreds of TZ's

view this post on Zulip Luke Boswell (Apr 30 2024 at 03:57):

Ok, so 597 timezones

view this post on Zulip Brendan Hansknecht (Apr 30 2024 at 03:58):

Ok

view this post on Zulip Luke Boswell (Apr 30 2024 at 03:58):

A bunch of timezones share common rules

view this post on Zulip Luke Boswell (Apr 30 2024 at 03:59):

https://en.wikipedia.org/wiki/Tz_database

view this post on Zulip Luke Boswell (Apr 30 2024 at 03:59):

This is the best summary I've found

view this post on Zulip Brendan Hansknecht (Apr 30 2024 at 03:59):

My gut feeling is that the best would be to write a program to read the timezones database and generate roc code for a package (can also try having the roc package directly read the time zone database, but I would guess that would be slow).

view this post on Zulip Brendan Hansknecht (Apr 30 2024 at 03:59):

I would push for using a tag instead of a string for the timezones.

view this post on Zulip Brendan Hansknecht (Apr 30 2024 at 04:00):

Of course with conversion

view this post on Zulip Brendan Hansknecht (Apr 30 2024 at 04:01):

Cause if a specific version of the time zone package doesn't know about a timezone, won't matter if it is a string or a tag. That said, I guess there could be value in making this more pluggable if the database changes often and you would rather load it at runtime.

view this post on Zulip Brendan Hansknecht (Apr 30 2024 at 04:01):

Anyway, this is small enough that if you use a tag, linear search (or binary for that matter) might beat a dictionary. If you use a string, probably should just make the string the key to a dictionary

view this post on Zulip Luke Boswell (Apr 30 2024 at 04:05):

Another approach that seems common is to use the database on the users host system. For example the go std library has this

func LoadLocation(name string) (*Location, error)
LoadLocation returns the Location with the given name.

If the name is "" or "UTC", LoadLocation returns UTC. If the name is "Local", LoadLocation returns Local.

Otherwise, the name is taken to be a location name corresponding to a file in the IANA Time Zone database, such as "America/New_York".

LoadLocation looks for the IANA Time Zone database in the following locations in order:

  1. the directory or uncompressed zip file named by the ZONEINFO environment variable
  2. on a Unix system, the system standard installation location
  3. $GOROOT/lib/time/zoneinfo.zip
  4. the time/tzdata package, if it was imported

view this post on Zulip Luke Boswell (Apr 30 2024 at 04:07):

I think it would be nice to have a package that includes the database, then it would just work wherever you write Roc code.

view this post on Zulip Brendan Hansknecht (Apr 30 2024 at 04:07):

Yeah, I think you want both.

view this post on Zulip Brendan Hansknecht (Apr 30 2024 at 04:07):

One that is 100% roc with nicer types

view this post on Zulip Brendan Hansknecht (Apr 30 2024 at 04:08):

One for loading from the system and being able to update with a file

view this post on Zulip Luke Boswell (Apr 30 2024 at 04:08):

This is how it's done in Elm https://github.com/justinmimbs/timezone-data/blob/10.1.1/src/TimeZone.elm

view this post on Zulip Luke Boswell (Apr 30 2024 at 04:29):

A platform could provide:

Utc := I128
TimeZone := Str
Local := I128

now : Task Utc *
name : TimeZone -> Task Str [NotFound]
systemTimeZone : Task TimeZone [NotAvailable]
offset : Utc, TimeZone -> Local

A package could provide:

Utc := I128
TimeZone := Str
Local := I128

name : TimeZone -> Result Str [NotFound]
offset : Utc, TimeZone -> Local

Using the platform to get the offset would use less memory as the IANA database is not included, at the expense of requiring a Task which would search for the database on the system - which may not be available in some environments.

view this post on Zulip Brendan Hansknecht (Apr 30 2024 at 05:00):

I think the database could be ingested as code which would cost binary size, but would be fast and as long as timezones don't update too much, not that bad for dependency Management

view this post on Zulip RKB (Apr 30 2024 at 22:56):

Ian McLerran said:

If you simply want to convert a zulu time to a local time to display to the user, offset is exactly what you're looking for.

it's a trap!
offsets are not stable over time; the offset for a time in this week may be different to the offset for a time next week.

to accurately convert any UTC time to a local time, you need to know the user's local Timezone Identifier (like Australia/Sydney)


Last updated: Jul 06 2025 at 12:14 UTC