Skip to content

Latest commit

 

History

History
639 lines (520 loc) · 33 KB

DESIGN.md

File metadata and controls

639 lines (520 loc) · 33 KB

The API design rationale for Jiff

This document discusses some of the design decisions that led to Jiff's API. The purpose of writing this document is to help folks understand why Jiff's API is the way it is, above and beyond "Jiff did it this way to match Temporal."

This document is written as an FAQ, although it is restricted in scope to the API design of Jiff. This isn't a FAQ for questions like, "How do I add 1 day to a zoned datetime?"

Unlike "Comparison with other Rust datetime crates," this document is opinionated. That is, some value judgments are expressed that are opinion based, and on which reasonable people may disagree.

Why name the library "jiff"?

I wanted something short and related to time for the library name. Most of the "obvious" names are taken.

I thought a lot about phrases or words that had some connection to time. There are more than you think. One phrase I heard a lot as a kid who grew up in New England was, "I'll be back in a jiff" or "I'll be back in a jiffy." The meaning of that phrase was, roughly, "I'll be back very soon." So "jiff" refers to some "short span of time." Since "jiff" was shorter than "jiffy," that's what I went with.

Jiff is pronounced like "gif" with a soft "g," as in "gem."

Why build another datetime library?

At the time of writing, there are four existing prominent datetime libraries for Rust: chrono, time, hifitime and icu. "Comparison with other Rust datetime crates" goes over the "facts of comparison" between the crates (mostly for just chrono and time), but it intentionally leaves out value judgments. To answer this question, I have to introduce value judgments and my opinions on existing libraries. These are opinions on which reasonable people can disagree. Moreover, since these are opinions meant to justify an alternative, these opinions tend to be oriented on the faults (as this author sees them) in existing crates as opposed to their benefits.

Broadly speaking, my view on Rust datetime libraries was that they had reached a local maximum, and there didn't seem to be much movement toward breaking out of that local maximum and getting to something that was categorically better. Much of my thoughts revolve around not just the functionality provided, but also the API design of these crates. Thus, I perceived a gap in the ecosystem that I felt like I could fill.

I'll share my brief thoughts on each crate. I'll cover "why not contribute to an existing library and make it better" in the next question.

chrono

In my view, Chrono is the closest to Jiff in terms of the functionality it provides. It has some support for DST safe arithmetic for example, and even some support for doing calendar math. But its support is incomplete. While one can add units of days to Chrono datetimes, Chrono lacks the ability to do math on multiple calendar units at the same time. Moreover, Chrono cannot produce calendar durations between two time zone aware datetimes.

Moreover, Chrono's integration with the IANA Time Zone Database is somewhat spotty. Support for it isn't included in the chrono crate itself, but is instead something you need to opt into with additional crates, such as chrono-tz or tzfile. And each crate comes with its own set of trade-offs. chrono-tz embeds the entire database into your binary and makes the time zones available at compile time, while tzfile reads the database from your system's copy of the database (i.e., /usr/share/zoneinfo on Unix). In my view, this creates a difficult situation for non-expert users of Chrono where the "right" choice isn't obvious. In my opinion, the default should be to read time zone transition data from the system's copy of the database on disk, and only bundle the data into the binary as a last resort when a copy of the database isn't reliably available (like on Windows). The main reason for this is that the database is frequently updated (a few times each year) since the rules for time zone transitions can change. If that time zone data is embedded into your binary, then you need to 1) wait for chrono-tz to update their data and 2) re-compile your application and ship it out to users.

In contrast, Jiff abstracts the method of time zone data discovery. That is, there are no API differences between "read time zone data from the system database" and "read embedded time zone data." Of course, Jiff provides options to choose which method to use (like forcing bundling on Unix), but all of this is transparent to users of Jiff itself. That is, Jiff tries to do the right thing by default. There's no need to go off and find a different crate to handle time zone data for you.

While on the topic of time zones, Chrono also has no support for serializing IANA time zone identifiers. This implies that if you have a time zone aware datetime in Chrono (whether by chrono-tz or tzfile), then you can't losslessly serialize and deserialize it. Namely, serialization will lose the time zone the datetime is associated with, and instead only include the offset. Then when it's deserialized, you're left with an offset-only datetime that won't, for example, provide DST safe arithmetic based on the original time zone. In contrast, Jiff follows RFC 9557 to support embedding IANA time zone identifiers in the serialized representation. For example, 2024-07-21T17:11-04[America/New_York].

There are a variety of other things that Jiff supports which Chrono does not, but that's covered in the comparison section between Jiff and Chrono.

As for API design, I found Chrono's API to be overengineered and difficult to deal with. This is a frustratingly vague complaint, but here are some things that I believe contribute to that opinion:

  • I've used Chrono for various things, and I found it very difficult to figure out from its API what the right operations to use were.
  • Chrono has been steadily deprecating huge portions of its API in favor of fallible routines. I find the resulting documentation difficult to read and the naming to be very clunky.
  • The fallible routines return a mixture of Option<T> and Result<T, E>. This is frustrating in my experience because the Option<T> usually needs to be converted to a human readable error message, and I think the library should make use of its contextual information to do this for you. Indeed, Jiff rarely returns Option<T> and instead returns Result<T, E> with a contextualized human readable error message. (Although I'm sure the error messages could use a lot of improvement.)
  • I find the use of generics in Chrono to be overengineered. It has some key benefits, for example, making the notion of "time zone" an open concept that can be defined by users of the crate. But in my opinion, this is rarely needed. The Chrono crate ecosystem makes use of this via the chrono-tz and tzfile crates, but Jiff covers both of those use cases (broadly speaking) automatically.
  • Chrono overall puts a large emphasis on "fixed offset" datetimes, but these are rarely the right abstraction to use. It's possible this is due to the fact that IANA time zone support is external to Chrono itself. Instead, Jiff just tries to do the right thing by default, and gives users IANA time zone support out of the box. Jiff does support fixed offset datetimes as well, but they are de-emphasized.
  • I find generic traits like Datelike to also be very confusing because they split the APIs of types like NaiveDate into concrete methods and generic methods, and there's no obvious rhyme or reason as to how those methods are split up.
  • Chrono's API doesn't offer clean on-ramps from civil ("naive") datetimes to time zone aware datetimes. For example, Chrono provides NaiveDate::succ_opt to get the next day, but this method isn't available on NaiveDateTime or DateTime. Instead, to implement it correctly on DateTime, you have to get the naive date, get the date for tomorrow via succ_opt, and then convert it back to the same NaiveDateTime and finally make it time zone aware by applying the original time zone to it. In contrast, in Jiff, it's just a matter of calling Zoned::tomorrow. Indeed, (almost) any method you can call on civil::Date in Jiff is also available on civil::DateTime and likewise for Zoned. This makes transitioning between datetime types very easy. Chrono almost appears to provide this same experience via traits like Datelike, but doesn't fully commit and, in my opinion, the end result has a feeling of arbitrariness to it.
  • Chrono lacks a standard timestamp type. You can approximate this with a DateTime<Utc>, but it's a more complicated type that includes a full datetime representation. In contrast, Jiff provides a Timestamp type for when you just want the number of seconds from the Unix epoch. And then integrates it with the rest of the datetime types in a consistent way.

time

My main gripe with the time crate is that it has no IANA Time Zone Database support at all. This means it cannot do DST safe arithmetic. Consequently, it emphasizes the use of "fixed offset" datetimes in a similar fashion as Chrono, except time does not provide any extension mechanism like a TimeZone trait. In my view, fixed offset datetimes are rarely the right thing to use. In my opinion, this makes writing correct datetime code with the time crate rather difficult. In contrast, Jiff provides full IANA Time Zone Database support, and it should be very rare to need fixed offset datetimes (although Jiff does support them via TimeZone::fixed).

The time crate also, at present, relies on unsound libc APIs for determining the current time zone offset, but makes them sound by requiring (by default) that UtcOffset::current_local_offset is only called when there is only 1 thread. This is a result of the libc APIs accessing the environment in a way that is unsynchronized with Rust's standard library access to the environment. This is likely a temporary limitation, but at time of writing, this state has persisted for quite some time already. In contrast, Jiff detects the current time zone of the platform on its own without use of libc, and thus sidesteps this issue. (This is also what Chrono does.)

I overall find the API of time to be easier to understand than Chrono, likely because there are fewer generics. But time is also supporting a lot less than Chrono and Jiff (because of the missing time zone support).

As with Chrono, I've done a more detailed comparison between Jiff and time.

hifitime

hifitime is more of a specialized datetime library suited to scientific applications, and so while Jiff and hifitime have overlapping use cases, hifitime fundamentally has a different target demographic than Jiff. As noted in a comparison between Jiff and hifitime, hifitime doesn't have any time zone support, but it does support conversions between many different time scales and leap second support. Leap second support, in this context, means that the durations between two points in time take leap seconds into account in hifitime, but Jiff pretends as if they don't exist.

In terms of building a new datetime library, I felt like hifitime wasn't really targeting the "general purpose datetime library" use case that I felt chrono and time were. And so, whether it existed or not didn't really impact whether another general purpose datetime library should be built.

icu

icu is, as I understand it, still under construction with respect to datetime support. For example, it doesn't have IANA Time Zone Database support. But, it does support locale aware formatting of datetimes and non-Gregorian calendars.

When I started working on Jiff, I didn't have a good understanding of what the icu crate offers. I still don't really. In part because the API is difficult for me to understand and in part because I haven't dedicated a ton of time to studying its API. But either way, I don't think it is currently in a position to be a general purpose datetime library and I wasn't clear on what its goals were.

Since I haven't spent a lot of time with icu, I didn't have much to say about it in my comparison with it and Jiff.

Why not contribute to an existing library instead of building a new one?

Given that Rust already has at least two prominent datetime libraries, doesn't adding another one just make things worse? And why not contribute to an existing library to make it better instead of starting over from scratch?

I first want to say that I acknowledge that throwing a new crate into the ecosystem, and adding yet another choice, does actually come with downsides. There is a cost to having too many choices, and when possible, I do believe it is better to improve an existing project rather than start a new one.

Improving existing projects can be difficult. Jiff has a different design than both chrono and time. Evolving either one of those crates into what Jiff is would, in my view, require a huge amount of resources. Not just in time, but in social capital as well. Because it wouldn't be greenfield development done by one person making all of the design choices, but instead someone from outside the project trying to convince the maintainers of established projects to move in a radically different direction. I know what it's like to be on the side of maintaining an established API for a library with a lot of users. There is a huge inertial cost to making sweeping API changes.

Moreover, when I started Jiff, I was not a domain expert in datetime libraries or datetime handling in general. Therefore, my opinion that chrono and time could be better would arguably not carry a lot of weight. It was only through the process of actually building a datetime library did I learn enough to form nuanced opinions about the status quo. When I started, my opinions were much more vague (but still strong enough to start this project).

On top of all of this, I was intrinsically motivated to work on this problem. I found it very interesting, and especially because I perceived there to be a gap in the ecosystem that I thought I could fill in. I had a vision for what a datetime library should look like. And it took a lot of iteration to get from my initial vision to something that works in practice. Doing this on an existing datetime library with real users would be extremely difficult.

And speaking as someone who has had folks publish better versions of some of my own crates, I know what it's like to be on the other end of this. Sometimes you just have to start fresh.

Are there any published alternative perspectives on Rust datetime libraries?

Here's a list. More may be added in the future:

Why are there two duration types?

The two duration types provided by Jiff are Span and SignedDuration. A SignedDuration is effectively identical to a std::time::Duration, but it's signed instead of unsigned. A Span is also a duration type, but is likely different than most other duration types you've used before.

While a SignedDuration can be thought of as a single integer corresponding to the number of nanoseconds between two points in time, a Span is a collection of individual unit values that combine to represent the difference between two point in time. Stated more concretely, while the spans 2 hours and 120 minutes both correspond to the same duration of time, when represented as a Jiff Span, they correspond to two distinct values in memory. This is something that is fundamentally not expressible by a type like SignedDuration, where 2 hours and 120 minutes are completely indistinguishable.

One of the key advantages of a Span is that it can represent units of non-uniform length. For example, not every month has the same number of days, but a Span can still represent units of months because it tracks the values of each unit independently. For example, Jiff is smart enough to know that the difference between 2024-03-01 and 2024-04-01 is the same number of months as 2024-04-01 and 2024-05-01, even though the number of days is different:

use jiff::{civil::date, ToSpan, Unit};

fn main() -> anyhow::Result<()> {
    let date1 = date(2024, 3, 1);
    let date2 = date(2024, 4, 1);
    let date3 = date(2024, 5, 1);

    // When computing durations between `Date` values,
    // the spans default to days.
    assert_eq!(date1.until(date2)?, 31.days());
    assert_eq!(date2.until(date3)?, 30.days());

    // But we can request bigger units!
    assert_eq!(date1.until((Unit::Month, date2))?, 1.month());
    assert_eq!(date2.until((Unit::Month, date3))?, 1.month());

    Ok(())
}

While most folks are very in tune with the fact that years and months have non-uniform length, a less obvious truth is that days themselves also have non-uniform length in the presence of time zones. For example, 2024-03-10 in America/New_York was only 23 hours long (the region entered daylight saving time, creating a gap in time), while 2024-11-03 was 25 hours long (the region left daylight saving time, creating a fold in time). Being unaware of this corner case leads to folks assuming that "1 day" and "24 hours" are always exactly equivalent. But they aren't. The design of Jiff leans into this and ensures that so long as you're using Span to encode a concept of days and are doing arithmetic with it on Zoned values, then you can never get it wrong. Jiff will always take time zones into account when dealing with units of days or bigger.

The design of Span comes from Temporal, which uses only one duration type. From that issue, there are some significant advantages to using a Span. In my own words:

  • It more closely lines up with ISO 8601 durations, which themselves combine calendar and clock units.
  • With a Span, it is very easy to move between 5 years 2 months and the number of hours in that same span.
  • Jiff's Span type specifically represents each unit as distinct from the others. In contrast, most absolute duration types (like std::time::Duration and Jiff's own SignedDuration), are "just" a 96-bit integer number of nanoseconds. This means that, for example, 1 hour 30 minutes is impossible to differentiate from 90 minutes. But depending on the use case, you might want one or the other. Jiff's Span design (copied from Temporal) enables users to express durations in whatever units they want. And this expression can be manipulated via APIs like Span::round in intuitive ways.

A SignedDuration is still useful in some respects. For example, when you need tighter integration with the standard library's std::time::Duration (since a SignedDuration is the same, but just signed), or when you need better performance than what Span gives you. In particular, since a Span keeps track of the values for each individual unit, it is a much heavier type than a SignedDuration. It uses up more stack space and also required more computation to do arithmetic with it.

Why isn't there a TimeZone trait?

First, let's start by explaining what a TimeZone is. In Jiff, a TimeZone is a concrete type that cannot be extended by users of Jiff. Instead, users of Jiff are forced to use one of three different kinds of time zones:

  • A "fixed offset" time zone where the civil time for any particular instant is computed by simply adding or subtracting a fixed number of seconds from UTC. The TimeZone::fixed constructor enables callers to build time zones with any offset within the limits imposed by Jiff.
  • A POSIX time zone, typically set via the TZ environment variable. These are rarely used by end users, but do provide a way to specify a rule for when daylight saving time transitions occur thoughtout the year. (But it does not support historical transitions that might not conform to the current rule.) The TimeZone::posix constructor enables callers to build a TimeZone with a POSIX time zone string.
  • TZif formatted data, usually from the IANA Time Zone Database. This data contains historical time zone transitions in addition to rules governing the future in the form of POSIX TZ strings.The TimeZone::tzif constructor enables callers to build a TimeZone with any TZif formatted data.

The jiff::tz::TimeZoneDatabase automatically looks for TZif formatted files in your system's copy of the IANA Time Zone Database, usually at /usr/share/zoneinfo. (On Windows, Jiff embeds a copy of the IANA Time Zone Database into the compiled artifact itself.)

So why isn't TimeZone a trait? Well, the above formulation should cover the vast majority of use cases. And even if that doesn't cover everything, it is possible for callers to use TimeZone::tzif to construct arbitrary time zones by building their own TZif data. This is a somewhat large hurdle though, so if this is something that is commonly needed, I'm open to exploring other options for building custom time zones. For example, perhaps we introduce a way to describe time zone transitions in Rust code that can then be used to build a TimeZone directly. But, the benefit of TZif is that it is inter-operable and a standard. There are tools that can build them.

An important thing to note here is that I actually approach questions like "Why isn't TimeZone a trait?" as instead "Why should TimeZone be a trait?" In particular, I personally perceive costs to introducing generics, especially on a fundamental type in the crate. For example, if TimeZone were a trait, then Zoned would not be a concrete type. It would be generic over a type parameter that implements the TimeZone trait. This in turn implies that anyone using a Zoned in their own types or APIs needs to think about the TimeZone trait and likely incorporate it into their own type signatures. This is because a TimeZone trait implies an open system that infects everything it touches. The complexity isn't contained. But a concrete TimeZone type, like what Jiff has, encapsulates everything there is about time zones.

(Making Zoned generic over a type parameter with a default type does contain the complexity in some cases, but not all. I explored using default type parameters in Jiff for supporting leap seconds, and it was overall quite awkward in my opinion.)

The trade off is that we do give up some flexibility. For example, Chrono uses a TimeZone trait design. This enables external crates to provide their own implementations of the TimeZone trait. But the two principle instances of this occurring, chrono-tz and tzfile, are both supported by Jiff itself. With that said, one key advantage of Chrono's design is that it permits its zone aware datetime type (DateTime<T>) to be Copy if T is Copy. It is somewhat difficult (although not literally impossible) to make a TZif-backed time zone implement Copy, but a reference to it is Copy and a reference to it can still implement Chrono's TimeZone trait. (And indeed, this is what the tzfile crate does.) This ultimately leads to more flexibility compared to Jiff, where its Zoned type embeds a TimeZone and a TimeZone cannot easily be made Copy without giving up something else.

My opinions on the costs of generics tend to overestimate them compared to many others in my experience, so your mileage may vary on where you land on this issue. Buy in my opinion, being able to just write Zoned as a concrete type without any generics is a huge win for comprehensibility.

Why doesn't TimeZone implement Copy?

When initially setting out to build Jiff, I really wanted the TimeZone type to implement Copy. The reason why I wanted it to implement Copy is because I wanted all datetime types to have "plain old data" semantics. That is, I want callers to think of them as small immutable bits of data that can be freely copied without worry. This makes APIs a little nicer because you can ask for a Zoned instead of a &Zoned, assuming the Zoned type is small enough.

But, in order for Zoned to be Copy, it must be the case that TimeZone is Copy. This is because Zoned embeds a TimeZone. Indeed, this is the reason for its existence: it couples an instant in time with a particular geographic region. This makes it possible to expose very ergonomic high level APIs that don't require the caller to keep passing in a TimeZone value repeatedly.

So, how can a TimeZone be Copy? Well, both fixed offset and POSIX time zones could be Copy. There's no huge challenge in that. (Internally, a POSIX time zone is not currently Copy because there's no reason for it given what we're about to discuss.) The main challenge is the TZif-backed time zone. TZif formatted data can contain an arbitrary number of time zone transitions. There is just no way to avoid some kind of dynamic memory allocation.

Since we need dynamic memory allocation, there is really only one way to make it Copy at this point: introduce some kind of caching mechanism with a small Copy identifier that lets us "look up" the time zone in some kind of global or thread-local cache. I thought a lot about how I might wire this together, and I could not come up with a satisfactory design. I believe garbage collection is the main challenge, but also synchronization overhead for accessing a time zone is likely also a problem. The nice benefit of the TimeZone type as it exists now is that it's just data. While getting a TimeZone from a TimeZoneDatabase might require synchronization and possibly even I/O if the cache for it was invalidated, a TimeZone itself requires no synchronization whatsoever to use it. It is just an Arc internally to make it pointer-sized.

Why isn't there a SystemZoned type? Or a OffsetZoned type?

Some datetime libraries have multiple different zone aware datetime types. Jiff opts to have just one, and embeds support for the different types of time zones that most people will ever need into that one type. Jiff does this via the TimeZone type, which can be a fixed offset, a POSIX time zone or TZif-backed (usually from the IANA Time Zone Database).

Why doesn't Jiff support leap seconds?

The short summary is that the use cases for leap second support are rather limited, and the effect they have on overall API complexity is quite large. That is, I believe they would make the API of Jiff more complicated than the value they bring to the domain.

A standard work-around for part of the leap second problem---and usually the one people care about---is to use custom TZif data that describes when each of the leap seconds occurs. In effect, you can treat TAI as its own time zone. This enables callers to compute accurate durations of time that span a leap second (positive or negative). And indeed, so long as you build that TZif data, Jiff supports this via TimeZone::tzif.

I wrote a lot more about leap seconds on the issue tracker.

Why isn't there any integration with std::time::Duration?

The omission of std::time::Duration from the jiff 0.1.0 release was quite intentional. Firstly, a Duration is an absolute time, meaning there is no way to distinguish between 3600 seconds and 1 hour. Secondly, a Duration is unsigned, which makes some API interactions awkward.

It is likely that there will eventually be at least some integration points with std::time::Duration. This issue discusses some of the challenges.

What are the ZoneDifference and ZonedRound types for?

The ZonedDifference and ZonedRound are builders for expressing the parameters to the functions Zoned::{since, until} and Zoned::round, respectively. For example, this:

use jiff::{civil::date, ToSpan};

let zdt1 = date(2024, 7, 16).at(22, 3, 23, 0).intz("America/New_York")?;
let zdt2 = date(2024, 8, 16).at(22, 3, 59, 0).intz("America/New_York")?;
assert_eq!(zdt1.until(&zdt2)?, 744.hours().seconds(36));

# Ok::<(), Box<dyn std::error::Error>>(())

Is equivalent to:

use jiff::{civil::date, ToSpan, ZonedDifference};

let zdt1 = date(2024, 7, 16).at(22, 3, 23, 0).intz("America/New_York")?;
let zdt2 = date(2024, 8, 16).at(22, 3, 59, 0).intz("America/New_York")?;
assert_eq!(zdt1.until(ZonedDifference::new(&zdt2))?, 744.hours().seconds(36));

# Ok::<(), Box<dyn std::error::Error>>(())

The point of this is that ZonedDifference permits specifying additional configuration. For example, rounding the span returned:

use jiff::{civil::date, ToSpan, Unit, ZonedDifference};

let zdt1 = date(2024, 7, 16).at(22, 3, 23, 0).intz("America/New_York")?;
let zdt2 = date(2024, 8, 16).at(22, 3, 59, 0).intz("America/New_York")?;
assert_eq!(
    zdt1.until(ZonedDifference::new(&zdt2).smallest(Unit::Minute))?,
    744.hours(),
);

# Ok::<(), Box<dyn std::error::Error>>(())

By using a dedicated type to represent the parameters, we can enable ergonomic uses of the API for common cases (by using From<&Zoned> for ZonedDifference trait implementations) while still permitting callers to provide additional configuration.

An alternative API would be to remove the additional parameters to Zoned::until, and instead require callers to do span rounding themselves explicitly. But this is more verbose and requires repeating the correct zoned datetime to indicate how to interpret non-uniform units. For example, instead of this:

use jiff::{civil::date, ToSpan, Unit, ZonedDifference};

let zdt1 = date(2024, 7, 16).at(22, 3, 23, 0).intz("America/New_York")?;
let zdt2 = date(2024, 8, 16).at(22, 3, 59, 0).intz("America/New_York")?;
let diff = ZonedDifference::new(&zdt2)
    .largest(Unit::Month)
    .smallest(Unit::Minute);
assert_eq!(zdt1.until(diff)?, 1.month());

# Ok::<(), Box<dyn std::error::Error>>(())

One would need to do this:

use jiff::{civil::date, RoundMode, SpanRound, ToSpan, Unit};

let zdt1 = date(2024, 7, 16).at(22, 3, 23, 0).intz("America/New_York")?;
let zdt2 = date(2024, 8, 16).at(22, 3, 59, 0).intz("America/New_York")?;
let span = zdt1.until(&zdt2)?;
let rounded = span.round(
    SpanRound::new()
        .largest(Unit::Month)
        .smallest(Unit::Minute)
        .relative(&zdt1)
        .mode(RoundMode::Trunc),
)?;
assert_eq!(rounded, 1.month());

# Ok::<(), Box<dyn std::error::Error>>(())

This is somewhat fiddly and easy to get wrong. Moreover, the Zoned::until API, when rounding is enabled, will automatically use RoundMode::Trunc, since this is what usually expects when computing the span between two datetimes. But span rounding uses RoundMode::HalfExpand by default, corresponding to how you were likely taught to round in school. (Rounds to the nearest unit, with ties breaking away from zero.)

Similar reasoning applies to other "parameter builder" types like civil::DateTimeDifference as well.

Why isn't Timestamp called Instant?

The main reason why is because of the existence of std::time::Instant. While that doesn't in and of itself prevent Jiff from using the name Instant, it creates a naming clash with something that is similar but different. Namely, a Jiff Timestamp corresponds to a time from your system clock, where as an Instant represents monotonic time. The system clock might change or even go backwards, where as a monotonic instant will always produce time that is greater than or equal to a previous time.

An Instant is, for example, something you might use to measure the time something takes in a program. Like in capturing a measurement for a benchmark. Conversely, a Timestamp is something you use to represent time as represented by the system. In particular, a Timestamp is like a std::time::SystemTime and not a std::time::Instant.

While Temporal uses the name Instant for their equivalent of Jiff's Timestamp type, using the name Instant in Jiff would likely result in serious confusion and conflicts in names when someone wants to use both an Instant and a Timestamp in the same namespace.