Stream: API design

Topic: Date and DateTime


view this post on Zulip Isaac Van Doren (Jan 03 2025 at 03:00):

On the topic of time (#API design > Datatype for representing epochs), in addition to Instants/Timestamps, we should have types for dates (yyyy-MM-dd) and date times (yyyy-MM-dd hh:mm:ss).

I propose these be named Date and DateTime.

Other possibilities for naming them are PlainDate and PlainDateTime, or LocalDate and LocalDateTime to communicate that they don't store timezone information, but I don't think these names are helpful enough to justify the longer and less natural names.

view this post on Zulip Sam Mohr (Jan 03 2025 at 03:00):

Is this for the standard library or for an external library?

view this post on Zulip Isaac Van Doren (Jan 03 2025 at 03:02):

I think they should be in the standard library given how frequently they are used. That way there can be standard conversion functions between all of the different date types.

view this post on Zulip Sam Mohr (Jan 03 2025 at 03:04):

I agree that having them in std is a noble goal, but this is one of the first modules that would require us to really consider the philosophy of our standard library

view this post on Zulip Sam Mohr (Jan 03 2025 at 03:04):

That is, should our standard library have stuff that stays in Roc forever?

view this post on Zulip Sam Mohr (Jan 03 2025 at 03:05):

The reason why Rust is reticent to put Regex and datetime and all into std is because the underlying implementations for those and which library is even popular seems to change a good bit, even now over a decade after its first release as a language

view this post on Zulip Sam Mohr (Jan 03 2025 at 03:06):

Though there are plenty of languages like Python that have "all the basics" in their standard library

view this post on Zulip Sam Mohr (Jan 03 2025 at 03:06):

I think the safer start is to make a roc-lang/datetime library and develop everything there

view this post on Zulip Sam Mohr (Jan 03 2025 at 03:07):

Once that seems stable, we can consider permanently adding it to Roc

view this post on Zulip Luke Boswell (Jan 03 2025 at 03:12):

We already have a few different impls in the wild

  1. https://github.com/hasnep/roc-datetimes
  2. https://github.com/imclerran/Roc-IsoDate
  3. the DateTime module in basic-webserver

view this post on Zulip Sky Rose (Jan 03 2025 at 03:18):

Elixir calls this NaiveDateTime, and their DateTime type has time zone info. https://hexdocs.pm/elixir/1.18.1/NaiveDateTime.html
I don't like the name "NaiveDateTime" but I do like that it's harder to use than the date time with time zone, which creates a pit of success. You only use NaiveDateTime if you really know that you're working with a fictional abstract concept of time, otherwise you use the one with time zones that represents the real world.

view this post on Zulip Sam Mohr (Jan 03 2025 at 03:18):

Good point. We should probably make a combined time library, since we'll want a single, common type for datetime, date, and times

view this post on Zulip Isaac Van Doren (Jan 03 2025 at 03:20):

I think the safer start is to make a roc-lang/datetime library and develop everything there
Once that seems stable, we can consider permanently adding it to Roc

Yeah that sounds reasonable. The design space for Date APIs is quite large

view this post on Zulip Sky Rose (Jan 03 2025 at 03:24):

time zone data also changes a lot, even if the API is stable (similar to Unicode). How to deal with time zone data is gonna be a problem for this library to solve. Even if we want datetimes to eventually be a builtin, the time zone data probably never could be moved to Roc.

view this post on Zulip Richard Feldman (Jan 03 2025 at 03:47):

yeah I think the complexity level of dates, especially with time zones involved, is just way higher than Instant and Duration, which are both fairly thin wrappers around integers :big_smile:

view this post on Zulip Jasper Woudenberg (Jan 03 2025 at 09:07):

I was working on bringing my blog into a static-site platform I'm working on last week. I've blog posts in a directory prefixed with the date, as in yyyy-mm-dd-name-of-the-post.md, and needed to:

I was loth to pull in a dependency for something so small, so I wrote some hacky Str.splitOn ... code to implement that.

I realized that this particular time-related logic had no dependency on locales or time zone databases. I wondered how many uses are like that. There might be a design where builtins include a Time type that could replace my hacky code, but no functions that require a timezone database, such as conversions between Time and Instant. At that point there might no longer be a need for an external time package, but we would instead have a locale package. Either way, it makes sense to me to test out these ideas in a package first.

I guess the Time type I'm describing here is the same as NaiveDateTime that sky mentioned Elixir has, but I don't think that's necessarily a bad thing. In the API documentation for NaiveDateTime it shows that the type implements all sorts of functions for adding intervals to a NaiveDateTime or getting the duration between two NaiveDateTimes. I'd argue it's those functions that are naive, not the type itself. We could omit those kinds of calculation functions for the Roc Time type, and require application authors to convert Time to Instant (using a timezone) before they can do math with it.

view this post on Zulip Sky Rose (Jan 03 2025 at 14:19):

That does sound useful to have NaiveDateTime available for stuff like that, but is it possible to make that easy without disincentivizing people from using the real DateTime when they really do need that correctness?

view this post on Zulip Jasper Woudenberg (Jan 03 2025 at 14:32):

I think so! If the builtin Time/NaiveDateTime type does not provide any operations that would return ambiguous results that would make it a quite a limited API. For any function that needs a timezone for an unambiguous result you'd need to make us types/functions that track timezones provided by external packages.

view this post on Zulip Richard Feldman (Jan 03 2025 at 15:12):

that's interesting - one way to split things up could be to have a Date module that literally just stores month/day/year and offers an API for translating to/from strings with various formats

view this post on Zulip Richard Feldman (Jan 03 2025 at 15:12):

or like Date and DateFormat maybe

view this post on Zulip Richard Feldman (Jan 03 2025 at 15:12):

and it is just completely not concerned with time zones even a little bit

view this post on Zulip Richard Feldman (Jan 03 2025 at 15:13):

and as soon as you want to do anything with time zones, UTC, etc., including converting a Date to an Instant, there's a third-party package for that

view this post on Zulip Sky Rose (Jan 03 2025 at 15:17):

What about supporting different calendars (Julian vs Gregorian, Hebrew calendar, Japanese years)? What about places that skipped a date (switching from Julian to Gregorian calendars skipped 11 days, and sometimes countries change which side of the international date line they're on). The builtins probably shouldn't try to do that stuff, so is it okay if the main date library can't do that?

view this post on Zulip Anthony Bullard (Jan 03 2025 at 15:36):

Makes me wish there was a concept of "builtins that aren't actually builtin", i.e., a set of abstract packages that declare (opaque/nominal) types and functions that work on them - but the language runtime does not implement them at all. All platforms would be required to implement them (hopefully through shared crates / zig packages). maybe some way for a platform to opt out? Not sure...

view this post on Zulip Richard Feldman (Jan 03 2025 at 16:21):

oh I don't think any of these would use anything from the platform

view this post on Zulip Richard Feldman (Jan 03 2025 at 16:22):

the only relevant effects for any of these are "what is the time zone set to on my OS right now?" and "what is the current system time?"

view this post on Zulip Richard Feldman (Jan 03 2025 at 16:22):

those 2 functions are platform-specific, and then the other 99.9% of the APIs (wherever they live) are just data structures and pure functions

view this post on Zulip Richard Feldman (Jan 03 2025 at 16:23):

and I think it's totally fine for those 2 functions to just live in a different module from the rest of the API

view this post on Zulip Richard Feldman (Jan 03 2025 at 16:23):

like with time I'm thinking:

view this post on Zulip Richard Feldman (Jan 03 2025 at 16:25):

similarly, I think we can have:

view this post on Zulip Richard Feldman (Jan 03 2025 at 16:28):

I do think it's a good idea to keep things that depend on third-party versioning out of builtins, so those can be released independently of language releases

view this post on Zulip Richard Feldman (Jan 03 2025 at 16:28):

for example, new versions of Unicode and HTTP get released periodically

view this post on Zulip Richard Feldman (Jan 03 2025 at 16:28):

so those are probably better off as packages

view this post on Zulip Richard Feldman (Jan 03 2025 at 16:29):

but there aren't new versions of UTF-8, ASCII, or units of time (milliseconds etc.) getting released periodically, so those all seem safe to include in builtins

view this post on Zulip Isaac Van Doren (Jan 03 2025 at 17:46):

What’s the motivation for Clock.now! instead of Instant.now! ?

view this post on Zulip Richard Feldman (Jan 03 2025 at 17:47):

the Instant module should be in a platform-agnostic package (or a builtin module) that doesn't know how to do effects

view this post on Zulip Richard Feldman (Jan 03 2025 at 17:48):

I think this is a theme of how we can make nice platform-agnostic APIs without module params

view this post on Zulip Richard Feldman (Jan 03 2025 at 17:49):

e.g. Path, Instant, Duration, Request, and Response are all just data structures and pure functions, and none of them know how to do any effects at all, and are all completely platform-agnostic

view this post on Zulip Richard Feldman (Jan 03 2025 at 17:49):

and then File, Clock, and Http are platform-specific and do the actual effects, but work with those platform-agnostic data structure modules

view this post on Zulip Isaac Van Doren (Jan 03 2025 at 17:49):

Oh oops, I thought you had said Clock would be a built in module not platform supplied

view this post on Zulip Richard Feldman (Jan 03 2025 at 17:50):

and then you have things which need actual effects, like Bugsnag, Logger, and Pg, which depend directly the platform-agnostic data structure modules, and then have initialization functions which say "pass me me an effectful function that does the effects I need, and I'll store it and use it whenever I need to do an effect" - which is still platform-agnostic, and accomplishes the sandboxing, capabilities, etc.

view this post on Zulip Kilian Vounckx (Jan 03 2025 at 17:59):

I really like this separation! Gleam does something similar where all HTTP types are in the gleam/http package. And then both client and server packages can just use that one package, while third party packages can target any of the clients/servers. This seems to have the same benefits! Love it

view this post on Zulip Isaac Van Doren (Jan 07 2025 at 01:46):

I like the idea of having Date and DateTime types in the builtins so that packages can agree, and then leaving the gritty parts to external packages. I think that gets a good portion of the benefit of having dates and datetimes in the builtins without the downsides

view this post on Zulip Isaac Van Doren (Jan 07 2025 at 01:59):

I'd argue it's those functions that are naive, not the type itself.

Completely agree

view this post on Zulip Tobias Steckenborn (Dec 29 2025 at 08:36):

Memo (or for something like duration if duration is not absolute but can be positive or negative) it could be helpful to have something in the direction of this package as well https://github.com/mergestat/timediff (human readable time span / difference)

view this post on Zulip Luke Boswell (Dec 29 2025 at 09:05):

@Hannes and @Ian McLerran had a package that handled Dates if I remember

view this post on Zulip Jasper Woudenberg (Jul 08 2026 at 20:53):

Maybe of interest: I'm porting a small date/time library I wrote for Zig to Roc. The bones are there, Roc's way of dealing with operators I think go well with it! It needs some polish and I have some ideas for supporting iterators in the current API. Current progress and some thoughts on the API design here:
https://git.sr.ht/~jwoudenberg/roc-gregorian

There's a Date type, an opaque wrapper around an I32 representing days since the unix epoch, and a Time type, an opaque wrapper around an I64 representing seconds since the unix epoch. Then there's Time.Zone which is a record with a from_utc and to_utc function, so any platform should be able to provide timezones regardless of how those are represented on the host.

The API is restricted to operations on dates or times that are safe and unambiguous, I think that allows for a pretty tidy API (I know these kinds of libraries can grow massive). Despite that I think it's quite a powerful API. The original version in Zig I wrote for a calendar TUI application that among other things evaluaties recurring icalendar events, so pretty serious date/time-work, and it held up so far.

view this post on Zulip Richard Feldman (Jul 08 2026 at 20:57):

sweet! :smiley:

view this post on Zulip Richard Feldman (Jul 08 2026 at 20:59):

related: I like the name (and general API design) of Rust's Duration and then they also draw a distinction between Instant (guaranteed to monotonically increase as time progresses) and SystemTime which is used for things like timestamps on files that have no guarantees

view this post on Zulip Richard Feldman (Jul 08 2026 at 21:00):

maybe Time makes more sense as a name than SystemTime though? :thinking:

view this post on Zulip Richard Feldman (Jul 08 2026 at 21:02):

a thing I think would be a neat design: give Duration a times method that takes a plain integer (not another Duration - e.g. "5 seconds times two", not "5 seconds times 2 seconds"), and then additionally, don't give Duration its own from_numeral, but then give it nested types like Duration.Sec, Duration.Ms, etc. so then you can do stuff like:

ten_seconds : Duration
ten_seconds = 5.Sec * 2

which I think reads really nicely, and is totally type-safe! :smiley:

view this post on Zulip Richard Feldman (Jul 08 2026 at 21:03):

and then if you have a platform with something like Clock.ago! you can do the full Rails thing of:

two_days_ago = 2.Days->ago!()

view this post on Zulip Tobias Steckenborn (Jul 09 2026 at 03:51):

I really like https://effect.website/docs/data-types/duration/ and https://effect.website/docs/data-types/datetime/ to work with. Never the less there are already a few date ones if not mistaken, e.g. https://github.com/imclerran/Roc-IsoDate that somebody started to migrate to the new compiler (per another thread). So maybe it rather makes sense to consolidate what we’d like to have then starting 4 different ones right in the beginning :sweat_smile:

view this post on Zulip Jasper Woudenberg (Jul 09 2026 at 19:25):

A lot of date-time libraries have a lot of different things representing some type of timestamp, so then you get into the trouble of coming up with clear names for each of them. In the case of roc-gregorian, because there's just one time representing a time, it made sense to just claim the name Time for it.

I think many date-time libraries are so large because they unwittingly serve a couple of different distinct use cases. Like, I would use Instant if I needed to measure time, maybe in the context of benchmarking, or a tracing library. But why would that need to live in the same library that gets used for building calendar-like things? The fact these usecases use entirely different types shows that really these big time package are secretly multiple libraries bundled together. If Instant forms the basic in some separate time-measuring library, call it stopwatch, that simplifies both stopwatch and the date-time library that now doesn't need to support time-measurement anymore.

For instance, if you don't support time-measuring I don't think you need sub-second precision, which means you can cut a lot of API supporting sub-second time units (milliseconds, nanoseconds), and you can also cut Time needing to support different levels of precision.

With regards to Duration, I thought a lot about this and I think there's more downsides than upsides to having it:

view this post on Zulip Richard Feldman (Jul 11 2026 at 19:33):

yeah that all makes sense!

view this post on Zulip Richard Feldman (Jul 11 2026 at 19:34):

part of what I'm thinking about is that I'm thinking over time that we will want to have the stdlib cover more pure-function-only use cases than it does today (and also more than what Elm covers)

view this post on Zulip Richard Feldman (Jul 11 2026 at 19:34):

Json being in builtins now is part of that, and I think dates, times, and calendars would someday make sense to have in there too

view this post on Zulip Richard Feldman (Jul 11 2026 at 19:35):

I'm not in a big rush to add them or anything, and I think it makes sense to experiment with packages right now, just noting that I'm thinking about "what if these were builtins, what APIs would we want?" as part of considering what the best design is :smile:

view this post on Zulip Jasper Woudenberg (Jul 11 2026 at 19:40):

Yeah, that's totally fair! Having some of these types in the standard library is going to help interopability between different libraries and and platforms a lot! I've been thinking maybe that's where the biggest lift is: with types that are likely to flow across API boundaries. Date and time types surely qualify. Something like Instant I'm less sure about.

view this post on Zulip Richard Feldman (Jul 11 2026 at 19:49):

yeah fair point about Instant maybe not making sense as a builtin


Last updated: Jul 23 2026 at 13:15 UTC