gleam/time/timestamp
Welcome to the timestamp module! This module and its Timestamp
type are
what you will be using most commonly when working with time in Gleam.
A timestamp represents a moment in time, represented as an amount of time since the calendar time 00:00:00 UTC on 1 January 1970, also known as the Unix epoch.
Wall clock time and monotonicity
Time is very complicated, especially on computers! While they generally do a good job of keeping track of what the time is, computers can get out-of-sync and start to report a time that is too late or too early. Most computers use “network time protocol” to tell each other what they think the time is, and computers that realise they are running too fast or too slow will adjust their clock to correct it. When this happens it can seem to your program that the current time has changed, and it may have even jumped backwards in time!
This measure of time is called wall clock time, and it is what people commonly think of when they think of time. It is important to be aware that it can go backwards, and your program must not rely on it only ever going forwards at a steady rate. For example, for tracking what order events happen in.
This module uses wall clock time. If your program needs time values to always increase you will need a monotonic time instead. It’s uncommon that you would need monotonic time, one example might be if you’re making a benchmarking framework.
The exact way that time works will depend on what runtime you use. The Erlang documentation on time has a lot of detail about time generally as well as how it works on the BEAM, it is worth reading. https://www.erlang.org/doc/apps/erts/time_correction.
Converting to local time
Timestamps don’t take into account time zones, so a moment in time will
have the same timestamp value regardless of where you are in the world. To
convert them to local time you will need to know the offset for the time
zone you wish to use, likely from a time zone database. See the
gleam/time/calendar
module for more information.
Types
The main time type, which you should favour over other types such as calendar time types. It is efficient, unambiguous, and it is not possible to construct an invalid timestamp.
The most common situation in which you may need a different time data structure is when you need to display time to human for them to read. When you need to do this convert the timestamp to calendar time when presenting it, but internally always keep the time as a timestamp.
pub opaque type Timestamp
Functions
pub fn add(timestamp: Timestamp, duration: Duration) -> Timestamp
Add a duration to a timestamp.
Examples
add(from_unix_seconds(1000), duration.seconds(5))
// -> from_unix_seconds(1005)
pub fn compare(left: Timestamp, right: Timestamp) -> Order
Compare one timestamp to another, indicating whether the first is further into the future (greater) or further into the past (lesser) than the second.
Examples
compare(from_unix_seconds(1), from_unix_seconds(2))
// -> order.Lt
pub fn difference(left: Timestamp, right: Timestamp) -> Duration
Calculate the difference between two timestamps.
This is effectively substracting the first timestamp from the second.
Examples
difference(from_unix_seconds(1), from_unix_seconds(5))
// -> duration.seconds(4)
pub fn from_calendar(
date date: Date,
time time: TimeOfDay,
offset offset: Duration,
) -> Timestamp
Create a Timestamp
from a human-readable calendar time.
Examples
timestamp.from_calendar(
date: calendar.Date(2024, calendar.December, 25),
time: calendar.TimeOfDay(12, 30, 50, 0),
offset: calendar.utc_offset,
)
|> timestamp.to_rfc3339(calendar.utc_offset)
// -> "2024-12-25T12:30:50Z"
pub fn from_unix_seconds(seconds: Int) -> Timestamp
Create a timestamp from a number of seconds since 00:00:00 UTC on 1 January 1970.
pub fn from_unix_seconds_and_nanoseconds(
seconds seconds: Int,
nanoseconds nanoseconds: Int,
) -> Timestamp
Create a timestamp from a number of seconds and nanoseconds since 00:00:00 UTC on 1 January 1970.
JavaScript int limitations
Remember that JavaScript can only perfectly represent ints between positive and negative 9,007,199,254,740,991! If you only use the nanosecond field then you will almost certainly not get the date value you want due to this loss of precision. Always use seconds primarily and then use nanoseconds for the final sub-second adjustment.
pub fn parse_rfc3339(input: String) -> Result(Timestamp, Nil)
Parses an RFC 3339 formatted time string into a Timestamp
.
Examples
let assert Ok(ts) = timestamp.parse_rfc3339("1970-01-01T00:00:01Z")
timestamp.to_unix_seconds_and_nanoseconds(ts)
// -> #(1, 0)
Parsing an invalid timestamp returns an error.
let assert Error(Nil) = timestamp.parse_rfc3339("1995-10-31")
Notes
- Follows the grammar specified in section 5.6 Internet Date/Time Format of RFC 3339 https://datatracker.ietf.org/doc/html/rfc3339#section-5.6.
- The
T
andZ
characters may alternatively be lower caset
orz
, respectively. - Full dates and full times must be separated by
T
ort
, not any other character such as a space ( - Leap seconds rules are not considered. That is, any timestamp may
specify digts
00
-60
for the seconds. - Any part of a fractional second that cannot be represented in the
nanosecond precision is tructated. That is, for the time string,
"1970-01-01T00:00:00.1234567899Z"
, the fractional second.1234567899
will be represented as123_456_789
in theTimestamp
.
pub fn system_time() -> Timestamp
Get the current system time.
Note this time is not unique or monotonic, it could change at any time or even go backwards! The exact behaviour will depend on the runtime used. See the module documentation for more information.
On Erlang this uses erlang:system_time/1
. On JavaScript this uses
Date.now
.
pub fn to_calendar(
timestamp: Timestamp,
offset: Duration,
) -> #(Date, TimeOfDay)
Convert a Timestamp
to calendar time, suitable for presenting to a human
to read.
If you want a machine to use the time value then you should not use this
function and should instead keep it as a timestamp. See the documentation
for the gleam/time/calendar
module for more information.
Examples
timestamp.from_unix_seconds(0)
|> timestamp.to_calendar(calendar.utc_offset)
// -> #(Date(1970, January, 1), TimeOfDay(0, 0, 0, 0))
pub fn to_rfc3339(
timestamp: Timestamp,
offset: Duration,
) -> String
Convert a timestamp to a RFC 3339 formatted time string, with an offset supplied as an additional argument.
The output of this function is also ISO 8601 compatible so long as the offset not negative. Offsets have at-most minute precision, so an offset with higher precision will be rounded to the nearest minute.
If you are making an API such as a HTTP JSON API you are encouraged to use Unix timestamps instead of this format or ISO 8601. Unix timestamps are a better choice as they don’t contain offset information. Consider:
- UTC offsets are not time zones. This does not and cannot tell us the time zone in which the date was recorded. So what are we supposed to do with this information?
- Users typically want dates formatted according to their local time zone. What if the provided UTC offset is different from the current user’s time zone? What are we supposed to do with it then?
- Despite it being useless (or worse, a source of bugs), the UTC offset creates a larger payload to transfer.
They also uses more memory than a unix timestamp. The way they are better than Unix timestamp is that it is easier for a human to read them, but this is a hinderance that tooling can remedy, and APIs are not primarily for humans.
Examples
to_rfc3339(from_unix_seconds(1000), 0)
// -> "1970-01-01T00:00:00Z"
pub fn to_unix_seconds(timestamp: Timestamp) -> Float
Convert the timestamp to a number of seconds since 00:00:00 UTC on 1 January 1970.
There may be some small loss of precision due to Timestamp
being
nanosecond accurate and Float
not being able to represent this.
pub fn to_unix_seconds_and_nanoseconds(
timestamp: Timestamp,
) -> #(Int, Int)
Convert the timestamp to a number of seconds and nanoseconds since 00:00:00 UTC on 1 January 1970. There is no loss of precision with this conversion on any target.