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.
Is this for the standard library or for an external library?
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.
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
That is, should our standard library have stuff that stays in Roc forever?
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
Though there are plenty of languages like Python that have "all the basics" in their standard library
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
We already have a few different impls in the wild
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.
Good point. We should probably make a combined time library, since we'll want a single, common type for datetime, date, and times
I think the safer start is to make a
roc-lang/datetimelibrary 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
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.
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:
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.
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?
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.
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
or like Date and DateFormat maybe
and it is just completely not concerned with time zones even a little bit
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
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?
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...
oh I don't think any of these would use anything from the platform
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?"
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
and I think it's totally fine for those 2 functions to just live in a different module from the rest of the API
like with time I'm thinking:
Duration and Instant live in their own modules and are all pure functions and data structures (likely should be builtins)Clock module with Clock.now!() that returns an Instantsimilarly, I think we can have:
Request and Response modules that are just data structures for HTTP requests and responses, totally platform-agnostic (could be builtins I suppose if we really wanted to)Http module with Http.get!(), Http.request!(), etc. that uses the platform-agnostic Request and Response modulesI 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
for example, new versions of Unicode and HTTP get released periodically
so those are probably better off as packages
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
What’s the motivation for Clock.now! instead of Instant.now! ?
the Instant module should be in a platform-agnostic package (or a builtin module) that doesn't know how to do effects
I think this is a theme of how we can make nice platform-agnostic APIs without module params
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
and then File, Clock, and Http are platform-specific and do the actual effects, but work with those platform-agnostic data structure modules
Oh oops, I thought you had said Clock would be a built in module not platform supplied
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.
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
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
I'd argue it's those functions that are naive, not the type itself.
Completely agree
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)
@Hannes and @Ian McLerran had a package that handled Dates if I remember
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.
sweet! :smiley:
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
maybe Time makes more sense as a name than SystemTime though? :thinking:
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:
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!()
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:
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:
Time type.Date and Time type, but then that raises a question with regards to how Duration should interact with both of these. It doesn't make sense to add a duration representing fractional days to a Date. Should only Time use Duration? Should Date and Time have their own Duration type?Duration types I think a lot of API will be consumed converting these different durations into one-another.time_a + 5 * 60 might not be super pretty, but I think it's reasonably clear. And now most of the API uses concrete types, which all other things being equal seems nice.yeah that all makes sense!
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)
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
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:
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.
yeah fair point about Instant maybe not making sense as a builtin
Last updated: Jul 23 2026 at 13:15 UTC