jiff-0.1.28/.cargo_vcs_info.json0000644000000001360000000000100120420ustar { "git": { "sha1": "86bff5f10fd3bf24a8fc77b5199dc4f0ca4e2c88" }, "path_in_vcs": "" }jiff-0.1.28/CHANGELOG.md000064400000000000000000000656461046102023000124640ustar 00000000000000# CHANGELOG 0.1.28 (2025-01-27) =================== This is a small release that just removes the dev-dependency on `serde_yml`. It has been replaced with the deprecated `serde_yaml`. See [this post about `serde_yml` shenanigans](https://x.com/davidtolnay/status/1883906113428676938) for why this was done. Note that this was only a dev-dependency and thus doesn't impact folks using Jiff. Bug fixes: * [#225](https://github.com/BurntSushi/jiff/pull/225): Remove dependency on `serde_yml` in favor of `serde_yaml`. 0.1.27 (2025-01-25) =================== This is a small release with a bug fix for precision loss in some cases when doing arithmetic on `Timestamp` or `Zoned`. Bug fixes: * [#223](https://github.com/BurntSushi/jiff/issues/223): Fix the check for fractional seconds before taking the fast path. 0.1.26 (2025-01-23) =================== This is a small release with another deprecation and a new API for doing prefix parsing via `strptime`. There's also a bug fix for a corner case when dealing with daylight saving time gaps with the `Zoned::with` API. Deprecations: * [#210](https://github.com/BurntSushi/jiff/pull/210): Deprecate `ISOWeekDate::to_date` in favor of `ISOWeekDate::date`. Enhancements: * [#209](https://github.com/BurntSushi/jiff/issues/209): Add `fmt::strtime::BrokenDownTime::parse_prefix` for parsing only a prefix. Bug fixes: * [#211](https://github.com/BurntSushi/jiff/issues/211): Fix unintuitive behavior of `Zoned::with` when time falls in a DST gap. 0.1.25 (2025-01-21) =================== This release contains a number of deprecations in preparation for a `jiff 0.2` release. The deprecations are meant to facilitate a smoother transition. The deprecations, when possible, come with new APIs that will permit users to write forward compatible code that will work in both `jiff 0.1` and `jiff 0.2`. This release also includes a handful of new conversion specifiers in Jiff's `strftime` and `strptime` APIs. This improves compatibility with the analogous implementation with GNU libc. Deprecations: * [#28](https://github.com/BurntSushi/jiff/issues/28): The `intz` methods on `Zoned`, `Timestamp`, `civil::DateTime` and `civil::Date` have been deprecated in favor of `in_tz`. * [#32](https://github.com/BurntSushi/jiff/issues/32): The `Eq` and `PartialEq` trait implementations on `Span` have been deprecated in favor of using the new `SpanFieldwise` type. * [#48](https://github.com/BurntSushi/jiff/issues/48): Silently assuming days are always 24 hours in some `Span` APIs has now been deprecated. This will become an error in `jiff 0.2`. To continue assuming days are 24 hours without a relative reference date, you can use the new `SpanRelativeTo::days_are_24_hours` API. In `jiff 0.1`, you'll seen a WARN-level log message emitted if you're code will be broken by `jiff 0.2`. * [#147](https://github.com/BurntSushi/jiff/issues/147): Both `%V` and `%:V` have been deprecated in favor of `%Q` and `%:Q`. In `jiff 0.2`, `%V` will correspond to the ISO 8601 week number and `%:V` will result in an error. This change was made to improve compatibility with other `strtime` implementations. `%V` and `%:V` continue to correspond to IANA time zone identifiers in `jiff 0.1`, but using them for parsing or formatting will result in a WARN-level deprecation message. Enhancements: * [#147](https://github.com/BurntSushi/jiff/issues/147): Adds a number of new conversion specifiers to Jiff's `strftime` and `strptime` APIs. Specifically, `%C`, `%G`, `%g`, `%j`, `%k`, `%l`, `%n`, `%R`, `%s`, `%t`, `%U`, `%u`, `%W`, `%w`. Their behavior should match the corresponding specifiers in GNU libc. 0.1.24 (2025-01-16) =================== This release updates Jiff's bundled copy of the [IANA Time Zone Database] to `2025a`. See the [`2025a` release announcement] for more details. Enhancements: * [#206](https://github.com/BurntSushi/jiff/pull/206): Update `jiff-tzdb` to `2025a`. [`2025a` release announcement]: https://lists.iana.org/hyperkitty/list/tz-announce@iana.org/thread/MWII7R3HMCEDNUCIYQKSSTYYR7UWK4OQ/ 0.1.23 (2025-01-13) =================== This release includes some bug fixes, particularly for compilation on `aarch64-linux-android`. There are also some minor enhancements, such as making `Zoned::iso_week_date` a convenience function for `civil::Date::iso_week_date`, in line with similar functions. My current plan is to make a reasonably quick transition to `jiff 0.2` with a few pending breaking changes. I will be making some `jiff 0.1` releases with deprecations in order to make the transition as smooth as possible. If all goes well with `jiff 0.2`, then my plan is still to do a Jiff 1.0 release in the Summer of 2025. Deprecations: * [#197](https://github.com/BurntSushi/jiff/discussions/197): `Date::to_iso_week_date` has been deprecated in favor of `Date::iso_week_date`. Enhancements: * [#196](https://github.com/BurntSushi/jiff/discussions/196): Improve ISO week date documentation regarding weekday offsets. * [#197](https://github.com/BurntSushi/jiff/discussions/197): Add `Zoned::iso_week_date`, `DateTime::iso_week_date` and `Date::iso_week_date`. Bug fixes: * [#200](https://github.com/BurntSushi/jiff/issues/200): Fix compilation failure on Android in a Termux shell. * [#202](https://github.com/BurntSushi/jiff/pull/202): Re-add license files to crate artifact. 0.1.22 (2025-01-12) =================== This release adds support for Android. This support means that Jiff will automatically read its special concatenated time zone database, and will read the `persist.sys.timezone` property to determine the system's current time zone. See [PLATFORM] for more specific information about Android support. Note that this release also removed all non-essential files (including tests and test data) for the artifact uploaded to crates.io. If you need or want these files, please open a new issue. Enhancements: * [#140](https://github.com/BurntSushi/jiff/issues/140): Add support for the Android platform. 0.1.21 (2025-01-04) =================== This release includes a new API for setting the unit designator label in a friendly formatted duration for zero-length durations. Enhancements: * [#192](https://github.com/BurntSushi/jiff/issues/192): Add option to the friendly printer for setting the unit when writing a zero-length duration. 0.1.20 (2025-01-03) =================== This release inclues a new type, `Pieces`, in the `jiff::fmt::temporal` sub-module. This exposes the individual components of a parsed Temporal ISO 8601 datetime string. It allows users of Jiff to circumvent the checks in the higher level parsing routines that prevent you from shooting yourself in the foot. For example, parsing into a `Zoned` will return an error for raw RFC 3339 timestamps like `2025-01-03T22:03-05` because there is no time zone annotation. Without a time zone, Jiff cannot do time zone aware arithmetic and rounding. Instead, such a datetime can only be parsed into a `Timestamp`. This lower level `Pieces` API now permits users of Jiff to parse this string into its component parts and assemble it into a `Zoned` if they so choose. Enhancements: * [#188](https://github.com/BurntSushi/jiff/issues/188): Add `fmt::temporal::Pieces` for granular datetime parsing and formatting. 0.1.19 (2025-01-02) =================== This releases includes a UTF-8 related bug fix and a few enhancements. Firstly, a `Span`'s default `Display` implementation now writes uppercase unit designator labels. That means you'll get `P1Y2M3DT4H5M6S` instead of `P1y2m3dT4h5m6s` by default. You can restore previous behavior via `jiff::fmt::temporal::SpanPrinter::lowercase`. This change was made to improve interoperability. Secondly, `SignedDuration` now supports rounding via `SignedDuration::round`. Note that it only supports rounding time units (hours or smaller). In order to round with calendar units, you'll still need to use a `Span`. Enhancements: * [#130](https://github.com/BurntSushi/jiff/issues/130): Document value ranges for methods like `year`, `day`, `hour` and so on. * [#187](https://github.com/BurntSushi/jiff/issues/187): Add a rounding API (for time units only) on `SignedDuration`. * [#190](https://github.com/BurntSushi/jiff/issues/190): `Span` and `SignedDuration` now use uppercase unit designator labels in their default ISO 8601 `Display` implementation. Bug fixes: * [#155](https://github.com/BurntSushi/jiff/issues/155): Relax `strftime` format strings from ASCII-only to all of UTF-8. 0.1.18 (2024-12-31) =================== This release includes a few minor enhancements. Namely, the ability to iterate over time zone transitions (in the future or the past), and some improvements to failure modes when `Timestamp` and `Span` arithmetic fails. Enhancements: * [#144](https://github.com/BurntSushi/jiff/issues/144): Add APIs for iterating over the transitions of a time zone. * [#145](https://github.com/BurntSushi/jiff/issues/145): Improve docs and error messages around fallible `Timestamp` arithmetic. 0.1.17 (2024-12-31) =================== This release enhances Jiff's support for `no_std` environments by making its `alloc` feature optional. When `alloc` is disabled, only fixed offset time zones are supported and error messages are significantly degraded. If you have core-only use cases for Jiff, I'd love to hear about them on the issue tracker. Enhancements: * [#162](https://github.com/BurntSushi/jiff/issues/162): Support platforms that do not have atomics in `std`. * [#168](https://github.com/BurntSushi/jiff/issues/168): Jiff now supports disabling the `alloc` feature, which enables core-only mode. * [#169](https://github.com/BurntSushi/jiff/issues/169): Add `TimeZone::to_fixed_offset` for accessing an invariant offset if possible. 0.1.16 (2024-12-26) =================== This release includes a new `jiff::fmt::friendly` module for formatting and parsing durations in a more human readable format than what ISO 8601 specifies. ISO 8601 remains the "default" duration format in Jiff due to its widespread support. Here are some examples: ```text 40d 40 days 1y1d 1yr 1d 3d4h59m 3 days, 4 hours, 59 minutes 3d 4h 59m 2h30m 2h 30m 1mo 1w 1 week 1w4d 1 wk 4 days 1m 0.0021s 0s 0d 0 days 3 mins 34s 123ms 3 mins 34.123 secs 3 mins 34,123s 1y1mo1d1h1m1.1s 1yr 1mo 1day 1hr 1min 1.1sec 1 year, 1 month, 1 day, 1 hour, 1 minute 1.1 seconds 1 year, 1 month, 1 day, 01:01:01.1 ``` To quickly demonstrate this new feature, here's a simple CLI program using Clap: ```rust,no_run use clap::Parser; use jiff::{Span, Zoned}; #[derive(Parser, Debug)] #[command(version, about, long_about = None)] struct Args { duration: Span, } fn main() { let args = Args::parse(); println!("adding duration to now: {}", &Zoned::now() + args.duration); } ``` And running the program: ```text $ cargo run -q -- '1 year, 2 months, 5 hours' adding duration to now: 2026-02-26T18:58:22-05:00[America/New_York] $ cargo run -q -- 'P1Y2MT5H' # ISO 8601 durations are supported too! adding duration to now: 2026-02-26T19:00:57-05:00[America/New_York] ``` With Jiff, you should no longer need to pull in crates like [`humantime`](https://docs.rs/humantime) and [`humantime-serde`](https://docs.rs/humantime-serde) to accomplish a similar task. While this new format doesn't support any kind of internationalization, the prevalence of the `humantime` crate suggests there's a desire for something like this. The "friendly" format is meant to service all the same use cases as `humantime` does for durations, but in a way that doesn't let you shoot yourself in the foot. The new "friendly" format is now the default for the `Debug` implementations of both `Span` and `SignedDuration`. It's also available via the "alternate" `Display` implementations for `Span` and `SignedDuration` as well. Moreover, the `FromStr` trait implementations for both `Span` and `SignedDuration` will parse _both_ the ISO 8601 duration and this new "friendly" format. Finally, when `serde` integration is enabled, the `Deserialize` implementations for `SignedDuration` and `Span` also automatically parse either ISO 8601 or the friendly format. For serialization, ISO 8601 remains the default, but the `jiff::fmt::serde` module provides easy to use helpers to switch to the friendly format. The `jiff::fmt::friendly` module documentation provides many more details, including a complete grammar for the format. Enhancements: * [#143](https://github.com/BurntSushi/jiff/pull/143): Add `Hash` implementation for `Zoned` and `Timestamp`. Bug fixes: * [#60](https://github.com/BurntSushi/jiff/issues/60): Use a better `Debug` output format for `SignedDuration` and `Span`. * [#111](https://github.com/BurntSushi/jiff/issues/111): Add a new "friendly" duration format. * [#138](https://github.com/BurntSushi/jiff/issues/138): Fix deserialization in `serde_yml` and `serde_yaml` crates. * [#161](https://github.com/BurntSushi/jiff/pull/161): Fix `serde` dependency configuration so that it builds in no-std mode. 0.1.15 (2024-11-30) =================== This release fixes a bug where Jiff would sometimes fail to parse TZif files (found, typically, in `/usr/share/zoneinfo` on Unix systems). This occurred when the TZif file contained a time zone transition outside the range of Jiff's `Timestamp` type (which is `-9999-01-01` to `9999-12-31`). The bug fix works by clamping the out-of-range transitions to Jiff's supported range. This bug only seems to occur in some environments where their TZif files contain more extreme values than what is typically found. Bug fixes: * [#163](https://github.com/BurntSushi/jiff/issues/163): Fix a bug where Jiff would fail to parse some TZif files. 0.1.14 (2024-11-01) =================== This release introduces new APIs to the RFC 2822 printer that explicitly print timestamps in a format strictly compatible with [RFC 9110]. Enhancements: * [#151](https://github.com/BurntSushi/jiff/issues/151): Add `rfc2822::DateTimePrinter::timestamp_to_rfc9110_string` method. 0.1.13 (2024-09-07) =================== This release introduces a new `jiff::tz::TimeZone::try_system` API. It is like `TimeZone::system`, but returns an error instead of an automatic fall back to UTC when the system time zone could not be discovered. This also includes an update to the bundled [IANA Time Zone Database] to the `2024b` release in the `jiff-tzdb` crate. As a reminder, the bundled database is not used or included on Unix platforms by default. See [PLATFORM] for more details. Enhancements: * [#65](https://github.com/BurntSushi/jiff/issues/65): Add `TimeZone::try_system` for fallibly determining the system's time zone. * [#125](https://github.com/BurntSushi/jiff/pull/125): Update to the `2024b` release of the [IANA Time Zone Database]. 0.1.12 (2024-08-31) =================== This release introduces some new minor APIs that support formatting `Timestamp` values as RFC 3339 strings with a specific offset. Previously, using the standard formatting routines that Jiff provides, it was only possible to format a `Timestamp` using Zulu time. For example: ```rust use jiff::Timestamp; assert_eq!( Timestamp::UNIX_EPOCH.to_string(), "1970-01-01T00:00:00Z", ); ``` This is fine most use cases, but it can be useful on occasion to format a `Timestamp` with a specific offset. While this isn't as expressive as formatting a datetime with a time zone (e.g., with an IANA time zone identifier), it may be useful in contexts where you just want to "hint" at what a user's local time is. To that end, there is a new `Timestamp::display_with_offset` method that makes this possible: ```rust use jiff::{tz, Timestamp}; assert_eq!( Timestamp::UNIX_EPOCH.display_with_offset(tz::offset(-5)).to_string(), "1969-12-31T19:00:00-05:00", ); ``` A corresponding API was added to `jiff::fmt::temporal::DateTimePrinter` for lower level use. Moreover, this release also includes new convenience APIs on the Temporal and RFC 2822 printer types for returning strings. For example, previously, if you were using the RFC 2822 printer to format a `Timestamp`, you had to do this: ```rust use jiff::{fmt::rfc2822::DateTimePrinter, Timestamp}; let mut buf = String::new(); DateTimePrinter::new().print_timestamp(&Timestamp::UNIX_EPOCH, &mut buf).unwrap(); assert_eq!(buf, "Thu, 1 Jan 1970 00:00:00 -0000"); ``` But now you can just do this: ```rust use jiff::{fmt::rfc2822::DateTimePrinter, Timestamp}; assert_eq!( DateTimePrinter::new().timestamp_to_string(&Timestamp::UNIX_EPOCH).unwrap(), "Thu, 1 Jan 1970 00:00:00 -0000", ); ``` Enhancements: * [#122](https://github.com/BurntSushi/jiff/pull/122): Support formatting `Timestamp` to an RFC 3339 string with a specific offset. 0.1.11 (2024-08-28) =================== This release includes a few small enhancements that have been requested over the last several weeks. The biggest enhancement is a new `jiff::fmt::serde` sub-module. It provides convenience routines similar to Chrono's `chrono::serde` sub-module for serializing and deserializing between a `jiff::Timestamp` and an integer number of seconds, milliseconds, microseconds or nanoseconds. For example: ```rust use jiff::Timestamp; #[derive(serde::Serialize, serde::Deserialize)] struct Record { #[serde(with = "jiff::fmt::serde::timestamp::second::required")] timestamp: Timestamp, } let json = r#"{"timestamp":1517644800}"#; let got: Record = serde_json::from_str(&json).unwrap(); assert_eq!(got.timestamp, Timestamp::from_second(1517644800).unwrap()); assert_eq!(serde_json::to_string(&got).unwrap(), json); ``` If you need to support optional timestamps via `Option`, then use `jiff::fmt::serde::timestamp::second::optional` instead. Similarly, if you need to support milliseconds instead of seconds, then replace `second` with `millisecond` in the module path. Enhancements: * [#78](https://github.com/BurntSushi/jiff/issues/78): Add `BrokenDownTime::set_{offset,iana_time_zone}` APIs. * [#93](https://github.com/BurntSushi/jiff/issues/93): Add note about using `Timestamp::now().to_zoned()` instead of `Zoned::now().with_time_zone()`. * [#101](https://github.com/BurntSushi/jiff/issues/101): Add new `jiff::fmt::serde` module for integration with integer timestamps. * [#117](https://github.com/BurntSushi/jiff/pull/117): Remove `unsafe` usage in `libm` functions (applicable only to no-std users). 0.1.10 (2024-08-23) =================== This release features a small bug fix where Jiff will detect an IANA time zone identifier in some cases where it wouldn't before. While Jiff would previously read the symlink metadata on `/etc/localtime` by default to discover the system configured time zone on Unix systems, it *wouldn't* do so when `TZ=/etc/localtime`. There's really no reason not to, so this release of Jiff is fixed to use symlink sniffing on file paths provided by thw `TZ` environment variable. Bug fixes: * [#113](https://github.com/BurntSushi/jiff/issues/113): When `TZ=/etc/localtime`, use symlink metadata to detect IANA identifier. 0.1.9 (2024-08-23) ================== This release introduces new options for controlling the precision of fractional seconds when printing `Zoned`, `Timestamp`, `civil::DateTime` or `civil::Time` values. This is principally exposed via `jiff::fmt::temporal::DateTimePrinter::precision`, but it's also available via the standard library's formatting machinery. For example, if `zdt` is a `jiff::Zoned`, then `format!("{zdt:.6}")` will format it into a string with microsecond precision, even if its fractional component is zero. Enhancements: * [#92](https://github.com/BurntSushi/jiff/issues/92): Support setting the precision of fractional seconds when printing datetimes. 0.1.8 (2024-08-19) ================== This releases fixes a build error in Jiff's `alloc`-only configuration. This regression was introduced in `jiff 0.1.6`. Bug fixes: * [#108](https://github.com/BurntSushi/jiff/issues/108): Use `core::time::Duration` everywhere instead of `std::time::Duration`. 0.1.7 (2024-08-18) ================== This release relaxes Jiff's dependency on `windows-sys` to include multiple semver incompatible releases. The purpose of this relaxation is to enable Jiff to work with different versions of `windows-sys` in the hopes that this reduces the likelihood that multiple copies of `windows-sys` are included in your dependency tree. Dependencies: * [#106](https://github.com/BurntSushi/jiff/pull/106): Relax `windows-sys` dependency constraint to `>=0.52.0, <=0.59.*`. 0.1.6 (2024-08-18) ================== This release includes a new top-level type, `SignedDuration`, that provides a near exact replica of `std::time::Duration`, but signed. It is meant to provide alternative APIs for working with durations at a lower level than what `Span` provides, and to facilitate better integration with the standard library. A `SignedDuration` has also been integrated with all of Jiff's datetime types. For example, previously, `Zoned::checked_add` only accepted a concrete `jiff::Span`. But now it accepts a `jiff::Span`, `jiff::SignedDuration` or even a `std::time::Duration`. Moreover, all of the `until` and `since` APIs on datetime types have been ported and copied to return `SignedDuration` under the `duration_until` and `duration_since` names. This marks an initial integration phase with `SignedDuration`. It is planned to integrate it more with the datetime types. Currently, there are integrations on `Timestamp` and `Span`, but more will be added in the future. Overall, folks should still use `Span`. That is the intended default duration type in Jiff and will continue to be. Users of Jiff may find `SignedDuration` useful in contexts where speed is important or when one needs to integrate with the standard library. This release also includes a few related deprecations as the APIs involving `std::time::Duration` are phased out in favor of `SignedDuration`. Deprecations: * `Timestamp::as_duration`: replaced with `as_jiff_duration`, which will be renamed to `as_duration` in `jiff 0.2`. * `Timestamp::from_duration`: replaced with `from_jiff_duration`, which will be renamed to `from_duration` in `jiff 0.2`. * `Timestamp::from_signed_duration`: replaced with `from_jiff_duration`. * `Span::to_duration`: replaced with `to_jiff_duration`, which will be renamed to `to_duration` in `jiff 0.2`. Basically, all of the above APIs either accept or return a `std::time::Duration`. To avoid breaking chnages at this point, new methods for `SignedDuration` were added. For example, `Timestamp::as_jiff_duration`. In `jiff 0.2`, the above deprecated methods will be removed and replaced with equivalent methods that accept or return a `SignedDuration` instead. Callers can then convert between a `SignedDuration` and a `std::time::Duration` using appropriate `TryFrom` trait implementations. Enhancements: * [#21](https://github.com/BurntSushi/jiff/issues/21): Add new top-level `SignedDuration` type. * [#90](https://github.com/BurntSushi/jiff/issues/90): Improve error message when using `Span` with >=day units with a `Timestamp`. Performance: * [#104](https://github.com/BurntSushi/jiff/pull/104) Optimize offset calculations in time zones without DST. * [#105](https://github.com/BurntSushi/jiff/pull/105) Optimize offset calculations for timestamps after last DST rule change. 0.1.5 (2024-08-09) ================== This release includes some improvements and bug fixes, particularly for Jiff's `strtime` APIs. Enhancements: * [#63](https://github.com/BurntSushi/jiff/issues/63): Add link to original Chrono maintainer's commentary in `DESIGN.md`. * [#75](https://github.com/BurntSushi/jiff/issues/75): Add support for `%V` for formatting _and_ parsing IANA time zone identifiers. * [#79](https://github.com/BurntSushi/jiff/pull/79): Add `devcontainer.json` to support GitHub Codespaces. * [#85](https://github.com/BurntSushi/jiff/pull/85): Set correct ranges for internal tracking in return value of `days_in_month`. Bug fixes: * [#59](https://github.com/BurntSushi/jiff/issues/59): Fixes a bug where some `Span`s could not be roundtripped through ISO 8601. * [#71](https://github.com/BurntSushi/jiff/issues/71): Tweak wording in documentation of "printf"-style API. * [#73](https://github.com/BurntSushi/jiff/issues/73): Make it so `%.Nf` only formats to `N` decimal places. * [#77](https://github.com/BurntSushi/jiff/pull/77): Disable optimizations when running tests. 0.1.4 (2024-08-01) ================== This release includes a small improvement for `strptime` that permits `%Y%m%d` to parse `20240730` correctly. Enhancements: * [#62](https://github.com/BurntSushi/jiff/issues/62): Tweak `strptime` so that things like `%Y` aren't unceremoniously greedy. 0.1.3 (2024-07-30) ================== This release features support for `wasm32-unknown-unknown`. That is, when Jiff's new `js` crate feature is enabled, Jiff will automatically use JavaScript APIs to determine the current time and time zone. Enhancements: * [#58](https://github.com/BurntSushi/jiff/pull/58): Add WASM support and a new `PLATFORM.md` guide. 0.1.2 (2024-07-28) ================== This release features a few new APIs that a need for arose while experimenting with actually using Jiff in real projects. Namely, the `jiff::fmt::strtime` module now has `%f` and `%.f` directives for parsing and formatting fractional seconds. And both `jiff::fmt::rfc2822` and `jiff::fmt::strtime` now have support for skipping weekday checks during parsing. (Previously, Jiff required that an English weekday be consistent with the date parsed, and there was no way to opt out. While this is still the default behavior, callers can disable this check.) Enhancements: * [#52](https://github.com/BurntSushi/jiff/pull/52): Improve documentation for `Span` getter methods. * [#53](https://github.com/BurntSushi/jiff/pull/53): Add support for skipping weekday checking when parsing datetimes. * [#55](https://github.com/BurntSushi/jiff/pull/55): Add support for fractional seconds in `jiff::fmt::strtime`. Bug fixes: * [#49](https://github.com/BurntSushi/jiff/pull/49): Fix informational regex describing ISO 8601 format. * [#51](https://github.com/BurntSushi/jiff/pull/51): Explicitly allow new deny-by-default lint `ambiguous_negative_literals`. 0.1.1 (2024-07-25) ================== This is a new semver compatible release. The principle addition are APIs for converting between a `jiff::Span` and a `std::time::Duration`. Specifically, there are now `TryFrom for Duration` and `TryFrom for Span` trait implementations. There is also a `Span::to_duration`, which requires a relative date, for converting spans with non-uniform units (like months) to a `Duration`. Enhancements: * [#21](https://github.com/BurntSushi/jiff/issues/21), [#40](https://github.com/BurntSushi/jiff/issues/40): Adds APIs for converting between `Span` and `std::time::Duration`. Bug fixes: * [#36](https://github.com/BurntSushi/jiff/issues/36): Saturating arithmetic for `Timestamp` panics with day-or-greater units. * [#38](https://github.com/BurntSushi/jiff/issues/38): Fix some bugs in the micro-benchmarks. * [#39](https://github.com/BurntSushi/jiff/issues/39): Document that the RFC 2822 parser is not technically fully spec compliant. 0.1.0 (2024-07-21) ================== The initial release of Jiff. [IANA Time Zone Database]: https://www.iana.org/time-zones [PLATFORM]: PLATFORM.md [RFC 9110]: https://datatracker.ietf.org/doc/html/rfc9110#section-5.6.7-15 jiff-0.1.28/COMPARE.md000064400000000000000000001255711046102023000122550ustar 00000000000000# Comparison with other Rust datetime crates This document is meant to be a comparison between Jiff and each of the other prominent open source datetime libraries for Rust. If you feel like there is a library missing from this list, please file an issue about it. I would prefer to only add libraries to this list that are being used in production or have a substantial number of users. The goal of this document is to be as _descriptive_ and _substantively complete_ as possible. For example, "Chrono has a better API design than Jiff" would be a pretty vague value judgment that someone could easily disagree with. But, "Chrono allows using a zone-aware datetime type that is `Copy` while Jiff does not" would be a factual comparison that someone might use to _support_ an opinion that Chrono's API design is better than Jiff's. In other words, this document should provide the "facts of comparison" but refrain from assigning value judgments. In terms of completeness, it is probably not realistic to expect 100% completion here. We aren't hunting for Korok Seeds. Instead, this document aims for _substantive_ completion. That is, if there's a point of difference between Jiff and another library that would likely influence someone's decision of which library to use, and can be articulated descriptively, then it should probably be in this document. The current status of this document is that it is both _incomplete_ and _biased_. That is, this first draft was written by the author of Jiff without any input from other crate maintainers. (To other crate maintainers: I welcome feedback. Even if it's just filing an issue.) Note that this document contains many code snippets. They can be tested with `cargo test --doc _documentation::comparison` from the root of this repository. ## [`chrono`](https://docs.rs/chrono) (v0.4.38) Chrono is a Rust datetime library that provides a time zone aware datetime type. For the following comparisons, a `Cargo.toml` with the following dependencies should be able to run any of the programs in this section: ```toml anyhow = "1.0.81" chrono = "0.4.38" chrono-tz = { version = "0.9.0", features = ["serde"] } jiff = { version = "0.1.0", features = ["serde"] } serde = "1.0.203" serde_json = "1.0.117" tzfile = "0.1.3" ``` ### Time zone database integration Jiff gives you automatic integration with your copy of the Time Zone Database. On Unix, it's usually found at `/usr/share/zoneinfo`. On Windows, since there is no canonical location, Jiff will depend on `jiff-tzdb` by default, which will embed the entire database into your binary. Jiff hides these details from you. For example, to convert a civil time into an absolute time in a particular time zone: ```rust use jiff::civil::date; fn main() -> anyhow::Result<()> { let zdt = date(2024, 6, 30).at(9, 46, 0, 0).in_tz("America/New_York")?; assert_eq!(zdt.to_string(), "2024-06-30T09:46:00-04:00[America/New_York]"); Ok(()) } ``` For Chrono, one recommended option is to use the [`chrono-tz`](https://docs.rs/chrono-tz) crate: ```rust use anyhow::Context; use chrono::TimeZone; use chrono_tz::America::New_York; fn main() -> anyhow::Result<()> { let zdt = New_York.with_ymd_and_hms(2024, 6, 30, 9, 46, 0) .single() .context("invalid naive time")?; assert_eq!(zdt.to_string(), "2024-06-30 09:46:00 EDT"); Ok(()) } ``` `chrono-tz` works by embedding an entire copy of the Time Zone Database into your binary, where each time zone is represented as a Rust value that can be imported via `use`. A disadvantage of this approach is that you're reliant on `chrono-tz` updates to get the most recent time zone information. An advantage of this approach is that you never need to worry about an end user's system state. Another advantage is that this allows a `TimeZone` trait implementation to be `Copy` via a `&Tz`, and that in turn allows a `chrono::DateTime` to be `Copy`. In contrast, in Jiff, a `TimeZone` is never `Copy`. Since a `Zoned` embeds a `TimeZone`, a `Zoned` is never `Copy` either. Another recommended option is the [`tzfile`](https://docs.rs/tzfile) crate. Unlike `chrono-tz`, the `tzfile` crate will try to read time zone data from your system's copy of the Time Zone Database. ```rust use anyhow::Context; use chrono::{NaiveDate, NaiveDateTime, NaiveTime, TimeZone}; use tzfile::Tz; #[cfg(unix)] fn main() -> anyhow::Result<()> { let tz = Tz::named("America/New_York")?; let zdt = (&tz).with_ymd_and_hms(2024, 6, 30, 9, 46, 0) .single() .context("invalid naive time")?; assert_eq!(zdt.to_string(), "2024-06-30 09:46:00 EDT"); Ok(()) } // `tzfile` exposes a platform specific API, which means // users of the crate have to deal with platform differences // themselves. #[cfg(not(unix))] fn main() -> anyhow::Result<()> { Ok(()) } ``` Note though that at time of writing (2024-07-11), `tzfile::Tz::named` will read and parse the corresponding time zone rules from disk on every call. Conversely, in Jiff, all time zone lookups by name are cached. This may or may not matter for your use case. ### Jiff losslessly roundtrips time zone aware datetimes In Jiff, with `serde` support enabled, one can serialize and deserialize a `Zoned` value losslessly. This means that, after deserialization, you can expect it to still perform DST arithmetic: ```rust use jiff::{civil::date, ToSpan, Zoned}; fn main() -> anyhow::Result<()> { let zdt = date(2024, 3, 10).at(1, 59, 59, 0).in_tz("America/New_York")?; let json = serde_json::to_string_pretty(&zdt)?; assert_eq!(json, "\"2024-03-10T01:59:59-05:00[America/New_York]\""); let got: Zoned = serde_json::from_str(&json)?; assert_eq!(got.to_string(), "2024-03-10T01:59:59-05:00[America/New_York]"); let next = got.checked_add(1.minute())?; assert_eq!(next.to_string(), "2024-03-10T03:00:59-04:00[America/New_York]"); Ok(()) } ``` Notice that when we add a minute, it jumps to `03:00` civil time because of the transition into daylight saving time in my selected time zone. Notice also the offset change from `-05` to `-04`. Compare this with Chrono which also supports `serde`, but not with `chrono-tz` or `tzfile`. One option is to use its `Local` implementation of its `TimeZone` trait: ```rust,no_run use anyhow::Context; use chrono::{DateTime, FixedOffset, Local, TimeDelta, TimeZone}; fn main() -> anyhow::Result<()> { let zdt = Local.with_ymd_and_hms(2024, 3, 10, 1, 59, 59) .single() .context("invalid naive time")?; let json = serde_json::to_string_pretty(&zdt)?; // Chrono only serializes the offset, which makes lossless // deserialization impossible. Chrono loses the time zone // information. assert_eq!(json, "\"2024-03-10T01:59:59-05:00\""); // The serialized datetime has no time zone information, // so unless there is some out-of-band information saying // what its time zone is, we're forced to use a fixed offset: let got: DateTime = serde_json::from_str(&json)?; assert_eq!(got.to_string(), "2024-03-10 01:59:59 -05:00"); let next = got.checked_add_signed(TimeDelta::minutes(1)) .context("arithmetic failed")?; // This is correct for fixed offset, but it's no longer // DST aware. assert_eq!(next.to_string(), "2024-03-10 02:00:59 -05:00"); // We could deserialize into a `DateTime`, but this // requires knowing that the time zone of the datetime matches // local time zone. Which you might know. But you might not. let got: DateTime = serde_json::from_str(&json)?; assert_eq!(got.to_string(), "2024-03-10 01:59:59 -05:00"); let next = got.checked_add_signed(TimeDelta::minutes(1)) .context("arithmetic failed")?; assert_eq!(next.to_string(), "2024-03-10 03:00:59 -04:00"); Ok(()) } ``` Or, if you have a `Tz` from `chrono-tz`. But in this case, since `chrono-tz` doesn't support Serde, you have to convert to a `DateTime`. Like above, you'll lose DST safe arithmetic after deserialization: ```rust use anyhow::Context; use chrono::{DateTime, FixedOffset, TimeDelta, TimeZone}; use chrono_tz::America::New_York; fn main() -> anyhow::Result<()> { let zdt = New_York.with_ymd_and_hms(2024, 3, 10, 1, 59, 59) .single() .context("invalid naive time")?; let json = serde_json::to_string_pretty(&zdt.fixed_offset())?; // Chrono only serializes the offset, which makes lossless // deserialization impossible. Chrono loses the time zone // information. assert_eq!(json, "\"2024-03-10T01:59:59-05:00\""); // The serialized datetime has no time zone information, // so unless there is some out-of-band information saying // what its time zone is, we're forced to use a fixed offset: let got: DateTime = serde_json::from_str(&json)?; assert_eq!(got.to_string(), "2024-03-10 01:59:59 -05:00"); let next = got.checked_add_signed(TimeDelta::minutes(1)) .context("arithmetic failed")?; // This is correct for fixed offset, but it's no longer // DST aware. assert_eq!(next.to_string(), "2024-03-10 02:00:59 -05:00"); Ok(()) } ``` The main way to solve this problem (and is how `java.time`, Temporal and Jiff solve it), is by supporting [RFC 9557]. Otherwise, the only way to fully capture Jiff's functionality in Chrono is to define a custom serialization format that includes the instant, the time zone identifier *and* the offset. (The offset is used for conflict resolution when deserializing datetimes made in the future for which their offset has changed due to changes in the time zone database.) [RFC 9557]: https://datatracker.ietf.org/doc/rfc9557/ ### Jiff provides support for zone aware calendar arithmetic With Jiff, you can add non-uniform units like days to time zone aware datetimes, and get non-uniform units like days as a representation of a span between datetimes. And they agree on the results. ```rust use jiff::{civil::date, ToSpan, Unit}; fn main() -> anyhow::Result<()> { let zdt1 = date(2024, 3, 9).at(21, 0, 0, 0).in_tz("America/New_York")?; let zdt2 = zdt1.checked_add(1.day())?; // Even though 2 o'clock didn't occur on 2024-03-10, adding 1 day // returns the same civil time the next day. assert_eq!(zdt2.to_string(), "2024-03-10T21:00:00-04:00[America/New_York]"); // The span of time is 23 hours: assert_eq!(&zdt2 - &zdt1, 23.hours().fieldwise()); // But if you ask for the span in units of days, you get exactly 1: assert_eq!(zdt1.until((Unit::Day, &zdt2))?, 1.day().fieldwise()); Ok(()) } ``` This is important and difficult to get right because some days are only 23 hours long (typically the day of the year where DST starts) and some days are 25 hours long (typically the day of the year where DST ends). With Jiff, you can seamlessly go back-and-forth between calendar units and clock units without worrying about whether "day" will be interpreted differently. Chrono has some support for this. Namely, it can add units of days in a time zone aware fashion, but it cannot produce spans of time involving days between two zone aware datetimes that is consistent with adding days. ```rust use anyhow::Context; use chrono::{Days, TimeDelta, TimeZone}; use chrono_tz::America::New_York; fn main() -> anyhow::Result<()> { let zdt1 = New_York.with_ymd_and_hms(2024, 3, 9, 21, 0, 0) .single() .context("invalid naive time")?; // Adding 1 day via TimeDelta leads to a result that is // 24 hours later, including the gap at 2am on 2024-03-10. // As a result, you get a different civil time, which is // usually not what is intended. let zdt2 = zdt1.checked_add_signed(TimeDelta::days(1)) .context("adding a time delta failed")?; assert_eq!(zdt2.to_string(), "2024-03-10 22:00:00 EDT"); // However, Chrono does expose a separate API for adding // units of days specifically. This does get you the // correct result. let zdt2 = zdt1.checked_add_days(Days::new(1)) .context("adding days failed")?; assert_eq!(zdt2.to_string(), "2024-03-10 21:00:00 EDT"); // The only way to compute a duration between two datetimes // in Chrono is with a `TimeDelta`: let delta = zdt2.signed_duration_since(&zdt1); // And since `TimeDelta` assumes all days are exactly 24 // hours long, you get a result of `0` days. If this were // a fold, the number of days would be `1`, but you'd also // have a number of hours equal to `1`. assert_eq!(delta.num_days(), 0); Ok(()) } ``` ### Jiff losslessly roundtrips durations Jiff implements something close to ISO 8601 to provide lossless serialization and deserialization of its `Span` type. A `Span` covers both calendar and clock units. ```rust use jiff::{Span, ToSpan}; fn main() -> anyhow::Result<()> { let span = 5.years().months(2).days(1).hours(20); let json = serde_json::to_string_pretty(&span)?; assert_eq!(json, "\"P5Y2M1DT20H\""); let got: Span = serde_json::from_str(&json)?; assert_eq!(got, span.fieldwise()); Ok(()) } ``` Chrono [does not currently have Serde support for its duration type][serde-duration]. [serde-duration]: https://github.com/chronotope/chrono/issues/117 ### Jiff supports dealing with gaps in civil time A gap in civil time most typically occurs when a particular region enters daylight saving time. When this happens, some time on the clocks in that region is skipped. It never appears. (A fold happens when the clocks are rolled back, usually when leaving daylight saving time. In this case, some time on the clock is repeated.) Jiff supports automatically selecting a "reasonable" choice in either case via its "compatible" strategy (as specified by [RFC 5545]). ```rust use jiff::Zoned; fn main() -> anyhow::Result<()> { // This is a gap. The default strategy takes the time after the gap. let zdt: Zoned = "2024-03-10 02:30[America/New_York]".parse()?; assert_eq!(zdt.to_string(), "2024-03-10T03:30:00-04:00[America/New_York]"); // This is a fold. The default strategy takes the time before the fold. let zdt: Zoned = "2024-11-03 01:30[America/New_York]".parse()?; // The time after the fold would be identical, // except the offset would be -05. assert_eq!(zdt.to_string(), "2024-11-03T01:30:00-04:00[America/New_York]"); Ok(()) } ``` Jiff also exposes all information available with respect to ambiguous civil datetimes via `tz::AmbiguousZoned`, `tz::AmbiguousTimestamp` and `tz::AmbiguousOffset`. This enables callers to implement whatever strategy they want. While Chrono will let you deal with folds, it returns `MappedLocalTime::None` in the case of a gap with no additional information. So there's really nothing else you can conveniently do in this case except return an error: ```rust use anyhow::Context; use chrono::{offset::MappedLocalTime, TimeZone}; use chrono_tz::America::New_York; fn main() -> anyhow::Result<()> { // For gaps, Chrono exposes no additional information. let mapped = New_York.with_ymd_and_hms(2024, 3, 10, 2, 30, 0); assert_eq!(mapped, MappedLocalTime::None); // For folds, Chrono gives you the two choices. // This is approximately equivalent to what Jiff exposes // in the case of a fold. let zdt = New_York.with_ymd_and_hms(2024, 11, 3, 1, 30, 0) .earliest() .context("invalid datetime")?; assert_eq!(zdt.to_string(), "2024-11-03 01:30:00 EDT"); Ok(()) } ``` [RFC 5545]: https://datatracker.ietf.org/doc/html/rfc5545 ### Jiff supports rounding durations In Jiff, one can round the duration computed between two datetimes: ```rust use jiff::{civil::date, RoundMode, ToSpan, Unit, ZonedDifference}; fn main() -> anyhow::Result<()> { let zdt1 = date(2001, 11, 18).at(8, 30, 0, 0).in_tz("America/New_York")?; let zdt2 = date(2024, 7, 11).at(22, 38, 0, 0).in_tz("America/New_York")?; let round_options = ZonedDifference::new(&zdt2) .largest(Unit::Year) .smallest(Unit::Day) .mode(RoundMode::HalfExpand); let span = zdt1.until(round_options)?; assert_eq!(span.fieldwise(), 22.years().months(7).days(24)); Ok(()) } ``` While Chrono supports rounding datetimes themselves via its `chrono::duration::DurationRound` trait, it does not support rounding durations themselves. Indeed, its principle duration type, `TimeDelta`, is an "absolute" duration like `std::time::Duration` (except that it is signed). It doesn't keep track of individual units like Jiff does. Instead, everything gets normalized into a 96-bit integer number of nanoseconds. With this representation, it is impossible to do DST safe rounding to non-uniform units like days. ### Jiff supports zone-aware rounding of durations Jiff's duration rounding is time zone aware. For example, if you're rounding to a number of days, it knows to round 11.5 hours up to 1 day on days with gaps, and round 12 hours down to 0 days on days with folds. The only requirement is that we provide a reference datetime with which to interpret the span. ```rust use jiff::{civil::date, SpanRound, ToSpan, Unit}; fn main() -> anyhow::Result<()> { let gapday = date(2024, 3, 10).in_tz("America/New_York")?; let foldday = date(2024, 11, 3).in_tz("America/New_York")?; let span1 = 11.hours().minutes(30); let span2 = span1.round( SpanRound::new().smallest(Unit::Day).relative(&gapday), )?; // rounds up, even though on a normal day 11.5 hours would round down. assert_eq!(span2, 1.day().fieldwise()); let span1 = 12.hours(); let span2 = span1.round( SpanRound::new().smallest(Unit::Day).relative(&foldday), )?; // rounds down, even though on a normal day 12 hours would round up. assert_eq!(span2, 0.days().fieldwise()); Ok(()) } ``` As with the previous section, Chrono does not support rounding durations or rounding units like `Days` with respect to a reference datetime. ### Jiff supports re-balancing durations This example is like the one above, except we choose a smaller "largest" unit: ```rust use jiff::{civil::date, RoundMode, ToSpan, Unit, ZonedDifference}; fn main() -> anyhow::Result<()> { let zdt1 = date(2001, 11, 18).at(8, 30, 0, 0).in_tz("America/New_York")?; let zdt2 = date(2024, 7, 11).at(22, 38, 0, 0).in_tz("America/New_York")?; let round_options = ZonedDifference::new(&zdt2) .largest(Unit::Month) .smallest(Unit::Day) .mode(RoundMode::HalfExpand); let span = zdt1.until(round_options)?; assert_eq!(span, 271.months().days(24).fieldwise()); Ok(()) } ``` ### Jiff supports getting the `nth` weekday from the current date ```rust use jiff::civil::{date, Weekday}; fn main() -> anyhow::Result<()> { let zdt = date(2024, 7, 11).at(22, 59, 0, 0).in_tz("America/New_York")?; assert_eq!(zdt.weekday(), Weekday::Thursday); let next_tuesday = zdt.nth_weekday(1, Weekday::Tuesday)?; assert_eq!( next_tuesday.to_string(), "2024-07-16T22:59:00-04:00[America/New_York]", ); Ok(()) } ``` Chrono does have `NaiveDate::from_weekday_of_month_opt`, but it only counts the number of weekdays for a particular month. (The Jiff equivalent is `nth_weekday_of_month`.) Moreover, Chrono's method is only available on naive dates and not zone aware datetimes. ### Jiff supports detecting time zone offset conflicts One of the problems with storing datetimes in the future is that time zone rules can change. For example, if you stored the zone aware datetime `2020-01-15T12:00-02[America/Sao_Paulo]` in 2018, then it would be considered to be in daylight saving time with an offset of `-2`. However, in 2019, daylight saving time was abolished in this time zone, which renders the datetime invalid because its offset *should* be `-3`. Jiff can detect these sorts of conflicts and will actually return a parse error by default. We exemplify this by creating and serializing a zoned datetime from an old copy of the Time Zone Database, and then try to parse it back using our system's current copy of the Time Zone Database. (This also demonstrate's Jiff support for using multiple copies of the Time Zone Database simultaneously. But the main point here is to simulate the process of "serialize datetime, time zone rules change, deserialize datetime.") ```rust,no_run use jiff::{fmt::temporal::DateTimeParser, tz::{self, TimeZoneDatabase}}; // We use a custom parser with a default configuration because we need // to ask the parser to use a different time zone database than the // default. This can't be done via the nice `"...".parse()` API one // would typically use. static PARSER: DateTimeParser = DateTimeParser::new(); fn main() -> anyhow::Result<()> { // Open a version of tzdb from before Brazil announced its abolition // of daylight saving time. let tzdb2018 = TimeZoneDatabase::from_dir("path/to/tzdb-2018b")?; // Open the system tzdb. let tzdb = tz::db(); // Parse the same datetime string with the same parser, but using two // different versions of tzdb. let dt = "2020-01-15T12:00[America/Sao_Paulo]"; let zdt2018 = PARSER.parse_zoned_with(&tzdb2018, dt)?; let zdt = PARSER.parse_zoned_with(tzdb, dt)?; // Before DST was abolished, 2020-01-15 was in DST, which corresponded // to UTC offset -02. Since DST rules applied to datetimes in the // future, the 2018 version of tzdb would lead one to interpret // 2020-01-15 as being in DST. assert_eq!(zdt2018.offset(), tz::offset(-2)); // But DST was abolished in 2019, which means that 2020-01-15 was no // no longer in DST. So after a tzdb update, the same datetime as above // now has a different offset. assert_eq!(zdt.offset(), tz::offset(-3)); // So if you try to parse a datetime serialized from an older copy of // tzdb with a new copy of tzdb, you'll get an error under the default // configuration because of `OffsetConflict::Reject`. This would succeed if // you parsed it using tzdb2018! assert!(PARSER.parse_zoned_with(tzdb, zdt2018.to_string()).is_err()); Ok(()) } ``` With Chrono, this sort of checking isn't possible in the first place because it doesn't support an interchange format that includes the IANA time zone identifier. ### Jiff supports adding durations with calendar units Since `Span` is Jiff's single duration type that combines calendar and clock units, one can freely add them together. The only requirement is that if a span has calendar units, you need to provide a reference date. (Because 1 month from April 1 is shorter than 1 month from May 1.) ```rust use jiff::{civil::date, ToSpan}; fn main() -> anyhow::Result<()> { let span1 = 2.years().months(4).days(25).hours(23); let span2 = 3.hours(); let span3 = span1.checked_add((span2, date(2024, 1, 1)))?; assert_eq!(span3.fieldwise(), 2.years().months(4).days(26).hours(2)); Ok(()) } ``` While Chrono has types like `Months` and `Days`, there's no way to combine them into one, and Chrono does not provide operations on both at the same time. ### Jiff supports zone-aware re-balancing of durations If you have a span of `1.day()` and want to convert it to hours, then that calculation depends on how long the day is. If you don't provide any reference datetime, then Jiff assumes the day is always 24 hours long: ```rust use jiff::{SpanRound, ToSpan, Unit}; fn main() -> anyhow::Result<()> { let span1 = 1.day(); let span2 = span1.round(SpanRound::new().largest(Unit::Hour))?; assert_eq!(span2, 24.hours().fieldwise()); Ok(()) } ``` But if a reference date is provided with a time zone, then the re-balancing is DST safe: ```rust use jiff::{civil::date, SpanRound, ToSpan, Unit}; fn main() -> anyhow::Result<()> { // In the case of a gap (typically transitioning in DST): let zdt = date(2024, 3, 9).at(21, 0, 0, 0).in_tz("America/New_York")?; let span1 = 1.day(); let span2 = span1.round( SpanRound::new().largest(Unit::Hour).relative(&zdt) )?; assert_eq!(span2, 23.hours().fieldwise()); // In the case of a fold (typically transitioning out of DST): let zdt = date(2024, 11, 2).at(21, 0, 0, 0).in_tz("America/New_York")?; let span1 = 1.day(); let span2 = span1.round( SpanRound::new().largest(Unit::Hour).relative(&zdt) )?; assert_eq!(span2, 25.hours().fieldwise()); Ok(()) } ``` ### Chrono is faster than Jiff in some cases .. and in other cases, Jiff is a little faster than Chrono. But Chrono does seem to have the edge. These benchmark results were collected on `2024-07-11`. ```text $ cd bench $ cargo bench -- --save-baseline base [.. snip ..] $ critcmp -g '^[^/]+/(.*)$' -f '^(chrono|chrono-tzfile|jiff)/' base group base/chrono-tzfile/ base/chrono/ base/jiff/ ----- ------------------- ------------ ---------- civil_datetime_to_instant_static 1.00 19.2±0.26ns ? ?/sec 1.16 22.1±0.20ns ? ?/sec 1.00 19.2±0.74ns ? ?/sec civil_datetime_to_instant_with_tzdb_lookup 21.31 2.2±0.03µs ? ?/sec 1.00 103.0±4.99ns ? ?/sec instant_to_civil_datetime_offset 1.00 6.9±0.02ns ? ?/sec 3.45 23.8±1.11ns ? ?/sec instant_to_civil_datetime_static 1.17 20.1±0.16ns ? ?/sec 1.00 17.3±0.12ns ? ?/sec 2.32 40.1±0.22ns ? ?/sec offset_to_civil_datetime 6.22 5.5±0.02ns ? ?/sec 1.00 0.9±0.02ns ? ?/sec offset_to_instant 3.61 1.4±0.02ns ? ?/sec 1.00 0.4±0.00ns ? ?/sec parse_civil_datetime 2.00 69.1±1.33ns ? ?/sec 1.00 34.5±0.15ns ? ?/sec parse_rfc2822 1.36 57.5±0.58ns ? ?/sec 1.00 42.1±0.46ns ? ?/sec parse_strptime 2.59 169.4±3.96ns ? ?/sec 1.00 65.5±0.90ns ? ?/sec zoned_add_time_duration 1.00 5.6±0.06ns ? ?/sec 5.85 32.8±0.25ns ? ?/sec ``` It's plausible that in cases where Jiff is slower (for example, `zoned_add_time_duration`), users could use `Timestamp` instead of `Zoned`. Namely, `Zoned` has some overhead associated with it due to the fact that it stores a `civil::DateTime`, `Timestamp` and a `TimeZone`. Where as a `Timestamp` is just a 96-bit integer number of nanoseconds. ## [`time`](https://docs.rs/time) (v0.3.36) `time` is a Rust datetime library that provides a time zone offset aware datetime type. For the following comparisons, a `Cargo.toml` with the following dependencies should be able to run any of the programs in this section: ```toml anyhow = "1.0.81" jiff = { version = "0.1.0", features = ["serde"] } time = { version = "0.3.36", features = ["local-offset", "macros", "parsing"] } ``` ### Time zone database integration Like `chrono`, the `time` crate does not come with any out of the box functionality for reading your system's copy of the Time Zone Database. Unlike Chrono, however, `time` does not have any way to use the Time Zone Database at all. That is, there is nothing like `chrono-tz` or `tzfile` for `time`, and `time` does not provide the extension points necessary in its API for such a thing to exist. (The `chrono-tz` and `tzfile` crates work by implementing Chrono's `TimeZone` trait.) The main thing `time` supports is a concept of "local" time. In particular, it is limited to determining your system's default time zone offset, but nothing more. That is, it doesn't support DST safe arithmetic: ```rust use anyhow::Context; use time::{ext::NumericalDuration, macros::datetime, Duration}; fn main() -> anyhow::Result<()> { // We create a fixed datetime for testing purposes, // but it's the same sort of value we would get back // from `OffsetDateTime::now_local()`. let dt1 = datetime!(2024-03-10 01:30:00 -05:00); let dt2 = dt1.checked_add(1.hours()) .context("datetime arithmetic failed")?; // The 2 o'clock hour didn't exist on 2024-03-10 // in New York. assert_eq!(dt2.to_string(), "2024-03-10 2:30:00.0 -05:00:00"); Ok(()) } ``` `time`, in its present design, is fundamentally incapable of doing daylight saving time safe arithmetic because its `OffsetDateTime` type doesn't know anything about the time zone rules. Compare this with Jiff, which lets you not only create a datetime with an offset, but with a _time zone_: ```rust use jiff::{civil::date, ToSpan}; fn main() -> anyhow::Result<()> { let zdt1 = date(2024, 3, 10).at(1, 30, 0, 0).in_tz("America/New_York")?; let zdt2 = zdt1.checked_add(1.hour())?; assert_eq!(zdt2.to_string(), "2024-03-10T03:30:00-04:00[America/New_York]"); Ok(()) } ``` In my comparison with Chrono I went through a lot of examples involving time zones. I did this because Chrono supports DST safe arithmetic generally, but with a lot of nuanced differences from what Jiff supports. Conversely, `time` doesn't really support time zones at all. (The main exception is that `time` can return the system configured offset by virtue of platform APIs like `libc`. But time zone support stops there.) So at this time, in this document, we won't belabor the point. ### Jiff allows getting the current time safely from multiple threads ```rust use jiff::Zoned; fn main() -> anyhow::Result<()> { let handle = std::thread::spawn(|| { println!("{}", Zoned::now()); }); handle.join().unwrap(); Ok(()) } ``` The output on my system of the above program is: ```text 2024-07-12T15:02:15.92054241-04:00[America/New_York] ``` Conversely, this program using the `time` crate: ```rust,no_run use time::OffsetDateTime; fn main() -> anyhow::Result<()> { let handle = std::thread::spawn(|| { println!("{}", OffsetDateTime::now_local().unwrap()); }); handle.join().unwrap(); Ok(()) } ``` Has this output: ```text thread '' panicked at main.rs:7:52: called `Result::unwrap()` on an `Err` value: IndeterminateOffset note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace thread 'main' panicked at main.rs:9:19: called `Result::unwrap()` on an `Err` value: Any { .. } ``` The reason for this is that `time` uses `libc` APIs for querying the local time. These `libc` APIs may access the environment in a way that is not synchronized with Rust's standard library, which leads to a path where safe Rust code can be written to cause undefined behavior. `time` mitigates this by checking how many threads are active. If it's a value other than `1`, then `now_local()` fails. Jiff avoids this by avoiding `libc`. Jiff does still read environment variables, but only does so through Rust's standard library `std::env` module. This makes Jiff's access to the environment sound. The `time` crate does provide a way to change this behavior by explicitly opting into the possibility of undefined behavior via `time::util::local_offset::set_soundness`. Aside from that, it is likely that this is a temporary state for `time` until it either implements the `libc` functionality it needs by itself, or until [`std::env::set_var`] is marked `unsafe`. (Which will likely happen in Rust 2024.) [`std::env::set_var`]: https://doc.rust-lang.org/std/env/fn.set_var.html ### `time` supports its own custom format description ```rust use time::{macros::format_description, OffsetDateTime}; fn main() -> anyhow::Result<()> { let format = format_description!( "[year]-[month]-[day] [hour]:[minute]:[second] \ [offset_hour sign:mandatory]:[offset_minute]:[offset_second]" ); let odt = OffsetDateTime::parse("2024-07-11 22:49:00 -04:00:00", &format)?; assert_eq!(odt.to_string(), "2024-07-11 22:49:00.0 -04:00:00"); Ok(()) } ``` Jiff does support a `strptime`/`strftime` style API via the `jiff::fmt::strtime` module. ### Jiff supports rounding datetimes We use a `Zoned` with a `TimeZone` that has a fixed offset. This is same as `time`'s `OffsetDateTime` type: ```rust use jiff::{civil::date, tz::{self, TimeZone}, Unit, Zoned}; fn main() -> anyhow::Result<()> { let tz = TimeZone::fixed(tz::offset(-4)); let zdt1 = date(2024, 7, 11).at(16, 46, 0, 0).to_zoned(tz)?; let zdt2 = zdt1.round(Unit::Hour)?; assert_eq!(zdt2.to_string(), "2024-07-11T17:00:00-04:00[-04:00]"); Ok(()) } ``` Note though that because Jiff has support for time zones, you generally shouldn't need to (and shouldn't _want_ to) use fixed offset datetimes. It's because they don't take time zone rules into account and thus do not provide DST safe arithmetic. Instead, the code above should be written like this (unless you have a very specific reason to do otherwise): ```rust use jiff::{civil::date, Unit, Zoned}; fn main() -> anyhow::Result<()> { // Can also use `.to_zoned(TimeZone::system())` to use your system's // default time zone. let zdt1 = date(2024, 7, 11).at(16, 46, 0, 0).in_tz("America/New_York")?; let zdt2 = zdt1.round(Unit::Hour)?; assert_eq!(zdt2.to_string(), "2024-07-11T17:00:00-04:00[America/New_York]"); Ok(()) } ``` From here on, we won't use fixed offset datetimes in order to avoid encouraging their use. The `time` crate has no rounding APIs. ### Jiff supports rounding durations In Jiff, one can round the duration computed between two datetimes ```rust use jiff::{civil::date, RoundMode, ToSpan, Unit, ZonedDifference}; fn main() -> anyhow::Result<()> { let zdt1 = date(2001, 11, 18).at(8, 30, 0, 0).in_tz("America/New_York")?; let zdt2 = date(2024, 7, 11).at(22, 38, 0, 0).in_tz("America/New_York")?; let round_options = ZonedDifference::new(&zdt2) .largest(Unit::Year) .smallest(Unit::Day) .mode(RoundMode::HalfExpand); let span = zdt1.until(round_options)?; assert_eq!(span, 22.years().months(7).days(24).fieldwise()); Ok(()) } ``` The `time` crate has no rounding APIs. ### Jiff provides support for calendar arithmetic With Jiff, you can add durations with calendar units: ```rust use jiff::{civil::date, ToSpan, Unit}; fn main() -> anyhow::Result<()> { let zdt1 = date(2024, 7, 11).at(21, 0, 0, 0).in_tz("America/New_York")?; let zdt2 = zdt1.checked_add(2.years().months(6).days(1))?; assert_eq!(zdt2.to_string(), "2027-01-12T21:00:00-05:00[America/New_York]"); Ok(()) } ``` The `time` crate does provide a way to construct a `Duration` from units of days via `Duration::days`, but this of course requires assuming that all days are 24 hours long. And `time` does not support adding years or months. ### Jiff supports conveniently re-balancing durations Aside from calendar arithmetic, Jiff also supports re-balancing durations based on what you want the largest unit to be: ```rust use jiff::{SpanRound, ToSpan, Unit}; fn main() -> anyhow::Result<()> { // Balance down to seconds. let span1 = 4.hours().minutes(36).seconds(59); let span2 = span1.round(SpanRound::new().largest(Unit::Second))?; assert_eq!(span2, 16_619.seconds().fieldwise()); // Now go back by balancing up to hours. let span1 = 16_619.seconds(); let span2 = span1.round(SpanRound::new().largest(Unit::Hour))?; assert_eq!(span2, 4.hours().minutes(36).seconds(59).fieldwise()); Ok(()) } ``` The `time` crate's `Duration` type can go from bigger units down to smaller units easily enough: ```rust use time::{ext::NumericalDuration, Duration}; fn main() -> anyhow::Result<()> { let span = 4.hours() + 36.minutes() + 59.seconds(); assert_eq!(span.whole_seconds(), 16_619); Ok(()) } ``` But going from smaller units back up to larger units is difficult: ```rust use time::{ext::NumericalDuration, Duration}; fn main() -> anyhow::Result<()> { let span = 16_619.seconds(); assert_eq!(span.whole_hours(), 4); assert_eq!(span.whole_minutes(), 276); assert_eq!(span.whole_seconds(), 16_619); Ok(()) } ``` Notice that the accessors just report how many whole units the span is. You can't get the span broken down into smaller units. To achieve that, you need to do the arithmetic yourself: ```rust use time::{convert::{Hour, Minute, Second}, ext::NumericalDuration, Duration}; fn main() -> anyhow::Result<()> { let mut span = 16_619.seconds(); assert_eq!(span.whole_hours(), 4); assert_eq!(span.whole_minutes() % Minute::per(Hour) as i64, 36); assert_eq!(span.whole_seconds() % Second::per(Minute) as i64, 59); Ok(()) } ``` ### Jiff is faster than `time` in some cases .. and in other cases, `time` is a little faster than Jiff. These benchmark results were collected on `2024-07-11`. ```text $ cd bench $ cargo bench -- --save-baseline base [.. snip ..] $ critcmp -g '^[^/]+/(.*)$' -f '^(time|jiff)/' base group base/jiff/ base/time/ ----- ---------- ---------- instant_to_civil_datetime_offset 1.65 23.8±1.11ns ? ?/sec 1.00 14.4±0.14ns ? ?/sec offset_to_civil_datetime 1.14 0.9±0.02ns ? ?/sec 1.00 0.8±0.00ns ? ?/sec offset_to_instant 1.00 0.4±0.00ns ? ?/sec 6.29 2.4±0.03ns ? ?/sec parse_civil_datetime 1.00 34.5±0.15ns ? ?/sec 1.95 67.2±1.49ns ? ?/sec parse_rfc2822 1.00 42.1±0.46ns ? ?/sec 1.75 73.8±0.66ns ? ?/sec parse_strptime 1.00 65.5±0.90ns ? ?/sec 1.71 112.0±0.92ns ? ?/sec zoned_add_time_duration 1.42 32.8±0.25ns ? ?/sec 1.00 23.1±0.15ns ? ?/sec ``` It's plausible that in cases where Jiff is slower (for example, `zoned_add_time_duration`), users could use `Timestamp` instead of `Zoned`. Namely, `Zoned` has some overhead associated with it due to the fact that it stores a `civil::DateTime`, `Timestamp` and a `TimeZone`. Where as a `Timestamp` is just a 96-bit integer number of nanoseconds. Note that some benchmarks were omitted here since `time` does not support time zones. ## [`hifitime`](https://docs.rs/hifitime) (v3.9.0) `hifitime` is a datetime library with a focus on engineering and scientific calculations where general relativity and time dilation matter. It supports conversion between many different time scales: TAI, Terrestrial Time, UTC, GPST and more. It also supports leap seconds. For the following comparisons, a `Cargo.toml` with the following dependencies should be able to run any of the programs in this section: ```toml anyhow = "1.0.81" hifitime = "3.9.0" jiff = { version = "0.1.0", features = ["serde"] } ``` ### Time zone database integration Like the `time` crate, `hifitime` does not support time zones and does not have any integration with the Time Zone Database. `hifitime` doesn't have any equivalent to `OffsetDateTime` like in `time` either. The only datetime type that `hifitime` has is `Epoch`, and it is an absolute time. While you can convert between it and civil time (assuming civil time is in UTC), there is no data type in `hifitime` for representing civil time. ### `hifitime` supports leap seconds In particular, when computing a duration from two `Epoch` values that spans a positive leap second (a second gets repeated), `hifitime` will correctly report the accurate duration: ```rust use hifitime::{Duration, Epoch}; fn main() -> anyhow::Result<()> { let e1: Epoch = "2015-06-30T23:00:00 UTC".parse()?; let e2: Epoch = "2015-07-01T00:00:00 UTC".parse()?; let duration = e2 - e1; assert_eq!(duration, Duration::from_seconds(3_601.0)); Ok(()) } ``` Jiff, however, [does not support leap seconds][jiff-leap-seconds]: ```rust use jiff::{Timestamp, ToSpan}; fn main() -> anyhow::Result<()> { let ts1: Timestamp = "2015-06-30T23:00:00Z".parse()?; let ts2: Timestamp = "2015-07-01T00:00:00Z".parse()?; let span = ts2 - ts1; assert_eq!(span, 3_600.seconds().fieldwise()); Ok(()) } ``` So in this case, Jiff reports `3,600` seconds as the duration, but the _actual_ duration was `3,601` seconds, as reported by `hifitime`. [jiff-leap-seconds]: https://github.com/BurntSushi/jiff/issues/7 ### Jiff makes checked or saturating arithmetic explicit For Jiff, whether you want to saturate or not is an explicit part of the API. And implementations of the `Add` operator will panic on overflow: ```rust use jiff::{Timestamp, ToSpan}; fn main() -> anyhow::Result<()> { let ts = Timestamp::MAX; assert!(ts.checked_add(1.day()).is_err()); assert_eq!(ts.saturating_add(1.hour()), ts); Ok(()) } ``` In contrast, `hifitime` appears to use saturating arithmetic everywhere (I've not been able to find this behavior documented though, so I'm not clear on what the intended semantics are): ```rust use hifitime::{Duration, Epoch}; fn main() -> anyhow::Result<()> { let e1 = Epoch::from_unix_seconds(f64::MAX); let e2 = e1 + Duration::from_days(1.0); assert_eq!(e1, e2); Ok(()) } ``` ## [`icu`](https://docs.rs/icu) (v1.5.0) The `icu` crate fulfils a slightly different need than `jiff`. Its main features are calendrical calculations (`icu::calendar`), supporting conversions between different calendar systems such as Gregorian, Buddhist, Islamic, Japanese, etc., as well as localized datetime formatting (`icu::datetime`). It does not perform datetime or time-zone arithmetic, and does not have a timestamp or duration type. `icu` can be used to complement `jiff` when localized date formatting or calendar conversions are required: ```rust use icu::{ calendar::{japanese::Japanese, DateTime}, datetime::TypedDateTimeFormatter, locid::locale, }; use jiff::Timestamp; fn main() -> anyhow::Result<()> { let ts: Timestamp = "2024-09-10T23:37:20Z".parse()?; let zoned = ts.in_tz("Asia/Tokyo")?; // Create ICU datetime. let datetime = DateTime::try_new_iso_datetime( i32::from(zoned.year()), // These unwraps are all guaranteed to be // correct because Jiff's bounds on allowable // values fit within icu's bounds. u8::try_from(zoned.month()).unwrap(), u8::try_from(zoned.day()).unwrap(), u8::try_from(zoned.hour()).unwrap(), u8::try_from(zoned.minute()).unwrap(), u8::try_from(zoned.second()).unwrap(), )?; // Convert to Japanese calendar. let japanese_datetime = DateTime::new_from_iso(datetime, Japanese::new()); // Format for the en-GB locale. let formatter = TypedDateTimeFormatter::try_new( &locale!("en-GB").into(), Default::default(), )?; // Assert that we get the expected result. assert_eq!( formatter.format(&japanese_datetime).to_string(), "Sept 11, 6 Reiwa, 08:37:20", ); Ok(()) } ``` The above example requires the following dependency specifications: ```toml anyhow = "1.0.81" icu = { version = "1.5.0", features = ["std"] } jiff = { version = "0.1.0", features = ["serde"] } ``` jiff-0.1.28/COPYING000064400000000000000000000001761046102023000116710ustar 00000000000000This project is dual-licensed under the Unlicense and MIT licenses. You may use this code under the terms of either license. jiff-0.1.28/Cargo.lock0000644000001204100000000000100100130ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "android-tzdata" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" [[package]] name = "android_system_properties" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ "libc", ] [[package]] name = "anstream" version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ "windows-sys", ] [[package]] name = "anstyle-wincon" version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", "windows-sys", ] [[package]] name = "anyhow" version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "bumpalo" version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "calendrical_calculations" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f27ca2b6e2f7d75f43e001ded6f25e79b80bded5abbe764cbdf78c25a3051f4b" dependencies = [ "core_maths", "displaydoc", ] [[package]] name = "cc" version = "1.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e" dependencies = [ "shlex", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "serde", "wasm-bindgen", "windows-targets", ] [[package]] name = "chrono-tz" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd6dd8046d00723a59a2f8c5f295c515b9bb9a331ee4f8f3d4dd49e428acd3b6" dependencies = [ "chrono", "chrono-tz-build", "phf", ] [[package]] name = "chrono-tz-build" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e94fea34d77a245229e7746bd2beb786cd2a896f306ff491fb8cecb3074b10a7" dependencies = [ "parse-zoneinfo", "phf_codegen", ] [[package]] name = "clap" version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" dependencies = [ "clap_builder", "clap_derive", ] [[package]] name = "clap_builder" version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim", ] [[package]] name = "clap_derive" version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ "heck", "proc-macro2", "quote", "syn", ] [[package]] name = "clap_lex" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "colorchoice" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "console" version = "0.15.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b" dependencies = [ "encode_unicode", "libc", "once_cell", "windows-sys", ] [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "core_maths" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3b02505ccb8c50b0aa21ace0fc08c3e53adebd4e58caa18a36152803c7709a3" dependencies = [ "libm", ] [[package]] name = "deranged" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", ] [[package]] name = "displaydoc" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "either" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "encode_unicode" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "fixed_decimal" version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0febbeb1118a9ecdee6e4520ead6b54882e843dd0592ad233247dbee84c53db8" dependencies = [ "displaydoc", "smallvec", "writeable", ] [[package]] name = "getrandom" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "hashbrown" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hifitime" version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c587aef1280b84f15bfd84eefff9ee55d1a2826e67f089ed263a8c3a029c273" dependencies = [ "js-sys", "lexical-core", "num-traits", "serde", "serde_derive", "wasm-bindgen", "web-sys", ] [[package]] name = "humantime" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "iana-time-zone" version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", "windows-core", ] [[package]] name = "iana-time-zone-haiku" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ "cc", ] [[package]] name = "icu" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dff5e3018d703f168b00dcefa540a65f1bbc50754ae32f3f5f0e43fe5ee51502" dependencies = [ "icu_calendar", "icu_casemap", "icu_collator", "icu_collections", "icu_datetime", "icu_decimal", "icu_experimental", "icu_list", "icu_locid", "icu_locid_transform", "icu_normalizer", "icu_plurals", "icu_properties", "icu_provider", "icu_segmenter", "icu_timezone", ] [[package]] name = "icu_calendar" version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7265b2137f9a36f7634a308d91f984574bbdba8cfd95ceffe1c345552275a8ff" dependencies = [ "calendrical_calculations", "displaydoc", "icu_calendar_data", "icu_locid", "icu_locid_transform", "icu_provider", "tinystr", "writeable", "zerovec", ] [[package]] name = "icu_calendar_data" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e009b7f0151ee6fb28c40b1283594397e0b7183820793e9ace3dcd13db126d0" [[package]] name = "icu_casemap" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ff0c8ae9f8d31b12e27fc385ff9ab1f3cd9b17417c665c49e4ec958c37da75f" dependencies = [ "displaydoc", "icu_casemap_data", "icu_collections", "icu_locid", "icu_properties", "icu_provider", "writeable", "zerovec", ] [[package]] name = "icu_casemap_data" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d57966d5ab748f74513be4046867f9a20e801e2775d41f91d04a0f560b61f08" [[package]] name = "icu_collator" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d370371887d31d56f361c3eaa15743e54f13bc677059c9191c77e099ed6966b2" dependencies = [ "displaydoc", "icu_collator_data", "icu_collections", "icu_locid_transform", "icu_normalizer", "icu_properties", "icu_provider", "smallvec", "utf16_iter", "utf8_iter", "zerovec", ] [[package]] name = "icu_collator_data" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ee3f88741364b7d6269cce6827a3e6a8a2cf408a78f766c9224ab479d5e4ae5" [[package]] name = "icu_collections" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" dependencies = [ "displaydoc", "yoke", "zerofrom", "zerovec", ] [[package]] name = "icu_datetime" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d115efb85e08df3fd77e77f52e7e087545a783fffba8be80bfa2102f306b1780" dependencies = [ "displaydoc", "either", "fixed_decimal", "icu_calendar", "icu_datetime_data", "icu_decimal", "icu_locid", "icu_locid_transform", "icu_plurals", "icu_provider", "icu_timezone", "smallvec", "tinystr", "writeable", "zerovec", ] [[package]] name = "icu_datetime_data" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ba7e7f7a01269b9afb0a39eff4f8676f693b55f509b3120e43a0350a9f88bea" [[package]] name = "icu_decimal" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb8fd98f86ec0448d85e1edf8884e4e318bb2e121bd733ec929a05c0a5e8b0eb" dependencies = [ "displaydoc", "fixed_decimal", "icu_decimal_data", "icu_locid_transform", "icu_provider", "writeable", ] [[package]] name = "icu_decimal_data" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d424c994071c6f5644f999925fc868c85fec82295326e75ad5017bc94b41523" [[package]] name = "icu_experimental" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "844ad7b682a165c758065d694bc4d74ac67f176da1c499a04d85d492c0f193b7" dependencies = [ "displaydoc", "fixed_decimal", "icu_collections", "icu_decimal", "icu_experimental_data", "icu_locid", "icu_locid_transform", "icu_normalizer", "icu_pattern", "icu_plurals", "icu_properties", "icu_provider", "litemap", "num-bigint", "num-rational", "num-traits", "smallvec", "tinystr", "writeable", "zerofrom", "zerotrie", "zerovec", ] [[package]] name = "icu_experimental_data" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c178b9a34083fca5bd70d61f647575335e9c197d0f30c38e8ccd187babc69d0" [[package]] name = "icu_list" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbfeda1d7775b6548edd4e8b7562304a559a91ed56ab56e18961a053f367c365" dependencies = [ "displaydoc", "icu_list_data", "icu_locid_transform", "icu_provider", "regex-automata 0.2.0", "writeable", ] [[package]] name = "icu_list_data" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1825170d2c6679cb20dbd96a589d034e49f698aed9a2ef4fafc9a0101ed298f" [[package]] name = "icu_locid" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" dependencies = [ "displaydoc", "litemap", "tinystr", "writeable", "zerovec", ] [[package]] name = "icu_locid_transform" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" dependencies = [ "displaydoc", "icu_locid", "icu_locid_transform_data", "icu_provider", "tinystr", "zerovec", ] [[package]] name = "icu_locid_transform_data" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" [[package]] name = "icu_normalizer" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" dependencies = [ "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", "icu_provider", "smallvec", "utf16_iter", "utf8_iter", "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" [[package]] name = "icu_pattern" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb7f36aafd098d6717de34e668a8120822275c1fba22b936e757b7de8a2fd7e4" dependencies = [ "displaydoc", "either", "writeable", "yoke", "zerofrom", ] [[package]] name = "icu_plurals" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba5a70e7c025dbd5c501b0a5c188cd11666a424f0dadcd4f0a95b7dafde3b114" dependencies = [ "displaydoc", "fixed_decimal", "icu_locid_transform", "icu_plurals_data", "icu_provider", "zerovec", ] [[package]] name = "icu_plurals_data" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e3e8f775b215d45838814a090a2227247a7431d74e9156407d9c37f6ef0f208" [[package]] name = "icu_properties" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" dependencies = [ "displaydoc", "icu_collections", "icu_locid_transform", "icu_properties_data", "icu_provider", "tinystr", "zerovec", ] [[package]] name = "icu_properties_data" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" [[package]] name = "icu_provider" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" dependencies = [ "displaydoc", "icu_locid", "icu_provider_macros", "stable_deref_trait", "tinystr", "writeable", "yoke", "zerofrom", "zerovec", ] [[package]] name = "icu_provider_macros" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "icu_segmenter" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a717725612346ffc2d7b42c94b820db6908048f39434504cb130e8b46256b0de" dependencies = [ "core_maths", "displaydoc", "icu_collections", "icu_locid", "icu_provider", "icu_segmenter_data", "utf8_iter", "zerovec", ] [[package]] name = "icu_segmenter_data" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f739ee737260d955e330bc83fdeaaf1631f7fb7ed218761d3c04bb13bb7d79df" [[package]] name = "icu_timezone" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa91ba6a585939a020c787235daa8aee856d9bceebd6355e283c0c310bc6de96" dependencies = [ "displaydoc", "icu_calendar", "icu_provider", "icu_timezone_data", "tinystr", "zerotrie", "zerovec", ] [[package]] name = "icu_timezone_data" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c588878c508a3e2ace333b3c50296053e6483c6a7541251b546cc59dcd6ced8e" [[package]] name = "indexmap" version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "insta" version = "1.41.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e9ffc4d4892617c50a928c52b2961cb5174b6fc6ebf252b2fac9d21955c48b8" dependencies = [ "console", "lazy_static", "linked-hash-map", "similar", ] [[package]] name = "is_terminal_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itoa" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "jiff" version = "0.1.28" dependencies = [ "anyhow", "chrono", "chrono-tz", "clap", "hifitime", "humantime", "icu", "insta", "jiff-tzdb", "jiff-tzdb-platform", "js-sys", "log", "portable-atomic", "portable-atomic-util", "quickcheck", "serde", "serde_json", "serde_yaml", "tabwriter", "time", "tzfile", "walkdir", "wasm-bindgen", "windows-sys", ] [[package]] name = "jiff-tzdb" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf2cec2f5d266af45a071ece48b1fb89f3b00b2421ac3a5fe10285a6caaa60d3" [[package]] name = "jiff-tzdb-platform" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a63c62e404e7b92979d2792352d885a7f8f83fd1d0d31eea582d77b2ceca697e" dependencies = [ "jiff-tzdb", ] [[package]] name = "js-sys" version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" dependencies = [ "once_cell", "wasm-bindgen", ] [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "lexical-core" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2cde5de06e8d4c2faabc400238f9ae1c74d5412d03a7bd067645ccbc47070e46" dependencies = [ "lexical-parse-float", "lexical-parse-integer", "lexical-util", ] [[package]] name = "lexical-parse-float" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683b3a5ebd0130b8fb52ba0bdc718cc56815b6a097e28ae5a6997d0ad17dc05f" dependencies = [ "lexical-parse-integer", "lexical-util", "static_assertions", ] [[package]] name = "lexical-parse-integer" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d0994485ed0c312f6d965766754ea177d07f9c00c9b82a5ee62ed5b47945ee9" dependencies = [ "lexical-util", "static_assertions", ] [[package]] name = "lexical-util" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5255b9ff16ff898710eb9eb63cb39248ea8a5bb036bea8085b1a767ff6c4e3fc" dependencies = [ "static_assertions", ] [[package]] name = "libc" version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libm" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "linked-hash-map" version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "litemap" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" [[package]] name = "log" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "num-bigint" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", ] [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-integer" version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ "num-traits", ] [[package]] name = "num-rational" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" dependencies = [ "num-bigint", "num-integer", "num-traits", ] [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "libm", ] [[package]] name = "num_threads" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" dependencies = [ "libc", ] [[package]] name = "once_cell" version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "parse-zoneinfo" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24" dependencies = [ "regex", ] [[package]] name = "phf" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" dependencies = [ "phf_shared", ] [[package]] name = "phf_codegen" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" dependencies = [ "phf_generator", "phf_shared", ] [[package]] name = "phf_generator" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" dependencies = [ "phf_shared", "rand", ] [[package]] name = "phf_shared" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" dependencies = [ "siphasher", ] [[package]] name = "portable-atomic" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" [[package]] name = "portable-atomic-util" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" dependencies = [ "portable-atomic", ] [[package]] name = "powerfmt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "proc-macro2" version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] [[package]] name = "quickcheck" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" dependencies = [ "rand", ] [[package]] name = "quote" version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "rand_core", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] [[package]] name = "regex" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", "regex-automata 0.4.9", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9368763f5a9b804326f3af749e16f9abf378d227bcdee7634b13d8f17793782" dependencies = [ "memchr", ] [[package]] name = "regex-automata" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "ryu" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ "winapi-util", ] [[package]] name = "serde" version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.134" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d" dependencies = [ "itoa", "memchr", "ryu", "serde", ] [[package]] name = "serde_yaml" version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ "indexmap", "itoa", "ryu", "serde", "unsafe-libyaml", ] [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "similar" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" [[package]] name = "siphasher" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" [[package]] name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "stable_deref_trait" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" version = "2.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d53cbcb5a243bd33b7858b1d7f4aca2153490815872d86d955d6ea29f743c035" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "synstructure" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tabwriter" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a327282c4f64f6dc37e3bba4c2b6842cc3a992f204fa58d917696a89f691e5f6" dependencies = [ "unicode-width", ] [[package]] name = "time" version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" dependencies = [ "deranged", "libc", "num-conv", "num_threads", "powerfmt", "serde", "time-core", "time-macros", ] [[package]] name = "time-core" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" dependencies = [ "num-conv", "time-core", ] [[package]] name = "tinystr" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" dependencies = [ "displaydoc", "zerovec", ] [[package]] name = "tzfile" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f59c22c42a2537e4c7ad21a4007273bbc5bebed7f36bc93730a5780e22a4592e" dependencies = [ "byteorder", "chrono", ] [[package]] name = "unicode-ident" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-width" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unsafe-libyaml" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" [[package]] name = "utf16_iter" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" [[package]] name = "utf8_iter" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "walkdir" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", ] [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" dependencies = [ "cfg-if", "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" dependencies = [ "bumpalo", "log", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" [[package]] name = "web-sys" version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "winapi-util" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ "windows-sys", ] [[package]] name = "windows-core" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ "windows-targets", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "write16" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" [[package]] name = "writeable" version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" dependencies = [ "either", ] [[package]] name = "yoke" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" dependencies = [ "serde", "stable_deref_trait", "yoke-derive", "zerofrom", ] [[package]] name = "yoke-derive" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", "syn", "synstructure", ] [[package]] name = "zerofrom" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", "quote", "syn", "synstructure", ] [[package]] name = "zerotrie" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb594dd55d87335c5f60177cee24f19457a5ec10a065e0a3014722ad252d0a1f" dependencies = [ "displaydoc", "yoke", "zerofrom", ] [[package]] name = "zerovec" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" dependencies = [ "yoke", "zerofrom", "zerovec-derive", ] [[package]] name = "zerovec-derive" version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", "syn", ] jiff-0.1.28/Cargo.toml0000644000000100400000000000100100330ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.70" name = "jiff" version = "0.1.28" authors = ["Andrew Gallant "] build = false include = [ "/src/**/*.rs", "/tests/lib.rs", "/*.md", "COPYING", "LICENSE-MIT", "UNLICENSE", ] autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = """ A date-time library that encourages you to jump into the pit of success. This library is heavily inspired by the Temporal project. """ documentation = "https://docs.rs/jiff" readme = "README.md" keywords = [ "date", "time", "calendar", "zone", "duration", ] categories = [ "date-and-time", "no-std", ] license = "Unlicense OR MIT" repository = "https://github.com/BurntSushi/jiff" [package.metadata.docs.rs] all-features = true rustdoc-args = [ "--cfg", "docsrs", ] [features] alloc = [ "serde?/alloc", "portable-atomic-util/alloc", ] default = [ "std", "tz-system", "tzdb-bundle-platform", "tzdb-zoneinfo", "tzdb-concatenated", ] js = [ "dep:wasm-bindgen", "dep:js-sys", ] logging = ["dep:log"] serde = ["dep:serde"] std = [ "alloc", "log?/std", "serde?/std", ] tz-system = [ "std", "dep:windows-sys", ] tzdb-bundle-always = [ "dep:jiff-tzdb", "alloc", ] tzdb-bundle-platform = [ "dep:jiff-tzdb-platform", "alloc", ] tzdb-concatenated = ["std"] tzdb-zoneinfo = ["std"] [lib] name = "jiff" path = "src/lib.rs" [[test]] name = "integration" path = "tests/lib.rs" [dependencies.jiff-tzdb] version = "0.1.2" optional = true [dependencies.log] version = "0.4.21" optional = true default-features = false [dependencies.serde] version = "1.0.203" optional = true default-features = false [dev-dependencies.anyhow] version = "1.0.81" [dev-dependencies.chrono] version = "0.4.38" features = ["serde"] [dev-dependencies.chrono-tz] version = "0.10.0" [dev-dependencies.clap] version = "4.5.23" features = ["derive"] [dev-dependencies.humantime] version = "2.1.0" [dev-dependencies.icu] version = "1.5.0" features = ["std"] [dev-dependencies.insta] version = "1.39.0" [dev-dependencies.quickcheck] version = "1.0.3" default-features = false [dev-dependencies.serde] version = "1.0.203" features = ["derive"] [dev-dependencies.serde_json] version = "1.0.117" [dev-dependencies.serde_yaml] version = "0.9.34" [dev-dependencies.tabwriter] version = "1.4.0" [dev-dependencies.time] version = "0.3.36" features = [ "local-offset", "macros", "parsing", ] [dev-dependencies.tzfile] version = "0.1.3" [dev-dependencies.walkdir] version = "2.5.0" [target.'cfg(all(any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown"))'.dependencies.js-sys] version = "0.3.50" optional = true [target.'cfg(all(any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown"))'.dependencies.wasm-bindgen] version = "0.2.70" optional = true [target.'cfg(any(windows, target_family = "wasm"))'.dependencies.jiff-tzdb-platform] version = "0.1.2" optional = true [target.'cfg(not(target_family = "wasm"))'.dev-dependencies.hifitime] version = "3.9.0" [target.'cfg(not(target_has_atomic = "ptr"))'.dependencies.portable-atomic] version = "1.10.0" default-features = false [target.'cfg(not(target_has_atomic = "ptr"))'.dependencies.portable-atomic-util] version = "0.2.4" default-features = false [target."cfg(windows)".dependencies.windows-sys] version = ">=0.52.0, <=0.59.*" features = [ "Win32_Foundation", "Win32_System_Time", ] optional = true default-features = false [profile.testrelease] debug-assertions = false inherits = "test" jiff-0.1.28/Cargo.toml.orig000064400000000000000000000164541046102023000135330ustar 00000000000000[package] name = "jiff" version = "0.1.28" #:version authors = ["Andrew Gallant "] license = "Unlicense OR MIT" repository = "https://github.com/BurntSushi/jiff" documentation = "https://docs.rs/jiff" description = ''' A date-time library that encourages you to jump into the pit of success. This library is heavily inspired by the Temporal project. ''' categories = ["date-and-time", "no-std"] keywords = ["date", "time", "calendar", "zone", "duration"] edition = "2021" autotests = false autoexamples = false rust-version = "1.70" # We include `/tests/lib.rs` to squash a `cargo package` warning that the # `integration` test target is being ignored. We don't include anything else # so tests obviously won't work, but it makes `cargo package` quiet. include = [ "/src/**/*.rs", "/tests/lib.rs", "/*.md", "COPYING", "LICENSE-MIT", "UNLICENSE", ] [workspace] members = [ "jiff-cli", "jiff-tzdb", "jiff-tzdb-platform", "examples/*", ] # Features are documented in the "Crate features" section of the crate docs: # https://docs.rs/jiff/*/#crate-features [features] default = [ "std", "tz-system", "tzdb-bundle-platform", "tzdb-zoneinfo", "tzdb-concatenated", ] std = ["alloc", "log?/std", "serde?/std"] alloc = ["serde?/alloc", "portable-atomic-util/alloc"] serde = ["dep:serde"] logging = ["dep:log"] # When enabled, Jiff will include code that attempts to determine the "system" # time zone. For example, on Unix systems, this is usually determined by # looking at the symlink information on /etc/localtime. But in general, it's # very platform specific and heuristic oriented. On some platforms, this may # require extra dependencies. (For example, `windows-sys` on Windows.) tz-system = ["std", "dep:windows-sys"] # This conditionally bundles tzdb into the binary depending on which platform # Jiff is being built for. tzdb-bundle-platform = ["dep:jiff-tzdb-platform", "alloc"] # This forces the jiff-tzdb crate to be included. If tzdb-zoneinfo is enabled, # then the system tzdb will take priority over the bundled database. tzdb-bundle-always = ["dep:jiff-tzdb", "alloc"] # This enables the system or "zoneinfo" time zone database. This is the # database that is typically found at /usr/share/zoneinfo on macOS and Linux. tzdb-zoneinfo = ["std"] # This enables the system concatenated time zone database. On some platforms, # like Android, this is the standard time zone database instead of the more # widespread `zoneinfo` directory created by `zic` itseld. # # This being enabled just means that some standard paths will be searched # for the concatenated database and it will be used if the standard zoneinfo # directory couldn't be found. tzdb-concatenated = ["std"] # This enables bindings to web browser APIs for retrieving the current time # and configured time zone. This ONLY applies on wasm32-unknown-unknown and # wasm64-unknown-unknown targets. Specifically, *not* on wasm32-wasi or # wasm32-unknown-emscripten targets. # # This is an "ecosystem" compromise due to the fact that there is no general # way to determine at compile time whether a wasm target is intended for use # on the "web." In practice, only wasm{32,64}-unknown-unknown targets are used # on the web, but wasm{32,64}-unknown-unknown targets can be used in non-web # contexts as well. Thus, the `js` feature should be enabled only by binaries, # tests or benchmarks when it is *known* that the application will be used in a # web context. # # Libraries that depend on Jiff should not need to define their own `js` # feature just to forward it to Jiff. Instead, application authors can depend # on Jiff directly and enable the `js` feature themselves. # # (This is the same dependency setup that the `getrandom` crate uses.) js = ["dep:wasm-bindgen", "dep:js-sys"] [dependencies] jiff-tzdb = { version = "0.1.2", path = "jiff-tzdb", optional = true } log = { version = "0.4.21", optional = true, default-features = false } serde = { version = "1.0.203", optional = true, default-features = false } # Note that the `cfg` gate for the `tzdb-bundle-platform` must repeat the # target gate on this dependency. The intent is that `tzdb-bundle-platform` # is enabled by default, but that the `tzdb-bundle-platform` crate is only # actually used on platforms without a system tzdb (i.e., Windows and wasm). [target.'cfg(any(windows, target_family = "wasm"))'.dependencies] jiff-tzdb-platform = { version = "0.1.2", path = "jiff-tzdb-platform", optional = true } [target.'cfg(windows)'.dependencies.windows-sys] version = ">=0.52.0, <=0.59.*" default-features = false features = ["Win32_Foundation", "Win32_System_Time"] optional = true [target.'cfg(all(any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown"))'.dependencies] js-sys = { version = "0.3.50", optional = true } wasm-bindgen = { version = "0.2.70", optional = true } # For targets that have no atomics in `std`, we add a dependency on # `portable-atomic-util` for its Arc implementation. # # Note that for this to work, you may need to enable a `portable-atomic` # feature like `portable-atomic/unsafe-assume-single-core` or # `portable-atomic/critical-section`. [target.'cfg(not(target_has_atomic = "ptr"))'.dependencies] portable-atomic = { version = "1.10.0", default-features = false } portable-atomic-util = { version = "0.2.4", default-features = false } [dev-dependencies] anyhow = "1.0.81" chrono = { version = "0.4.38", features = ["serde"] } chrono-tz = "0.10.0" clap = { version = "4.5.23", features = ["derive"] } humantime = "2.1.0" # This adds approximately 50 new compilation units when running `cargo test` # locally on Unix. Blech. icu = { version = "1.5.0", features = ["std"] } insta = "1.39.0" # We force `serde` to be enabled in dev mode so that the docs render and test # correctly. This is highly suspicious. jiff = { path = "./", default-features = false, features = ["serde"] } quickcheck = { version = "1.0.3", default-features = false } serde = { version = "1.0.203", features = ["derive"] } serde_json = "1.0.117" serde_yaml = "0.9.34" tabwriter = "1.4.0" time = { version = "0.3.36", features = ["local-offset", "macros", "parsing"] } tzfile = "0.1.3" walkdir = "2.5.0" # hifitime doesn't build on wasm for some reason, so exclude it there. [target.'cfg(not(target_family = "wasm"))'.dev-dependencies.hifitime] version = "3.9.0" [[test]] path = "tests/lib.rs" name = "integration" # This is just like the default 'test' profile, but debug_assertions are # disabled. This is important to cover for Jiff because we do a lot of extra # work in our internal ranged integer types when debug_assertions are enabled. # It also makes types fatter. It's very useful for catching overflow bugs. # But since there's a fair bit of logic there, it's also worth running tests # without debug_assertions enabled to exercise the *actual* code paths used # in production. [profile.testrelease] inherits = "test" debug-assertions = false [package.metadata.docs.rs] # We want to document all features. all-features = true # Since this crate's feature setup is pretty complicated, it is worth opting # into a nightly unstable option to show the features that need to be enabled # for public API items. To do that, we set 'docsrs', and when that's enabled, # we enable the 'doc_auto_cfg' feature. # # To test this locally, run: # # RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features rustdoc-args = ["--cfg", "docsrs"] jiff-0.1.28/DESIGN.md000064400000000000000000001022451046102023000121310ustar 00000000000000# 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][comparison]," 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][comparison]" 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][comparison-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` and `Result`. This is frustrating in my experience because the `Option` _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` and instead returns `Result` 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`, 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`][comparison-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`][comparison-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][comparison-icu]. ## 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: * [Commentary from the original author of the `chrono` crate.][alt1] [alt1]: https://github.com/BurntSushi/jiff/issues/63 ## 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: ```rust 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().fieldwise()); assert_eq!(date2.until(date3)?, 30.days().fieldwise()); // But we can request bigger units! assert_eq!(date1.until((Unit::Month, date2))?, 1.month().fieldwise()); assert_eq!(date2.until((Unit::Month, date3))?, 1.month().fieldwise()); 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][temporal-one-duration]. 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][POSIX TZ], 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][RFC 8536], 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][github-issue-leap], 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`) 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][github-issue-leap]. ## 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][github-issue-duration] 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: ```rust use jiff::{civil::date, ToSpan}; let zdt1 = date(2024, 7, 16).at(22, 3, 23, 0).in_tz("America/New_York")?; let zdt2 = date(2024, 8, 16).at(22, 3, 59, 0).in_tz("America/New_York")?; assert_eq!(zdt1.until(&zdt2)?, 744.hours().seconds(36).fieldwise()); # Ok::<(), Box>(()) ``` Is equivalent to: ```rust use jiff::{civil::date, ToSpan, ZonedDifference}; let zdt1 = date(2024, 7, 16).at(22, 3, 23, 0).in_tz("America/New_York")?; let zdt2 = date(2024, 8, 16).at(22, 3, 59, 0).in_tz("America/New_York")?; assert_eq!( zdt1.until(ZonedDifference::new(&zdt2))?, 744.hours().seconds(36).fieldwise(), ); # Ok::<(), Box>(()) ``` The point of this is that `ZonedDifference` permits specifying additional configuration. For example, rounding the span returned: ```rust use jiff::{civil::date, ToSpan, Unit, ZonedDifference}; let zdt1 = date(2024, 7, 16).at(22, 3, 23, 0).in_tz("America/New_York")?; let zdt2 = date(2024, 8, 16).at(22, 3, 59, 0).in_tz("America/New_York")?; assert_eq!( zdt1.until(ZonedDifference::new(&zdt2).smallest(Unit::Minute))?, 744.hours().fieldwise(), ); # Ok::<(), Box>(()) ``` 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: ```rust use jiff::{civil::date, ToSpan, Unit, ZonedDifference}; let zdt1 = date(2024, 7, 16).at(22, 3, 23, 0).in_tz("America/New_York")?; let zdt2 = date(2024, 8, 16).at(22, 3, 59, 0).in_tz("America/New_York")?; let diff = ZonedDifference::new(&zdt2) .largest(Unit::Month) .smallest(Unit::Minute); assert_eq!(zdt1.until(diff)?, 1.month().fieldwise()); # Ok::<(), Box>(()) ``` One would need to do this: ```rust use jiff::{civil::date, RoundMode, SpanRound, ToSpan, Unit}; let zdt1 = date(2024, 7, 16).at(22, 3, 23, 0).in_tz("America/New_York")?; let zdt2 = date(2024, 8, 16).at(22, 3, 59, 0).in_tz("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().fieldwise()); # Ok::<(), Box>(()) ``` 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. [Temporal]: https://tc39.es/proposal-temporal/docs/ [temporal-one-duration]: https://github.com/tc39/proposal-temporal/issues/2915 [comparison]: http://docs.rs/jiff/*/jiff/_documentation/comparison/index.html [comparison-chrono]: https://docs.rs/jiff/*/jiff/_documentation/comparison/index.html#chrono-v0438 [comparison-time]: https://docs.rs/jiff/*/jiff/_documentation/comparison/index.html#time-v0336 [comparison-hifitime]: https://docs.rs/jiff/*/jiff/_documentation/comparison/index.html#hifitime-v0390 [comparison-icu]: https://docs.rs/jiff/*/jiff/_documentation/comparison/index.html#icu-v150 [RFC 8536]: https://datatracker.ietf.org/doc/draft-murchison-rfc8536bis/ [RFC 9557]: https://datatracker.ietf.org/doc/rfc9557/ [POSIX TZ]: https://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html [IANA Time Zone Database]: https://en.wikipedia.org/wiki/Tz_database [TAI]: https://en.wikipedia.org/wiki/International_Atomic_Time [github-issue-duration]: https://github.com/BurntSushi/jiff/issues/21 [github-issue-leap]: https://github.com/BurntSushi/jiff/issues/7 [`java.time`]: https://docs.oracle.com/javase/8/docs/api/java/time/package-summary.html [NodaTime]: https://nodatime.org/ [`chrono-tz`]: https://docs.rs/chrono-tz [`tzfile`]: https://docs.rs/tzfile [`chrono`]: https://docs.rs/chrono [`time`]: https://docs.rs/time [`hifitime`]: https://docs.rs/hifitime [`icu`]: https://docs.rs/icu jiff-0.1.28/LICENSE-MIT000064400000000000000000000020711046102023000122660ustar 00000000000000The MIT License (MIT) Copyright (c) 2015 Andrew Gallant Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. jiff-0.1.28/PLATFORM.md000064400000000000000000000523371046102023000124120ustar 00000000000000# Platform support This document describes Jiff's platform support. That is, it describes the interaction points between this library and its environment. Most of the details in this document are written down elsewhere on individual APIs, but this document serves to centralize everything in one place. As a general rule, interaction with the environment requires that Jiff's `std` feature is enabled. The `std` feature is what allows Jiff to read environment variables and files, for example. Before starting, let's cover some vocabulary first. ## Vocabulary This section defines the key terms used below when describing platform support. We also try to contextualize the concepts to make their meaning concrete in a way that hopefully relates to your lived experience. * [Civil time]: The time you see on your clock. And in general, the time that the humans in your approximate geographic vicinity also see. That is, civil time is a human coordinated agreement for communicating time in a particular geographic region. Civil time is also known as: local time, plain time, naive time, clock time and others. * [Time zone]: A set of rules for determining the civil (or "local") time, via an offset from UTC, in a particular geographic region. In many cases, the offset in a particular time zone can vary over the course of a year through transitions into and out of [daylight saving time]. A time zone is necessary to convert civil time into a precise unambiguous instant in time. * [IANA Time Zone Database]: A directory on your system containing a store of files, one per time zone, which encode the time at which transitions between UTC offsets occur in a specific geographic region. In effect, each time zone file provides a mapping between civil (or "local") time and UTC. The format of each file is called TZif and is specified by [RFC 8536]. This database is typically found at `/usr/share/zoneinfo` and only on Unix systems (including macOS). Other environments, like Windows and WASM, do not have a standard copy of the Time Zone Database. (Jiff will instead embed it into your program by default on these platforms.) * [IANA time zone identifier]: A short ASCII human readable string identifying a time zone in the IANA Time Zone Database. The time zone for where I live, `America/New_York`, has an entry at `/usr/share/zoneinfo/America/New_York` on my system. IANA time zone identifiers are used by Jiff's `Zoned` type to losslessly roundtrip datetimes via an interchange format specified by [Temporal] that draws inspiration from [RFC 3339], [RFC 9557] and [ISO 8601]. ## Environment variables Jiff generally only reads two environment variables. These variables are read on all platforms that support environment variables. So for example, Jiff will respect `TZ` on Windows. Note though that some environments, like `wasm32-wasip1` or `wasm32-unknown-emscripten`, are sandboxed by default. A sandboxed environment typically makes reading environment variables set outside the sandbox impossible (or require opt-in support, such as [wasmtime]'s `-S inherit-env` or `--env` flags). Jiff may read additional environment variables for platform specific integration. ### `TZDIR` The `TZDIR` environment variable tells Jiff where to look for the [IANA Time Zone Database]. When it isn't set, Jiff will check a few standard locations for the database. It's usually found at `/usr/share/zoneinfo`. It can be useful to set this for non-standard environments or when you specifically want Jiff to prefer using a non-system copy of the database. (If you want Jiff to _only_ use a non-system copy of the database, then you'll need to use `TimeZoneDatabase::from_dir` and use the resulting handle explicitly.) If a IANA Time Zone Database could not be found a `TZDIR`, then Jiff will still attempt to look for a database at the standard locations (like `/usr/share/zoneinfo`). ### `TZ` The `TZ` environment variable overrides and sets the default system time zone. It is [specified by POSIX][POSIX TZ]. Jiff implements the POSIX specification (even on non-POSIX platforms like Windows) with some common extensions. It is useful to set `TZ` when Jiff could not detect (or had a problem detecting) the system time zone, or if the system time zone is wrong in a specific circumstance. Summarizing POSIX (and common extensions supported by GNU libc and musl), the `TZ` environment variable accepts these kinds of values: * `America/New_York` sets the time zone via a IANA time zone identifier. * `/usr/share/zoneinfo/America/New_York` sets the time zone by providing a path to a TZif formatted file. * `EST5EDT,M3.2.0,M11.1.0` sets the time zone using a POSIX daylight saving time rule. The rule shown here is for `US/Eastern` at time of writing (2024). This is useful for specifying a custom time zone with generating TZif data, but is rarely used in practice. When `TZ` isn't set, then Jiff uses heuristics to detect the system's configured time zone. If this automatic detection fails, please first check for an [existing issue for your platform][issue-platform], and if one doesn't exist, please [file a new issue][issue-new]. Otherwise, setting `TZ` should be considered as a work-around. ### `ANDROID_ROOT` and `ANDROID_DATA` These environment variables are read to help determine the location of Android's [Concatenated Time Zone Database]. If `ANDROID_ROOT` is not defined, then Jiff uses `/system` as its default value. If `ANDROID_DATA` is not defined, then Jiff uses `/data/misc` as its default value. Note that these environment variables are not necessarily only read on Android, although they likely only make sense in the context of an Android environment. This is because Jiff's supported for the Concatenated Time Zone Database is platform independent. For example, Jiff will let users create a database from a Concatenated Time Zone Database file via the `TimeZoneDatabase::from_concatenated_path` API on _any_ platform. This is intended to enable maximum flexibility, and because there is no specific reason to make the Concatenated Time Zone Database format Android-specific. ## Platforms This section lists the platforms that Jiff has explicit support for. Support may not be perfect, so if something isn't working as it should, check the list of [existing platform related issues][issue-platform]. If you can't find one that matches your specific problem, [create a new issue][issue-new]. For each platform, there are generally three things to consider: 1. Whether getting the current time is supported. 2. How Jiff finds the IANA Time Zone Database. 3. How Jiff finds the system configured time zone. We answer these questions for each platform. ### Unix #### Current time All Unix platforms should be supported in terms of getting the current time. This support comes from Rust's standard library. #### IANA Time Zone Database The vast majority of Unix systems, including macOS, store a copy of the IANA time zone database at `/usr/share/zoneinfo`, which Jiff will automatically detect. If your Unix system uses a different directory, you may try to submit a PR adding support for it in Jiff proper, or just set the `TZDIR` environment variable. The existence of `/usr/share/zoneinfo` is not guaranteed in all Unix environments. For example, stripped down Docker containers might omit a full copy of the time zone database. Jiff will still work in such environments, but all IANA time zone identifier lookups will fail. To fix this, you can either install the IANA Time Zone Database into your environment, or you can enable the Jiff crate feature `tzdb-bundle-always`. This compile time setting will cause Jiff to depend on `jiff-tzdb`, which includes a complete copy of the IANA Time Zone Database embedded into the compiled artifact. Bundling the IANA Time Zone Database should only be done as a last resort. Especially on Unix systems, it is greatly preferred to use the system copy of the database, as the database is typically updated a few times each year. By using the system copy, Jiff will automatically pick up updates without needing to be recompiled. But if bundling is needed, it is a fine solution. It just means that Jiff will need to be re-compiled after `jiff-tzdb` is updated when a new IANA Time Zone Database release is published. #### System time zone On most Unix systems, the system configured time zone manifests as a symbolic link at `/etc/localtime`. The symbolic link usually points to a file in your system copy of the IANA Time Zone Database. For example, on my Linux system: ```text $ ls -l /etc/localtime lrwxrwxrwx 1 root root 36 Jul 15 20:26 /etc/localtime -> /usr/share/zoneinfo/America/New_York ``` And my macOS system: ```text $ ls -l /etc/localtime lrwxr-xr-x 1 root wheel 42 Jun 20 07:13 /etc/localtime -> /var/db/timezone/zoneinfo/America/New_York ``` Jiff examines the symbolic link metadata to extract the IANA time zone identifier from the file path. In the above two examples, that would be `America/New_York`. The identifier is then used to do a lookup in the system copy of the IANA Time Zone Database. If `/etc/localtime` is not a symbolic link, then Jiff reads it directly as a TZif file. When this happens, Jiff cannot feasibly know the IANA time zone identifier. While arithmetic on the resulting `Zoned` value will still be DST safe, one cannot losslessly serialize and deserialize it since Jiff won't be able to include the IANA time zone identifier in the serialized format. When such a `Zoned` value is serialized, the offset of the datetime will be used in lieu of the IANA time zone identifier. (NOTE: Not all Unix systems follow this pattern. If your system uses a different way to configure the system time zone, please check [available platform issues][issue-platform] for a related issue. If one doesn't exist, please [create a new issue][issue-new].) ### Android #### Current time All Android platforms should be supported in terms of getting the current time. This support comes from Rust's standard library. #### IANA Time Zone Database Unlike effectively every other Unix system, Android has its own special time zone database format. While it still makes use of TZif formatted data for defining time zone transitions themselves, it does not use the `zoneinfo` directory format (where there is one file per time zone). Instead, it _concatenates_ all time zone files into one single file. This is combined with some meta data that makes it quick to search for time zones by their IANA time zone identifier. This format is technically unnamed, but Jiff refers to it as the [Concatenated Time Zone Database] format. It has no formal specification. Jiff's implementation was done by inferring the format implemented by the Android Platform and also the implementation in [Go's standard library]. In practice this tends to work well, although there are obviously no guarantees. This is a practical trade-off given that there doesn't appear to be any obvious alternative. Moreover, others (such as Go, a project maintained by the same company that maintains Android) are already doing it, so it seems likely that if Android decides to make breaking changes to the format, they'll need to version it in some way to avoid breaking the ecosystem. Note that Jiff supports reading this format on all platforms, not just Android. For example, Jiff users can use the `TimeZoneDatabase::from_concatenated_path` API to create a `TimeZoneDatabase` from a concatenated `tzdata` file on any platform. If users of Jiff are uncomfortable relying on Android's "unstable" time zone database format, then there are three options available to them after disabling the `tzdb-concatenated` crate feature: * They can own the responsibility of putting a standard `zoneinfo` database installation into their environment. Then set the `TZDIR` environment variable to point at it, and Jiff will automatically use it. * Enable the `tzdb-bundle-always` crate feature. This will cause all time zone database to be compiled into your binary. Nothing else needs to be done. Jiff will automatically use the bundled copy. * Manually create `TimeZone` values via `TimeZone::tzif` from TZif formatted data. With this approach, you may need to change how you use Jiff in some cases. For example, any `in_tz` method will need to be changed to use the `to_zoned` equivalent. #### System time zone The system time zone on Android is discovered by reading the `persist.sys.timezone` property. Note that in addition to Android developers citing the [Concatenated Time Zone Database] format as unstable, they also discourage the discovery of the system time zone through properties as well. (See [chrono#1018] and [chrono#1148] for some discussion on this topic.) For Jiff at least, there is no feasible alternative. Apparently, the blessed API is to use their Java libraries, but that doesn't seem feasible to Jiff since I (Jiff's author) is unaware of a mechanism for easily calling Java code from Rust. The only option left is to use their `libc` APIs, which they did at least improve to make them thread safe, but this isn't enough for Jiff. For Jiff, we really want the actual IANA time zone identifier, and it isn't clear how to discover this from their `libc` APIs. Moreover, Jiff supports far more sophisticated operations on a time zone (like dealing with discontinuities in civil time) that cannot be implemented on top of `libc`-style APIs. Using Android's `libc` APIs for time handling would be a huge regression compared to all other platforms. It's worth noting that all other popular Unix systems provide at least some reliable means of both querying the time zone database _and_ discovering the system-wide IANA time zone identifier. Why Android is incapable of following the existing conventions for Unix systems is unclear. If users of Jiff are uncomfortable relying on Android's `persist.sys.timezone` property, then they should avoid APIs like `Zoned::now` and `TimeZone::system`. Instead, they can use `TimeZone::UTC`, which is what the fallback time zone would be when the system time zone cannot be discovered. ### Windows #### Current time All Windows platforms should be supported in terms of getting the current time. This support comes from Rust's standard library. #### IANA Time Zone Database Windows does not have a canonical installation of the IANA Time Zone Database like Unix. Because of this, and because of the importance of time zone support to Jiff's design, Jiff will automatically embed an entire copy of the IANA Time Zone Database into your binary on Windows. The automatic bundling is done via the Jiff crate feature `tzdb-bundle-platform`. This is a _target activated feature_. Namely, it is enabled by default, but only results in a bundled database on an enumerated set of platforms (where Windows is one of them). If you want to opt out of bundling the database on Windows, you'll need to disable this feature. Bundling the IANA Time Zone Database is not ideal, since after a new release of the database, you'll need to wait for the `jiff-tzdb` crate to be updated. Then you'll need to update your dependency version and re-compile your software to get the database updates. One alternative is to point Jiff to a copy of the IANA Time Zone Database via the `TZDIR` environment variable. Even on Windows, Jiff will attempt to read the directory specified as a time zone database. But you'll likely need to manage the database yourself. #### System time zone Jiff currently uses [`GetDynamicTimeZoneInformation`] from the Windows C API to query the current time zone information. This provides a value of type [`DYNAMIC_TIME_ZONE_INFORMATION`]. Jiff uses the `TimeZoneKeyName` member of that type to do a lookup in Unicode's [CLDR XML data] that maps Windows time zone names to IANA time zone identifiers. The resulting IANA time zone identifier is then used as a key to find a time zone in the configured IANA Time Zone Database. ### WASM There are a variety of WASM targets available for Rust that service different use cases. Here is a possibly incomplete list of those targets and a short imprecise blurb about them: * `wasm32-unknown-emscripten`: Sandboxed and emulates Unix as much as possible. * `wasm32-wasi` and `wasm32-wasip1`: Provides a sandbox with capability-based security. This is not typically used in web browsers. [wasmtime] is an example of a runtime that can run programs compiled for these targets. * `wasm{32,64}-unknown-unknown`: Typically used for web deployments to run in a browser via `wasm-pack`. But, crucially, not exclusively so. Jiff supports all of these targets, but the nature of that support varies. Each target is discussed in the sections below. #### The `js` crate feature Jiff comes with a `js` crate feature that is disabled by default. It is a _target activated feature_ that enables dependencies on the `js-sys` and `wasm-bindgen` crates. This feature is intended to be enabled only in binaries, tests and benchmarks when it is known that the code will be running in a web context. Consequently, this feature only activates this support for the `wasm{32,64}-unknown-unknown` targets. It has no effect on any other target, including other WASM targets. Library crates should generally never enable Jiff's `js` feature or even forward it. Applications using your library can depend on Jiff directly and enable the feature. #### Current time * `wasm32-unknown-emscripten`: Supported via Rust's standard library. * `wasm32-wasi*`: Supported via Rust's standard library. * `wasm{32,64}-unknown-unknown`: `std::time::SystemTime::now()`, and thus `Zoned::now()`, panics in Jiff's default configuration. Enabling Jiff's `js` feature will cause Jiff to assume a web context and use JavaScript's [`Date.now`] API to determine the current time. #### IANA Time Zone Database None of the WASM targets have a canonical installation of the IANA Time Zone Database. Because of this, and because of the important of time zone support to Jiff's design, Jiff will automatically embed an entire copy of the IANA Time Zone Database into your binary on all WASM targets. The automatic bundling is done via the Jiff crate feature `tzdb-bundle-platform`. This is a _target activated feature_. Namely, it is enabled by default, but only results in a bundled database on an enumerated set of platforms (where WASM is one of them). If you want to opt out of bundling the database on WASM targets, you'll need to disable this feature. Bundling the IANA Time Zone Database is not ideal, since after a new release of the database, you'll need to wait for the `jiff-tzdb` crate to be updated. Then you'll need to update your dependency version and re-compile your software to get the database updates. Some WASM targets, like `wasm32-wasip1`, can actually read the host's IANA Time Zone Database (e.g., on Unix), but this requires relaxing its sandbox restrictions so that the code can read system directories like `/usr/share/zoneinfo`. That is, it won't work out of the box. The same applies to the `wasm32-unknown-emscripten` target. (Although this author could not figure out how to relax emscripten's sandbox.) #### System time zone * `wasm32-unknown-emscripten`: Unsupported. * `wasm32-wasi*`: Unsupported. But you may set the `TZ` environment variable via your WASM runtime, and Jiff will respect it. For example, with [wasmtime], that's `--env TZ=America/New_York`. * `wasm{32,64}-unknown-unknown`: Unsupported in Jiff's default configuration. Enabling Jiff's `js` feature will cause Jiff to assume a web context and use JavaScript's [`Intl.DateTimeFormat`] API to determine the system configured IANA time zone identifier. This time zone identifier is then used to look up the time zone in Jiff's configured IANA Time Zone Database. [Civil time]: https://en.wikipedia.org/wiki/Civil_time [Time zone]: https://en.wikipedia.org/wiki/Time_zone [daylight saving time]: https://en.wikipedia.org/wiki/Daylight_saving_time [IANA Time Zone Database]: https://en.wikipedia.org/wiki/Tz_database [IANA time zone identifier]: https://data.iana.org/time-zones/theory.html#naming [RFC 8536]: https://datatracker.ietf.org/doc/html/rfc8536 [wasmtime]: https://wasmtime.dev/ [POSIX TZ]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html [RFC 3339]: https://www.rfc-editor.org/rfc/rfc3339 [RFC 9557]: https://www.rfc-editor.org/rfc/rfc9557.html [ISO 8601]: https://www.iso.org/iso-8601-date-and-time-format.html [Temporal]: https://tc39.es/proposal-temporal [issue-platform]: https://github.com/BurntSushi/jiff/labels/platform [issue-new]: https://github.com/BurntSushi/jiff/issues/new [`GetDynamicTimeZoneInformation`]: https://learn.microsoft.com/en-us/windows/win32/api/timezoneapi/nf-timezoneapi-getdynamictimezoneinformation [`DYNAMIC_TIME_ZONE_INFORMATION`]: https://learn.microsoft.com/en-us/windows/win32/api/timezoneapi/ns-timezoneapi-dynamic_time_zone_information [CLDR XML data]: https://github.com/unicode-org/cldr/raw/main/common/supplemental/windowsZones.xml [`Intl.DateTimeFormat`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/resolvedOptions#timezone [`Date.now`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now [Concatenated Time Zone Database]: https://android.googlesource.com/platform/libcore/+/jb-mr2-release/luni/src/main/java/libcore/util/ZoneInfoDB.java [Go's standard library]: https://github.com/golang/go/blob/19e923182e590ae6568c2c714f20f32512aeb3e3/src/time/zoneinfo_android.go [chrono#1018]: https://github.com/chronotope/chrono/pull/1018 [chrono#1148]: https://github.com/chronotope/chrono/pull/1148 jiff-0.1.28/README.md000064400000000000000000000164401046102023000121160ustar 00000000000000Jiff ==== Jiff is a datetime library for Rust that encourages you to jump into the pit of success. The focus of this library is providing high level datetime primitives that are difficult to misuse and have reasonable performance. Jiff supports automatic and seamless integration with the Time Zone Database, DST aware arithmetic and rounding, formatting and parsing zone aware datetimes losslessly, opt-in Serde support and a whole lot more. Jiff takes enormous inspiration from [Temporal], which is a [TC39] proposal to improve datetime handling in JavaScript. [![Build status](https://github.com/BurntSushi/jiff/workflows/ci/badge.svg)](https://github.com/BurntSushi/jiff/actions) [![Crates.io](https://img.shields.io/crates/v/jiff.svg)](https://crates.io/crates/jiff) [![Docs.rs](https://img.shields.io/docsrs/jiff)](https://docs.rs/jiff) Dual-licensed under MIT or the [UNLICENSE](https://unlicense.org/). [TC39]: https://tc39.es/ [Temporal]: https://tc39.es/proposal-temporal/docs/index.html ### Documentation * [API documentation on docs.rs](https://docs.rs/jiff) * [Comparison with `chrono`, `time`, `hifitime` and `icu`](COMPARE.md) * [The API design rationale for Jiff](DESIGN.md) * [Platform support](PLATFORM.md) * [CHANGELOG](CHANGELOG.md) ### Example Here is a quick example that shows how to parse a typical RFC 3339 instant, convert it to a zone aware datetime, add a span of time and losslessly print it: ```rust use jiff::{Timestamp, ToSpan}; fn main() -> Result<(), jiff::Error> { let time: Timestamp = "2024-07-11T01:14:00Z".parse()?; let zoned = time.in_tz("America/New_York")?.checked_add(1.month().hours(2))?; assert_eq!(zoned.to_string(), "2024-08-10T23:14:00-04:00[America/New_York]"); // Or, if you want an RFC3339 formatted string: assert_eq!(zoned.timestamp().to_string(), "2024-08-11T03:14:00Z"); Ok(()) } ``` There are many more examples in the [documentation](https://docs.rs/jiff). ### Usage Jiff is [on crates.io](https://crates.io/crates/jiff) and can be used by adding `jiff` to your dependencies in your project's `Cargo.toml`. Or more simply, just run `cargo add jiff`. Here is a complete example that creates a new Rust project, adds a dependency on `jiff`, creates the source code for a simple datetime program and then runs it. First, create the project in a new directory: ```text $ cargo new jiff-example $ cd jiff-example ``` Second, add a dependency on `jiff`: ```text $ cargo add jiff ``` Third, edit `src/main.rs`. Delete what's there and replace it with this: ```rust use jiff::{Unit, Zoned}; fn main() -> Result<(), jiff::Error> { let now = Zoned::now().round(Unit::Second)?; println!("{now}"); Ok(()) } ``` Fourth, run it with `cargo run`: ```text $ cargo run Compiling jiff v0.1.0 (/home/andrew/rust/jiff) Compiling jiff-play v0.1.0 (/home/andrew/tmp/scratch/rust/jiff-play) Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.37s Running `target/debug/jiff-play` 2024-07-10T19:54:20-04:00[America/New_York] ``` The first time you run the program will show more output like above. But subsequent runs shouldn't have to re-compile the dependencies. ### Crate features Jiff has several crate features for customizing support for Rust's standard library, `serde` support and whether to embed a copy of the Time Zone Database into your binary. The "[crate features](https://docs.rs/jiff/#crate-features)" section of the documentation lists the full set of supported features. ### Future plans My plan is to iterate on the Jiff API and make occasional breaking change releases over the next ~year. Assuming API breaking changes have settled down after about one year's time, my plan will be to release Jiff 1.0 and commit to the API for a long period of time. (Measured, at least, in years.) The purpose of this plan is to get Jiff to a 1.0 stable state as quickly as possible. The reason is so that others feel comfortable relying on Jiff as a public dependency that won't cause ecosystem churn. ### Performance The most important design goal of Jiff is to be a high level datetime library that makes it hard to do the wrong thing. Second to that is performance. Jiff should have reasonable performance, but there are likely areas in which it could improve. See the `bench` directory for benchmarks. ### Platform support The question of platform support in the context of datetime libraries comes up primarily in relation to time zone support. Specifically: * How should Jiff determine the time zone transitions for an IANA time zone identifier like `Antarctica/Troll`? * How should Jiff determine the default time zone for the current system? Both of these require some level of platform interaction. For discovering time zone transition data, Jiff relies on the [IANA Time Zone Database]. On Unix systems, this is usually found at `/usr/share/zoneinfo`, although it can be configured via the `TZDIR` environment variable (which Jiff respects). On Windows, Jiff will automatically embed a copy of the time zone database into the compiled library. For discovering the system time zone, Jiff reads `/etc/localtime` on Unix. On Windows, Jiff reads the Windows-specific time zone identifier via [`GetDynamicTimeZoneInformation`] and then maps it to an IANA time zone identifier via Unicode's [CLDR XML data]. I expect Jiff to grow more support for other platforms over time. Please file issues, although I will likely be reliant on contributor pull requests for more obscure platforms that aren't easy for me to test. For more on platform support, see [`PLATFORM.md`](PLATFORM.md). [IANA Time Zone Database]: https://en.wikipedia.org/wiki/Tz_database [`GetDynamicTimeZoneInformation`]: https://learn.microsoft.com/en-us/windows/win32/api/timezoneapi/nf-timezoneapi-getdynamictimezoneinformation [CLDR XML data]: https://github.com/unicode-org/cldr/raw/main/common/supplemental/windowsZones.xml ### Dependencies At time of writing, it is no accident that Jiff has zero dependencies on Unix. In general, my philosophy on adding new dependencies in an ecosystem crate like Jiff is very conservative. I consider there to be two primary use cases for adding new dependencies: 1. When a dependency is _practically_ required in order to interact with a platform. For example, `windows-sys` for discovering the system time zone on Windows. 2. When a dependency is necessary for inter-operability. For example, `serde`. But even here, I expect to be conservative, where I'm generally only willing to depend on things that have fewer breaking change releases than Jiff. A secondary use case for new dependencies is if Jiff gets split into multiple crates. I did a similar thing for the `regex` crate for very compelling reasons. It is possible that will happen with Jiff as well, although there are no plans for that. And in general, I expect the number of crates to stay small, if only to make keep maintenance lightweight. (Managing lots of semver API boundaries has a lot of overhead in my experience.) ### Minimum Rust version policy This crate's minimum supported `rustc` version is `1.70.0`. The policy is that the minimum Rust version required to use this crate can be increased in minor version updates. For example, if jiff 1.0 requires Rust 1.20.0, then jiff 1.0.z for all values of `z` will also require Rust 1.20.0 or newer. However, jiff 1.y for `y > 0` may require a newer minimum version of Rust. jiff-0.1.28/UNLICENSE000064400000000000000000000022731046102023000121060ustar 00000000000000This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to jiff-0.1.28/src/civil/date.rs000064400000000000000000004155131046102023000140230ustar 00000000000000use core::time::Duration as UnsignedDuration; use crate::{ civil::{DateTime, Era, ISOWeekDate, Time, Weekday}, duration::{Duration, SDuration}, error::{err, Error, ErrorContext}, fmt::{ self, temporal::{DEFAULT_DATETIME_PARSER, DEFAULT_DATETIME_PRINTER}, }, tz::TimeZone, util::{ common::{ days_in_month, days_in_month_unranged, is_leap_year, saturate_day_in_month, }, rangeint::{ri16, ri8, RFrom, RInto, TryRFrom}, t::{self, Constant, Day, Month, Sign, UnixEpochDays, Year, C}, }, RoundMode, SignedDuration, Span, SpanRound, Unit, Zoned, }; /// A representation of a civil date in the Gregorian calendar. /// /// A `Date` value corresponds to a triple of year, month and day. Every `Date` /// value is guaranteed to be a valid Gregorian calendar date. For example, /// both `2023-02-29` and `2023-11-31` are invalid and cannot be represented by /// a `Date`. /// /// # Civil dates /// /// A `Date` value behaves without regard to daylight saving time or time /// zones in general. When doing arithmetic on dates with spans defined in /// units of time (such as with [`Date::checked_add`]), days are considered to /// always be precisely `86,400` seconds long. /// /// # Parsing and printing /// /// The `Date` type provides convenient trait implementations of /// [`std::str::FromStr`] and [`std::fmt::Display`]: /// /// ``` /// use jiff::civil::Date; /// /// let date: Date = "2024-06-19".parse()?; /// assert_eq!(date.to_string(), "2024-06-19"); /// /// # Ok::<(), Box>(()) /// ``` /// /// A civil `Date` can also be parsed from something that _contains_ a date, /// but with perhaps other data (such as an offset or time zone): /// /// ``` /// use jiff::civil::Date; /// /// let date: Date = "2024-06-19T15:22:45-04[America/New_York]".parse()?; /// assert_eq!(date.to_string(), "2024-06-19"); /// /// # Ok::<(), Box>(()) /// ``` /// /// For more information on the specific format supported, see the /// [`fmt::temporal`](crate::fmt::temporal) module documentation. /// /// # Default value /// /// For convenience, this type implements the `Default` trait. Its default /// value corresponds to `0000-01-01`. One can also access this value via the /// `Date::ZERO` constant. /// /// # Comparisons /// /// The `Date` type provides both `Eq` and `Ord` trait implementations to /// facilitate easy comparisons. When a date `d1` occurs before a date `d2`, /// then `d1 < d2`. For example: /// /// ``` /// use jiff::civil::date; /// /// let d1 = date(2024, 3, 11); /// let d2 = date(2025, 1, 31); /// assert!(d1 < d2); /// ``` /// /// # Arithmetic /// /// This type provides routines for adding and subtracting spans of time, as /// well as computing the span of time between two `Date` values. /// /// For adding or subtracting spans of time, one can use any of the following /// routines: /// /// * [`Date::checked_add`] or [`Date::checked_sub`] for checked arithmetic. /// * [`Date::saturating_add`] or [`Date::saturating_sub`] for saturating /// arithmetic. /// /// Additionally, checked arithmetic is available via the `Add` and `Sub` /// trait implementations. When the result overflows, a panic occurs. /// /// ``` /// use jiff::{civil::date, ToSpan}; /// /// let start = date(2024, 2, 25); /// let one_week_later = start + 1.weeks(); /// assert_eq!(one_week_later, date(2024, 3, 3)); /// ``` /// /// One can compute the span of time between two dates using either /// [`Date::until`] or [`Date::since`]. It's also possible to subtract two /// `Date` values directly via a `Sub` trait implementation: /// /// ``` /// use jiff::{civil::date, ToSpan}; /// /// let date1 = date(2024, 3, 3); /// let date2 = date(2024, 2, 25); /// assert_eq!(date1 - date2, 7.days().fieldwise()); /// ``` /// /// The `until` and `since` APIs are polymorphic and allow re-balancing and /// rounding the span returned. For example, the default largest unit is days /// (as exemplified above), but we can ask for bigger units: /// /// ``` /// use jiff::{civil::date, ToSpan, Unit}; /// /// let date1 = date(2024, 5, 3); /// let date2 = date(2024, 2, 25); /// assert_eq!( /// date1.since((Unit::Year, date2))?, /// 2.months().days(7).fieldwise(), /// ); /// /// # Ok::<(), Box>(()) /// ``` /// /// Or even round the span returned: /// /// ``` /// use jiff::{civil::{DateDifference, date}, RoundMode, ToSpan, Unit}; /// /// let date1 = date(2024, 5, 15); /// let date2 = date(2024, 2, 25); /// assert_eq!( /// date1.since( /// DateDifference::new(date2) /// .smallest(Unit::Month) /// .largest(Unit::Year), /// )?, /// 2.months().fieldwise(), /// ); /// // `DateDifference` uses truncation as a rounding mode by default, /// // but you can set the rounding mode to break ties away from zero: /// assert_eq!( /// date1.since( /// DateDifference::new(date2) /// .smallest(Unit::Month) /// .largest(Unit::Year) /// .mode(RoundMode::HalfExpand), /// )?, /// // Rounds up to 8 days. /// 3.months().fieldwise(), /// ); /// /// # Ok::<(), Box>(()) /// ``` /// /// # Rounding /// /// Rounding dates is currently not supported. If you want this functionality, /// please participate in the [issue tracking its support][add-date-rounding]. /// /// [add-date-rounding]: https://github.com/BurntSushi/jiff/issues/1 #[derive(Clone, Copy, Hash)] pub struct Date { year: Year, month: Month, day: Day, } impl Date { /// The minimum representable Gregorian date. /// /// The minimum is chosen such that any [`Timestamp`](crate::Timestamp) /// combined with any valid time zone offset can be infallibly converted to /// this type. This means that the minimum `Timestamp` is guaranteed to be /// bigger than the minimum `Date`. pub const MIN: Date = Date::constant(-9999, 1, 1); /// The maximum representable Gregorian date. /// /// The maximum is chosen such that any [`Timestamp`](crate::Timestamp) /// combined with any valid time zone offset can be infallibly converted to /// this type. This means that the maximum `Timestamp` is guaranteed to be /// smaller than the maximum `Date`. pub const MAX: Date = Date::constant(9999, 12, 31); /// The first day of the zeroth year. /// /// This is guaranteed to be equivalent to `Date::default()`. /// /// # Example /// /// ``` /// use jiff::civil::Date; /// /// assert_eq!(Date::ZERO, Date::default()); /// ``` pub const ZERO: Date = Date::constant(0, 1, 1); /// Creates a new `Date` value from its component year, month and day /// values. /// /// To set the component values of a date after creating it, use /// [`DateWith`] via [`Date::with`] to build a new [`Date`] from the fields /// of an existing date. /// /// # Errors /// /// This returns an error when the given year-month-day does not /// correspond to a valid date. Namely, all of the following must be /// true: /// /// * The year must be in the range `-9999..=9999`. /// * The month must be in the range `1..=12`. /// * The day must be at least `1` and must be at most the number of days /// in the corresponding month. So for example, `2024-02-29` is valid but /// `2023-02-29` is not. /// /// # Example /// /// This shows an example of a valid date: /// /// ``` /// use jiff::civil::Date; /// /// let d = Date::new(2024, 2, 29).unwrap(); /// assert_eq!(d.year(), 2024); /// assert_eq!(d.month(), 2); /// assert_eq!(d.day(), 29); /// ``` /// /// This shows an example of an invalid date: /// /// ``` /// use jiff::civil::Date; /// /// assert!(Date::new(2023, 2, 29).is_err()); /// ``` #[inline] pub fn new(year: i16, month: i8, day: i8) -> Result { let year = Year::try_new("year", year)?; let month = Month::try_new("month", month)?; let day = Day::try_new("day", day)?; Date::new_ranged(year, month, day) } /// Creates a new `Date` value in a `const` context. /// /// # Panics /// /// This routine panics when [`Date::new`] would return an error. That is, /// when the given year-month-day does not correspond to a valid date. /// Namely, all of the following must be true: /// /// * The year must be in the range `-9999..=9999`. /// * The month must be in the range `1..=12`. /// * The day must be at least `1` and must be at most the number of days /// in the corresponding month. So for example, `2024-02-29` is valid but /// `2023-02-29` is not. /// /// # Example /// /// ``` /// use jiff::civil::Date; /// /// let d = Date::constant(2024, 2, 29); /// assert_eq!(d.year(), 2024); /// assert_eq!(d.month(), 2); /// assert_eq!(d.day(), 29); /// ``` #[inline] pub const fn constant(year: i16, month: i8, day: i8) -> Date { if !Year::contains(year) { panic!("invalid year"); } if !Month::contains(month) { panic!("invalid month"); } if day > days_in_month_unranged(year, month) { panic!("invalid day"); } let year = Year::new_unchecked(year); let month = Month::new_unchecked(month); let day = Day::new_unchecked(day); Date { year, month, day } } /// Construct a Gregorian date from an [ISO 8601 week date]. /// /// The [`ISOWeekDate`] type describes itself in more detail, but in /// breif, the ISO week date calendar system eschews months in favor of /// weeks. /// /// The minimum and maximum values of an `ISOWeekDate` correspond /// precisely to the minimum and maximum values of a `Date`. Therefore, /// converting between them is lossless and infallible. /// /// This routine is equivalent to [`ISOWeekDate::to_date`]. It is also /// available via a `From` trait implementation for `Date`. /// /// [ISO 8601 week date]: https://en.wikipedia.org/wiki/ISO_week_date /// /// # Example /// /// This shows a number of examples demonstrating the conversion from an /// ISO 8601 week date to a Gregorian date. /// /// ``` /// use jiff::civil::{Date, ISOWeekDate, Weekday, date}; /// /// let weekdate = ISOWeekDate::new(1994, 52, Weekday::Sunday).unwrap(); /// let d = Date::from_iso_week_date(weekdate); /// assert_eq!(d, date(1995, 1, 1)); /// /// let weekdate = ISOWeekDate::new(1997, 1, Weekday::Tuesday).unwrap(); /// let d = Date::from_iso_week_date(weekdate); /// assert_eq!(d, date(1996, 12, 31)); /// /// let weekdate = ISOWeekDate::new(2020, 1, Weekday::Monday).unwrap(); /// let d = Date::from_iso_week_date(weekdate); /// assert_eq!(d, date(2019, 12, 30)); /// /// let weekdate = ISOWeekDate::new(2024, 10, Weekday::Saturday).unwrap(); /// let d = Date::from_iso_week_date(weekdate); /// assert_eq!(d, date(2024, 3, 9)); /// /// let weekdate = ISOWeekDate::new(9999, 52, Weekday::Friday).unwrap(); /// let d = Date::from_iso_week_date(weekdate); /// assert_eq!(d, date(9999, 12, 31)); /// ``` #[inline] pub fn from_iso_week_date(weekdate: ISOWeekDate) -> Date { let mut days = iso_week_start_from_year(weekdate.year_ranged()); let year = t::NoUnits16::rfrom(weekdate.year_ranged()); let week = t::NoUnits16::rfrom(weekdate.week_ranged()); let weekday = t::NoUnits16::rfrom( weekdate.weekday().to_monday_zero_offset_ranged(), ); let [week, weekday] = t::NoUnits16::vary_many( [year, week, weekday], |[year, week, weekday]| { // This is weird, but because the max ISO week date is actually // 9999-W52-4, we need to explicitly cap our maximum computed // values here. This is only required because the maximums of // each component of an ISO week date combine to represent an // out-of-bounds Gregorian date. // // Note that this is purely done at the service of ranged // integers. Otherwise, our ranged integers will compute a // max value bigger than what can really occur, and then panic. // So we use these caps to say, "no range integer, it truly // won't exceed 9999-W52-4." if year == 9999 { if week >= 52 { [week.min(C(52)), weekday.min(C(4))] } else { [week, weekday] } } else { [week, weekday] } }, ); days += (UnixEpochDays::rfrom(week) - C(1)) * C(7); days += weekday; Date::from_unix_epoch_days(days) } /// Create a builder for constructing a `Date` from the fields of this /// date. /// /// See the methods on [`DateWith`] for the different ways one can set the /// fields of a new `Date`. /// /// # Example /// /// The builder ensures one can chain together the individual components /// of a date without it failing at an intermediate step. For example, /// if you had a date of `2024-10-31` and wanted to change both the day /// and the month, and each setting was validated independent of the other, /// you would need to be careful to set the day first and then the month. /// In some cases, you would need to set the month first and then the day! /// /// But with the builder, you can set values in any order: /// /// ``` /// use jiff::civil::date; /// /// let d1 = date(2024, 10, 31); /// let d2 = d1.with().month(11).day(30).build()?; /// assert_eq!(d2, date(2024, 11, 30)); /// /// let d1 = date(2024, 4, 30); /// let d2 = d1.with().day(31).month(7).build()?; /// assert_eq!(d2, date(2024, 7, 31)); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn with(self) -> DateWith { DateWith::new(self) } /// Returns the year for this date. /// /// The value returned is guaranteed to be in the range `-9999..=9999`. /// /// # Example /// /// ``` /// use jiff::civil::date; /// /// let d1 = date(2024, 3, 9); /// assert_eq!(d1.year(), 2024); /// /// let d2 = date(-2024, 3, 9); /// assert_eq!(d2.year(), -2024); /// /// let d3 = date(0, 3, 9); /// assert_eq!(d3.year(), 0); /// ``` #[inline] pub fn year(self) -> i16 { self.year_ranged().get() } /// Returns the year and its era. /// /// This crate specifically allows years to be negative or `0`, where as /// years written for the Gregorian calendar are always positive and /// greater than `0`. In the Gregorian calendar, the era labels `BCE` and /// `CE` are used to disambiguate between years less than or equal to `0` /// and years greater than `0`, respectively. /// /// The crate is designed this way so that years in the latest era (that /// is, `CE`) are aligned with years in this crate. /// /// The year returned is guaranteed to be in the range `1..=10000`. /// /// # Example /// /// ``` /// use jiff::civil::{Era, date}; /// /// let d = date(2024, 10, 3); /// assert_eq!(d.era_year(), (2024, Era::CE)); /// /// let d = date(1, 10, 3); /// assert_eq!(d.era_year(), (1, Era::CE)); /// /// let d = date(0, 10, 3); /// assert_eq!(d.era_year(), (1, Era::BCE)); /// /// let d = date(-1, 10, 3); /// assert_eq!(d.era_year(), (2, Era::BCE)); /// /// let d = date(-10, 10, 3); /// assert_eq!(d.era_year(), (11, Era::BCE)); /// /// let d = date(-9_999, 10, 3); /// assert_eq!(d.era_year(), (10_000, Era::BCE)); /// ``` #[inline] pub fn era_year(self) -> (i16, Era) { let year = self.year_ranged(); if year >= 1 { (year.get(), Era::CE) } else { // We specifically ensure our min/max bounds on `Year` always leave // room in its representation to add or subtract 1, so this will // never fail. let year = -t::YearBCE::rfrom(year.min(C(0))); let era_year = year + C(1); (era_year.get(), Era::BCE) } } /// Returns the month for this date. /// /// The value returned is guaranteed to be in the range `1..=12`. /// /// # Example /// /// ``` /// use jiff::civil::date; /// /// let d1 = date(2024, 3, 9); /// assert_eq!(d1.month(), 3); /// ``` #[inline] pub fn month(self) -> i8 { self.month_ranged().get() } /// Returns the day for this date. /// /// The value returned is guaranteed to be in the range `1..=31`. /// /// # Example /// /// ``` /// use jiff::civil::date; /// /// let d1 = date(2024, 2, 29); /// assert_eq!(d1.day(), 29); /// ``` #[inline] pub fn day(self) -> i8 { self.day_ranged().get() } /// Returns the weekday corresponding to this date. /// /// # Example /// /// ``` /// use jiff::civil::{Weekday, date}; /// /// // The Unix epoch was on a Thursday. /// let d1 = date(1970, 1, 1); /// assert_eq!(d1.weekday(), Weekday::Thursday); /// // One can also get the weekday as an offset in a variety of schemes. /// assert_eq!(d1.weekday().to_monday_zero_offset(), 3); /// assert_eq!(d1.weekday().to_monday_one_offset(), 4); /// assert_eq!(d1.weekday().to_sunday_zero_offset(), 4); /// assert_eq!(d1.weekday().to_sunday_one_offset(), 5); /// ``` #[inline] pub fn weekday(self) -> Weekday { weekday_from_unix_epoch_days(self.to_unix_epoch_days()) } /// Returns the ordinal day of the year that this date resides in. /// /// For leap years, this always returns a value in the range `1..=366`. /// Otherwise, the value is in the range `1..=365`. /// /// # Example /// /// ``` /// use jiff::civil::date; /// /// let d = date(2006, 8, 24); /// assert_eq!(d.day_of_year(), 236); /// /// let d = date(2023, 12, 31); /// assert_eq!(d.day_of_year(), 365); /// /// let d = date(2024, 12, 31); /// assert_eq!(d.day_of_year(), 366); /// ``` #[inline] pub fn day_of_year(self) -> i16 { let days = C(1) + self.since_days_ranged(self.first_of_year()); t::DayOfYear::rfrom(days).get() } /// Returns the ordinal day of the year that this date resides in, but /// ignores leap years. /// /// That is, the range of possible values returned by this routine is /// `1..=365`, even if this date resides in a leap year. If this date is /// February 29, then this routine returns `None`. /// /// The value `365` always corresponds to the last day in the year, /// December 31, even for leap years. /// /// # Example /// /// ``` /// use jiff::civil::date; /// /// let d = date(2006, 8, 24); /// assert_eq!(d.day_of_year_no_leap(), Some(236)); /// /// let d = date(2023, 12, 31); /// assert_eq!(d.day_of_year_no_leap(), Some(365)); /// /// let d = date(2024, 12, 31); /// assert_eq!(d.day_of_year_no_leap(), Some(365)); /// /// let d = date(2024, 2, 29); /// assert_eq!(d.day_of_year_no_leap(), None); /// ``` #[inline] pub fn day_of_year_no_leap(self) -> Option { let mut days = self.day_of_year(); if self.in_leap_year() { // day=60 is Feb 29 if days == 60 { return None; } else if days > 60 { days -= 1; } } Some(days) } /// Returns the first date of the month that this date resides in. /// /// # Example /// /// ``` /// use jiff::civil::date; /// /// let d = date(2024, 2, 29); /// assert_eq!(d.first_of_month(), date(2024, 2, 1)); /// ``` #[inline] pub fn first_of_month(self) -> Date { Date::new_ranged(self.year_ranged(), self.month_ranged(), C(1)) .expect("first day of month is always valid") } /// Returns the last date of the month that this date resides in. /// /// # Example /// /// ``` /// use jiff::civil::date; /// /// let d = date(2024, 2, 5); /// assert_eq!(d.last_of_month(), date(2024, 2, 29)); /// ``` #[inline] pub fn last_of_month(self) -> Date { let max_day = self.days_in_month_ranged(); Date::new_ranged(self.year_ranged(), self.month_ranged(), max_day) .expect("last day of month is always valid") } /// Returns the total number of days in the the month in which this date /// resides. /// /// This is guaranteed to always return one of the following values, /// depending on the year and the month: 28, 29, 30 or 31. /// /// # Example /// /// ``` /// use jiff::civil::date; /// /// let d = date(2024, 2, 10); /// assert_eq!(d.days_in_month(), 29); /// /// let d = date(2023, 2, 10); /// assert_eq!(d.days_in_month(), 28); /// /// let d = date(2024, 8, 15); /// assert_eq!(d.days_in_month(), 31); /// ``` #[inline] pub fn days_in_month(self) -> i8 { self.days_in_month_ranged().get() } /// Returns the first date of the year that this date resides in. /// /// # Example /// /// ``` /// use jiff::civil::date; /// /// let d = date(2024, 2, 29); /// assert_eq!(d.first_of_year(), date(2024, 1, 1)); /// ``` #[inline] pub fn first_of_year(self) -> Date { Date::new_ranged(self.year_ranged(), C(1), C(1)) .expect("first day of year is always valid") } /// Returns the last date of the year that this date resides in. /// /// # Example /// /// ``` /// use jiff::civil::date; /// /// let d = date(2024, 2, 5); /// assert_eq!(d.last_of_year(), date(2024, 12, 31)); /// ``` #[inline] pub fn last_of_year(self) -> Date { Date::new_ranged(self.year_ranged(), C(12), C(31)) .expect("last day of year is always valid") } /// Returns the total number of days in the the year in which this date /// resides. /// /// This is guaranteed to always return either `365` or `366`. /// /// # Example /// /// ``` /// use jiff::civil::date; /// /// let d = date(2024, 7, 10); /// assert_eq!(d.days_in_year(), 366); /// /// let d = date(2023, 7, 10); /// assert_eq!(d.days_in_year(), 365); /// ``` #[inline] pub fn days_in_year(self) -> i16 { if self.in_leap_year() { 366 } else { 365 } } /// Returns true if and only if the year in which this date resides is a /// leap year. /// /// # Example /// /// ``` /// use jiff::civil::date; /// /// assert!(date(2024, 1, 1).in_leap_year()); /// assert!(!date(2023, 12, 31).in_leap_year()); /// ``` #[inline] pub fn in_leap_year(self) -> bool { is_leap_year(self.year_ranged()) } /// Returns the date immediately following this one. /// /// # Errors /// /// This returns an error when this date is the maximum value. /// /// # Example /// /// ``` /// use jiff::civil::{Date, date}; /// /// let d = date(2024, 2, 28); /// assert_eq!(d.tomorrow()?, date(2024, 2, 29)); /// /// // The max doesn't have a tomorrow. /// assert!(Date::MAX.tomorrow().is_err()); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn tomorrow(self) -> Result { let day = self.to_unix_epoch_days(); let next = day.try_checked_add("days", C(1))?; Ok(Date::from_unix_epoch_days(next)) } /// Returns the date immediately preceding this one. /// /// # Errors /// /// This returns an error when this date is the minimum value. /// /// # Example /// /// ``` /// use jiff::civil::{Date, date}; /// /// let d = date(2024, 3, 1); /// assert_eq!(d.yesterday()?, date(2024, 2, 29)); /// /// // The min doesn't have a yesterday. /// assert!(Date::MIN.yesterday().is_err()); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn yesterday(self) -> Result { let day = self.to_unix_epoch_days(); let next = day.try_checked_sub("days", C(1))?; Ok(Date::from_unix_epoch_days(next)) } /// Returns the "nth" weekday from the beginning or end of the month in /// which this date resides. /// /// The `nth` parameter can be positive or negative. A positive value /// computes the "nth" weekday from the beginning of the month. A negative /// value computes the "nth" weekday from the end of the month. So for /// example, use `-1` to "find the last weekday" in this date's month. /// /// # Errors /// /// This returns an error when `nth` is `0`, or if it is `5` or `-5` and /// there is no 5th weekday from the beginning or end of the month. /// /// # Example /// /// This shows how to get the nth weekday in a month, starting from the /// beginning of the month: /// /// ``` /// use jiff::civil::{Weekday, date}; /// /// let month = date(2017, 3, 1); /// let second_friday = month.nth_weekday_of_month(2, Weekday::Friday)?; /// assert_eq!(second_friday, date(2017, 3, 10)); /// /// # Ok::<(), Box>(()) /// ``` /// /// This shows how to do the reverse of the above. That is, the nth _last_ /// weekday in a month: /// /// ``` /// use jiff::civil::{Weekday, date}; /// /// let month = date(2024, 3, 1); /// let last_thursday = month.nth_weekday_of_month(-1, Weekday::Thursday)?; /// assert_eq!(last_thursday, date(2024, 3, 28)); /// let second_last_thursday = month.nth_weekday_of_month( /// -2, /// Weekday::Thursday, /// )?; /// assert_eq!(second_last_thursday, date(2024, 3, 21)); /// /// # Ok::<(), Box>(()) /// ``` /// /// This routine can return an error if there isn't an `nth` weekday /// for this month. For example, March 2024 only has 4 Mondays: /// /// ``` /// use jiff::civil::{Weekday, date}; /// /// let month = date(2024, 3, 25); /// let fourth_monday = month.nth_weekday_of_month(4, Weekday::Monday)?; /// assert_eq!(fourth_monday, date(2024, 3, 25)); /// // There is no 5th Monday. /// assert!(month.nth_weekday_of_month(5, Weekday::Monday).is_err()); /// // Same goes for counting backwards. /// assert!(month.nth_weekday_of_month(-5, Weekday::Monday).is_err()); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn nth_weekday_of_month( self, nth: i8, weekday: Weekday, ) -> Result { type Nth = ri8<-5, 5>; let nth = Nth::try_new("nth", nth)?; if nth == 0 { Err(err!("nth weekday of month cannot be `0`")) } else if nth > 0 { let nth = nth.max(C(1)); let first_weekday = self.first_of_month().weekday(); let diff = weekday.since_ranged(first_weekday); let day = Day::rfrom(diff) + C(1) + (nth - C(1)) * C(7); Date::new_ranged(self.year_ranged(), self.month_ranged(), day) } else { let nth = nth.min(C(-1)); let last = self.last_of_month(); let last_weekday = last.weekday(); let diff = last_weekday.since_ranged(weekday); let day = last.day_ranged() - Day::rfrom(diff) - (nth.abs() - C(1)) * C(7); // Our math can go below 1 when nth is -5 and there is no "5th from // last" weekday in this month. Since this is outside the bounds // of `Day`, we can't let this boundary condition escape. So we // check it here. if day < 1 { return Err(day.to_error_with_bounds( "day", 1, self.days_in_month(), )); } Date::new_ranged(self.year_ranged(), self.month_ranged(), day) } } /// Returns the "nth" weekday from this date, not including itself. /// /// The `nth` parameter can be positive or negative. A positive value /// computes the "nth" weekday starting at the day after this date and /// going forwards in time. A negative value computes the "nth" weekday /// starting at the day before this date and going backwards in time. /// /// For example, if this date's weekday is a Sunday and the first Sunday is /// asked for (that is, `date.nth_weekday(1, Weekday::Sunday)`), then the /// result is a week from this date corresponding to the following Sunday. /// /// # Errors /// /// This returns an error when `nth` is `0`, or if it would otherwise /// result in a date that overflows the minimum/maximum values of `Date`. /// /// # Example /// /// This example shows how to find the "nth" weekday going forwards in /// time: /// /// ``` /// use jiff::civil::{Weekday, date}; /// /// // Use a Sunday in March as our start date. /// let d = date(2024, 3, 10); /// assert_eq!(d.weekday(), Weekday::Sunday); /// /// // The first next Monday is tomorrow! /// let next_monday = d.nth_weekday(1, Weekday::Monday)?; /// assert_eq!(next_monday, date(2024, 3, 11)); /// /// // But the next Sunday is a week away, because this doesn't /// // include the current weekday. /// let next_sunday = d.nth_weekday(1, Weekday::Sunday)?; /// assert_eq!(next_sunday, date(2024, 3, 17)); /// /// // "not this Thursday, but next Thursday" /// let next_next_thursday = d.nth_weekday(2, Weekday::Thursday)?; /// assert_eq!(next_next_thursday, date(2024, 3, 21)); /// /// # Ok::<(), Box>(()) /// ``` /// /// This example shows how to find the "nth" weekday going backwards in /// time: /// /// ``` /// use jiff::civil::{Weekday, date}; /// /// // Use a Sunday in March as our start date. /// let d = date(2024, 3, 10); /// assert_eq!(d.weekday(), Weekday::Sunday); /// /// // "last Saturday" was yesterday! /// let last_saturday = d.nth_weekday(-1, Weekday::Saturday)?; /// assert_eq!(last_saturday, date(2024, 3, 9)); /// /// // "last Sunday" was a week ago. /// let last_sunday = d.nth_weekday(-1, Weekday::Sunday)?; /// assert_eq!(last_sunday, date(2024, 3, 3)); /// /// // "not last Thursday, but the one before" /// let prev_prev_thursday = d.nth_weekday(-2, Weekday::Thursday)?; /// assert_eq!(prev_prev_thursday, date(2024, 2, 29)); /// /// # Ok::<(), Box>(()) /// ``` /// /// This example shows that overflow results in an error in either /// direction: /// /// ``` /// use jiff::civil::{Date, Weekday}; /// /// let d = Date::MAX; /// assert_eq!(d.weekday(), Weekday::Friday); /// assert!(d.nth_weekday(1, Weekday::Saturday).is_err()); /// /// let d = Date::MIN; /// assert_eq!(d.weekday(), Weekday::Monday); /// assert!(d.nth_weekday(-1, Weekday::Sunday).is_err()); /// ``` /// /// # Example: the start of Israeli summer time /// /// Israeli law says (at present, as of 2024-03-11) that DST or "summer /// time" starts on the Friday before the last Sunday in March. We can find /// that date using both `nth_weekday` and [`Date::nth_weekday_of_month`]: /// /// ``` /// use jiff::civil::{Weekday, date}; /// /// let march = date(2024, 3, 1); /// let last_sunday = march.nth_weekday_of_month(-1, Weekday::Sunday)?; /// let dst_starts_on = last_sunday.nth_weekday(-1, Weekday::Friday)?; /// assert_eq!(dst_starts_on, date(2024, 3, 29)); /// /// # Ok::<(), Box>(()) /// ``` /// /// # Example: getting the start of the week /// /// Given a date, one can use `nth_weekday` to determine the start of the /// week in which the date resides in. This might vary based on whether /// the weeks start on Sunday or Monday. This example shows how to handle /// both. /// /// ``` /// use jiff::civil::{Weekday, date}; /// /// let d = date(2024, 3, 15); /// // For weeks starting with Sunday. /// let start_of_week = d.tomorrow()?.nth_weekday(-1, Weekday::Sunday)?; /// assert_eq!(start_of_week, date(2024, 3, 10)); /// // For weeks starting with Monday. /// let start_of_week = d.tomorrow()?.nth_weekday(-1, Weekday::Monday)?; /// assert_eq!(start_of_week, date(2024, 3, 11)); /// /// # Ok::<(), Box>(()) /// ``` /// /// In the above example, we first get the date after the current one /// because `nth_weekday` does not consider itself when counting. This /// works as expected even at the boundaries of a week: /// /// ``` /// use jiff::civil::{Weekday, date}; /// /// // The start of the week. /// let d = date(2024, 3, 10); /// let start_of_week = d.tomorrow()?.nth_weekday(-1, Weekday::Sunday)?; /// assert_eq!(start_of_week, date(2024, 3, 10)); /// // The end of the week. /// let d = date(2024, 3, 16); /// let start_of_week = d.tomorrow()?.nth_weekday(-1, Weekday::Sunday)?; /// assert_eq!(start_of_week, date(2024, 3, 10)); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn nth_weekday( self, nth: i32, weekday: Weekday, ) -> Result { // ref: http://howardhinnant.github.io/date_algorithms.html#next_weekday let nth = t::SpanWeeks::try_new("nth weekday", nth)?; if nth == 0 { Err(err!("nth weekday cannot be `0`")) } else if nth > 0 { let nth = nth.max(C(1)); let weekday_diff = weekday.since_ranged(self.weekday().next()); let diff = (nth - C(1)) * C(7) + weekday_diff; let start = self.tomorrow()?.to_unix_epoch_days(); let end = start.try_checked_add("days", diff)?; Ok(Date::from_unix_epoch_days(end)) } else { let nth: t::SpanWeeks = nth.min(C(-1)).abs(); let weekday_diff = self.weekday().previous().since_ranged(weekday); let diff = (nth - C(1)) * C(7) + weekday_diff; let start = self.yesterday()?.to_unix_epoch_days(); let end = start.try_checked_sub("days", diff)?; Ok(Date::from_unix_epoch_days(end)) } } /// Construct an [ISO 8601 week date] from this Gregorian date. /// /// The [`ISOWeekDate`] type describes itself in more detail, but in /// brief, the ISO week date calendar system eschews months in favor of /// weeks. /// /// The minimum and maximum values of an `ISOWeekDate` correspond /// precisely to the minimum and maximum values of a `Date`. Therefore, /// converting between them is lossless and infallible. /// /// This routine is equivalent to [`ISOWeekDate::from_date`]. /// /// [ISO 8601 week date]: https://en.wikipedia.org/wiki/ISO_week_date /// /// # Example /// /// This shows a number of examples demonstrating the conversion from a /// Gregorian date to an ISO 8601 week date: /// /// ``` /// use jiff::civil::{Date, Weekday, date}; /// /// let weekdate = date(1995, 1, 1).iso_week_date(); /// assert_eq!(weekdate.year(), 1994); /// assert_eq!(weekdate.week(), 52); /// assert_eq!(weekdate.weekday(), Weekday::Sunday); /// /// let weekdate = date(1996, 12, 31).iso_week_date(); /// assert_eq!(weekdate.year(), 1997); /// assert_eq!(weekdate.week(), 1); /// assert_eq!(weekdate.weekday(), Weekday::Tuesday); /// /// let weekdate = date(2019, 12, 30).iso_week_date(); /// assert_eq!(weekdate.year(), 2020); /// assert_eq!(weekdate.week(), 1); /// assert_eq!(weekdate.weekday(), Weekday::Monday); /// /// let weekdate = date(2024, 3, 9).iso_week_date(); /// assert_eq!(weekdate.year(), 2024); /// assert_eq!(weekdate.week(), 10); /// assert_eq!(weekdate.weekday(), Weekday::Saturday); /// /// let weekdate = Date::MIN.iso_week_date(); /// assert_eq!(weekdate.year(), -9999); /// assert_eq!(weekdate.week(), 1); /// assert_eq!(weekdate.weekday(), Weekday::Monday); /// /// let weekdate = Date::MAX.iso_week_date(); /// assert_eq!(weekdate.year(), 9999); /// assert_eq!(weekdate.week(), 52); /// assert_eq!(weekdate.weekday(), Weekday::Friday); /// ``` #[inline] pub fn iso_week_date(self) -> ISOWeekDate { let days = t::NoUnits32::rfrom(self.to_unix_epoch_days()); let year = t::NoUnits32::rfrom(self.year_ranged()); let week_start = t::NoUnits32::vary([days, year], |[days, year]| { let mut week_start = t::NoUnits32::rfrom(iso_week_start_from_year(year)); if days < week_start { week_start = t::NoUnits32::rfrom(iso_week_start_from_year(year - C(1))); } else { let next_year_week_start = t::NoUnits32::rfrom(iso_week_start_from_year(year + C(1))); if days >= next_year_week_start { week_start = next_year_week_start; } } week_start }); let weekday = weekday_from_unix_epoch_days(days); let week = ((days - week_start) / C(7)) + C(1); let year = Date::from_unix_epoch_days( week_start + t::NoUnits32::rfrom( Weekday::Thursday.since_ranged(Weekday::Monday), ), ) .year_ranged(); ISOWeekDate::new_ranged(year, week, weekday) .expect("all Dates infallibly convert to ISOWeekDates") } /// Converts a civil date to a [`Zoned`] datetime by adding the given /// time zone and setting the clock time to midnight. /// /// This is a convenience function for /// `date.to_datetime(midnight).in_tz(name)`. See [`DateTime::to_zoned`] /// for more details. Note that ambiguous datetimes are handled in the /// same way as `DateTime::to_zoned`. /// /// # Errors /// /// This returns an error when the given time zone name could not be found /// in the default time zone database. /// /// This also returns an error if this date could not be represented as /// a timestamp. This can occur in some cases near the minimum and maximum /// boundaries of a `Date`. /// /// # Example /// /// This is a simple example of converting a civil date (a "wall" or /// "local" or "naive" date) to a precise instant in time that is aware of /// its time zone: /// /// ``` /// use jiff::civil::date; /// /// let zdt = date(2024, 6, 20).in_tz("America/New_York")?; /// assert_eq!(zdt.to_string(), "2024-06-20T00:00:00-04:00[America/New_York]"); /// /// # Ok::<(), Box>(()) /// ``` /// /// # Example: dealing with ambiguity /// /// Since a [`Zoned`] corresponds to a precise instant in time (to /// nanosecond precision) and a `Date` can be many possible such instants, /// this routine chooses one for this date: the first one, or midnight. /// /// Interestingly, some regions implement their daylight saving time /// transitions at midnight. This means there are some places in the world /// where, once a year, midnight does not exist on their clocks. As a /// result, it's possible for the datetime string representing a [`Zoned`] /// to be something other than midnight. For example: /// /// ``` /// use jiff::civil::date; /// /// let zdt = date(2024, 3, 10).in_tz("Cuba")?; /// assert_eq!(zdt.to_string(), "2024-03-10T01:00:00-04:00[Cuba]"); /// /// # Ok::<(), Box>(()) /// ``` /// /// Since this uses /// [`Disambiguation::Compatible`](crate::tz::Disambiguation::Compatible), /// and since that also chooses the "later" time in a forward transition, /// it follows that the date of the returned `Zoned` will always match /// this civil date. (Unless there is a pathological time zone with a 24+ /// hour transition forward.) /// /// But if a different disambiguation strategy is used, even when only /// dealing with standard one hour transitions, the date returned can be /// different: /// /// ``` /// use jiff::{civil::date, tz::TimeZone}; /// /// let tz = TimeZone::get("Cuba")?; /// let dt = date(2024, 3, 10).at(0, 0, 0, 0); /// let zdt = tz.to_ambiguous_zoned(dt).earlier()?; /// assert_eq!(zdt.to_string(), "2024-03-09T23:00:00-05:00[Cuba]"); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn in_tz(self, time_zone_name: &str) -> Result { let tz = crate::tz::db().get(time_zone_name)?; self.to_zoned(tz) } /// Converts a civil datetime to a [`Zoned`] datetime by adding the given /// [`TimeZone`] and setting the clock time to midnight. /// /// This is a convenience function for /// `date.to_datetime(midnight).to_zoned(tz)`. See [`DateTime::to_zoned`] /// for more details. Note that ambiguous datetimes are handled in the same /// way as `DateTime::to_zoned`. /// /// In the common case of a time zone being represented as a name string, /// like `Australia/Tasmania`, consider using [`Date::in_tz`] /// instead. /// /// # Errors /// /// This returns an error if this date could not be represented as a /// timestamp. This can occur in some cases near the minimum and maximum /// boundaries of a `Date`. /// /// # Example /// /// This example shows how to create a zoned value with a fixed time zone /// offset: /// /// ``` /// use jiff::{civil::date, tz}; /// /// let tz = tz::offset(-4).to_time_zone(); /// let zdt = date(2024, 6, 20).to_zoned(tz)?; /// // A time zone annotation is still included in the printable version /// // of the Zoned value, but it is fixed to a particular offset. /// assert_eq!(zdt.to_string(), "2024-06-20T00:00:00-04:00[-04:00]"); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn to_zoned(self, tz: TimeZone) -> Result { DateTime::from(self).to_zoned(tz) } /// Given a [`Time`], this constructs a [`DateTime`] value with its time /// component equal to this time. /// /// This is a convenience function for [`DateTime::from_parts`]. /// /// # Example /// /// ``` /// use jiff::civil::{DateTime, date, time}; /// /// let date = date(2010, 3, 14); /// let time = time(2, 30, 0, 0); /// assert_eq!(DateTime::from_parts(date, time), date.to_datetime(time)); /// ``` #[inline] pub const fn to_datetime(self, time: Time) -> DateTime { DateTime::from_parts(self, time) } /// A convenience function for constructing a [`DateTime`] from this date /// at the time given by its components. /// /// # Example /// /// ``` /// use jiff::civil::date; /// /// assert_eq!( /// date(2010, 3, 14).at(2, 30, 0, 0).to_string(), /// "2010-03-14T02:30:00", /// ); /// ``` /// /// One can also flip the order by making use of [`Time::on`]: /// /// ``` /// use jiff::civil::time; /// /// assert_eq!( /// time(2, 30, 0, 0).on(2010, 3, 14).to_string(), /// "2010-03-14T02:30:00", /// ); /// ``` #[inline] pub const fn at( self, hour: i8, minute: i8, second: i8, subsec_nanosecond: i32, ) -> DateTime { DateTime::from_parts( self, Time::constant(hour, minute, second, subsec_nanosecond), ) } /// Add the given span of time to this date. If the sum would overflow the /// minimum or maximum date values, then an error is returned. /// /// This operation accepts three different duration types: [`Span`], /// [`SignedDuration`] or [`std::time::Duration`]. This is achieved via /// `From` trait implementations for the [`DateArithmetic`] type. /// /// # Properties /// /// When adding a [`Span`] duration, this routine is _not_ reversible /// because some additions may be ambiguous. For example, adding `1 month` /// to the date `2024-03-31` will produce `2024-04-30` since April has only /// 30 days in a month. Conversely, subtracting `1 month` from `2024-04-30` /// will produce `2024-03-30`, which is not the date we started with. /// /// If spans of time are limited to units of days (or less), then this /// routine _is_ reversible. This also implies that all operations with /// a [`SignedDuration`] or a [`std::time::Duration`] are reversible. /// /// # Errors /// /// If the span added to this date would result in a date that exceeds the /// range of a `Date`, then this will return an error. /// /// # Examples /// /// This shows a few examples of adding spans of time to various dates. /// We make use of the [`ToSpan`](crate::ToSpan) trait for convenient /// creation of spans. /// /// ``` /// use jiff::{civil::date, ToSpan}; /// /// let d = date(2024, 3, 31); /// assert_eq!(d.checked_add(1.months())?, date(2024, 4, 30)); /// // Adding two months gives us May 31, not May 30. /// let d = date(2024, 3, 31); /// assert_eq!(d.checked_add(2.months())?, date(2024, 5, 31)); /// // Any time in the span that does not exceed a day is ignored. /// let d = date(2024, 3, 31); /// assert_eq!(d.checked_add(23.hours())?, date(2024, 3, 31)); /// // But if the time exceeds a day, that is accounted for! /// let d = date(2024, 3, 31); /// assert_eq!(d.checked_add(28.hours())?, date(2024, 4, 1)); /// /// # Ok::<(), Box>(()) /// ``` /// /// # Example: available via addition operator /// /// This routine can be used via the `+` operator. Note though that if it /// fails, it will result in a panic. /// /// ``` /// use jiff::{civil::date, ToSpan}; /// /// let d = date(2024, 3, 31); /// assert_eq!(d + 1.months(), date(2024, 4, 30)); /// ``` /// /// # Example: negative spans are supported /// /// ``` /// use jiff::{civil::date, ToSpan}; /// /// let d = date(2024, 3, 31); /// assert_eq!( /// d.checked_add(-1.months())?, /// date(2024, 2, 29), /// ); /// # Ok::<(), Box>(()) /// ``` /// /// # Example: error on overflow /// /// ``` /// use jiff::{civil::date, ToSpan}; /// /// let d = date(2024, 3, 31); /// assert!(d.checked_add(9000.years()).is_err()); /// assert!(d.checked_add(-19000.years()).is_err()); /// ``` /// /// # Example: adding absolute durations /// /// This shows how to add signed and unsigned absolute durations to a /// `Date`. Only whole numbers of days are considered. Since this is a /// civil date unaware of time zones, days are always 24 hours. /// /// ``` /// use std::time::Duration; /// /// use jiff::{civil::date, SignedDuration}; /// /// let d = date(2024, 2, 29); /// /// let dur = SignedDuration::from_hours(24); /// assert_eq!(d.checked_add(dur)?, date(2024, 3, 1)); /// assert_eq!(d.checked_add(-dur)?, date(2024, 2, 28)); /// /// // Any leftover time is truncated. That is, only /// // whole days from the duration are considered. /// let dur = Duration::from_secs((24 * 60 * 60) + (23 * 60 * 60)); /// assert_eq!(d.checked_add(dur)?, date(2024, 3, 1)); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn checked_add>( self, duration: A, ) -> Result { let duration: DateArithmetic = duration.into(); duration.checked_add(self) } #[inline] fn checked_add_span(self, span: Span) -> Result { let (month, years) = month_add_overflowing(self.month, span.get_months_ranged()); let year = self .year .try_checked_add("years", years)? .try_checked_add("years", span.get_years_ranged())?; let date = Date::constrain_ranged(year, month, self.day); let time_days = span .only_lower(Unit::Day) .to_invariant_nanoseconds() .div_ceil(t::NANOS_PER_CIVIL_DAY); let epoch_days = date.to_unix_epoch_days(); let days = epoch_days .try_checked_add( "days", C(7) * UnixEpochDays::rfrom(span.get_weeks_ranged()), )? .try_checked_add( "days", UnixEpochDays::rfrom(span.get_days_ranged()), )? .try_checked_add("time", time_days)?; Ok(Date::from_unix_epoch_days(days)) } #[inline] fn checked_add_duration( self, duration: SignedDuration, ) -> Result { // OK because 24!={-1,0}. let days = duration.as_hours() / 24; let days = UnixEpochDays::try_new("days", days).with_context(|| { err!( "{days} computed from duration {duration:?} overflows \ Jiff's datetime limits", ) })?; let days = self.to_unix_epoch_days().try_checked_add("days", days)?; Ok(Date::from_unix_epoch_days(days)) } /// This routine is identical to [`Date::checked_add`] with the duration /// negated. /// /// # Errors /// /// This has the same error conditions as [`Date::checked_add`]. /// /// # Example /// /// ``` /// use std::time::Duration; /// /// use jiff::{civil::date, SignedDuration, ToSpan}; /// /// let d = date(2024, 2, 29); /// assert_eq!(d.checked_sub(1.year())?, date(2023, 2, 28)); /// /// let dur = SignedDuration::from_hours(24); /// assert_eq!(d.checked_sub(dur)?, date(2024, 2, 28)); /// /// let dur = Duration::from_secs(24 * 60 * 60); /// assert_eq!(d.checked_sub(dur)?, date(2024, 2, 28)); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn checked_sub>( self, duration: A, ) -> Result { let duration: DateArithmetic = duration.into(); duration.checked_neg().and_then(|da| da.checked_add(self)) } /// This routine is identical to [`Date::checked_add`], except the /// result saturates on overflow. That is, instead of overflow, either /// [`Date::MIN`] or [`Date::MAX`] is returned. /// /// # Example /// /// ``` /// use jiff::{civil::{Date, date}, SignedDuration, ToSpan}; /// /// let d = date(2024, 3, 31); /// assert_eq!(Date::MAX, d.saturating_add(9000.years())); /// assert_eq!(Date::MIN, d.saturating_add(-19000.years())); /// assert_eq!(Date::MAX, d.saturating_add(SignedDuration::MAX)); /// assert_eq!(Date::MIN, d.saturating_add(SignedDuration::MIN)); /// assert_eq!(Date::MAX, d.saturating_add(std::time::Duration::MAX)); /// ``` #[inline] pub fn saturating_add>(self, duration: A) -> Date { let duration: DateArithmetic = duration.into(); self.checked_add(duration).unwrap_or_else(|_| { if duration.is_negative() { Date::MIN } else { Date::MAX } }) } /// This routine is identical to [`Date::saturating_add`] with the span /// parameter negated. /// /// # Example /// /// ``` /// use jiff::{civil::{Date, date}, SignedDuration, ToSpan}; /// /// let d = date(2024, 3, 31); /// assert_eq!(Date::MIN, d.saturating_sub(19000.years())); /// assert_eq!(Date::MAX, d.saturating_sub(-9000.years())); /// assert_eq!(Date::MIN, d.saturating_sub(SignedDuration::MAX)); /// assert_eq!(Date::MAX, d.saturating_sub(SignedDuration::MIN)); /// assert_eq!(Date::MIN, d.saturating_sub(std::time::Duration::MAX)); /// ``` #[inline] pub fn saturating_sub>(self, duration: A) -> Date { let duration: DateArithmetic = duration.into(); let Ok(duration) = duration.checked_neg() else { return Date::MIN }; self.saturating_add(duration) } /// Returns a span representing the elapsed time from this date until /// the given `other` date. /// /// When `other` occurs before this date, then the span returned will be /// negative. /// /// Depending on the input provided, the span returned is rounded. It may /// also be balanced up to bigger units than the default. By default, the /// span returned is balanced such that the biggest and smallest possible /// unit is days. /// /// This operation is configured by providing a [`DateDifference`] /// value. Since this routine accepts anything that implements /// `Into`, once can pass a `Date` directly. One /// can also pass a `(Unit, Date)`, where `Unit` is treated as /// [`DateDifference::largest`]. /// /// # Properties /// /// It is guaranteed that if the returned span is subtracted from `other`, /// and if no rounding is requested, and if the largest unit request is at /// most `Unit::Day`, then the original date will be returned. /// /// This routine is equivalent to `self.since(other).map(|span| -span)` /// if no rounding options are set. If rounding options are set, then /// it's equivalent to /// `self.since(other_without_rounding_options).map(|span| -span)`, /// followed by a call to [`Span::round`] with the appropriate rounding /// options set. This is because the negation of a span can result in /// different rounding results depending on the rounding mode. /// /// # Errors /// /// An error can occur if `DateDifference` is misconfigured. For example, /// if the smallest unit provided is bigger than the largest unit. /// /// It is guaranteed that if one provides a date with the default /// [`DateDifference`] configuration, then this routine will never fail. /// /// # Examples /// /// ``` /// use jiff::{civil::date, ToSpan}; /// /// let earlier = date(2006, 8, 24); /// let later = date(2019, 1, 31); /// assert_eq!(earlier.until(later)?, 4543.days().fieldwise()); /// /// // Flipping the dates is fine, but you'll get a negative span. /// let earlier = date(2006, 8, 24); /// let later = date(2019, 1, 31); /// assert_eq!(later.until(earlier)?, -4543.days().fieldwise()); /// /// # Ok::<(), Box>(()) /// ``` /// /// # Example: using bigger units /// /// This example shows how to expand the span returned to bigger units. /// This makes use of a `From<(Unit, Date)> for DateDifference` trait /// implementation. /// /// ``` /// use jiff::{civil::date, Unit, ToSpan}; /// /// let d1 = date(1995, 12, 07); /// let d2 = date(2019, 01, 31); /// /// // The default limits durations to using "days" as the biggest unit. /// let span = d1.until(d2)?; /// assert_eq!(span.to_string(), "P8456D"); /// /// // But we can ask for units all the way up to years. /// let span = d1.until((Unit::Year, d2))?; /// assert_eq!(span.to_string(), "P23Y1M24D"); /// /// # Ok::<(), Box>(()) /// ``` /// /// # Example: rounding the result /// /// This shows how one might find the difference between two dates and /// have the result rounded to the nearest month. /// /// In this case, we need to hand-construct a [`DateDifference`] /// in order to gain full configurability. /// /// ``` /// use jiff::{civil::{date, DateDifference}, Unit, ToSpan}; /// /// let d1 = date(1995, 12, 07); /// let d2 = date(2019, 02, 06); /// /// let span = d1.until(DateDifference::from(d2).smallest(Unit::Month))?; /// assert_eq!(span, 277.months().fieldwise()); /// /// // Or even include years to make the span a bit more comprehensible. /// let span = d1.until( /// DateDifference::from(d2) /// .smallest(Unit::Month) /// .largest(Unit::Year), /// )?; /// // Notice that we are one day shy of 23y2m. Rounding spans computed /// // between dates uses truncation by default. /// assert_eq!(span, 23.years().months(1).fieldwise()); /// /// # Ok::<(), Box>(()) /// ``` /// /// # Example: units biggers than days inhibit reversibility /// /// If you ask for units bigger than days, then adding the span /// returned to the `other` date is not guaranteed to result in the /// original date. For example: /// /// ``` /// use jiff::{civil::date, Unit, ToSpan}; /// /// let d1 = date(2024, 3, 2); /// let d2 = date(2024, 5, 1); /// /// let span = d1.until((Unit::Month, d2))?; /// assert_eq!(span, 1.month().days(29).fieldwise()); /// let maybe_original = d2.checked_sub(span)?; /// // Not the same as the original datetime! /// assert_eq!(maybe_original, date(2024, 3, 3)); /// /// // But in the default configuration, days are always the biggest unit /// // and reversibility is guaranteed. /// let span = d1.until(d2)?; /// assert_eq!(span, 60.days().fieldwise()); /// let is_original = d2.checked_sub(span)?; /// assert_eq!(is_original, d1); /// /// # Ok::<(), Box>(()) /// ``` /// /// This occurs because spans are added as if by adding the biggest units /// first, and then the smaller units. Because months vary in length, /// their meaning can change depending on how the span is added. In this /// case, adding one month to `2024-03-02` corresponds to 31 days, but /// subtracting one month from `2024-05-01` corresponds to 30 days. #[inline] pub fn until>( self, other: A, ) -> Result { let args: DateDifference = other.into(); let span = args.until_with_largest_unit(self)?; if args.rounding_may_change_span() { span.round(args.round.relative(self)) } else { Ok(span) } } /// This routine is identical to [`Date::until`], but the order of the /// parameters is flipped. /// /// # Errors /// /// This has the same error conditions as [`Date::until`]. /// /// # Example /// /// This routine can be used via the `-` operator. Since the default /// configuration is used and because a `Span` can represent the difference /// between any two possible dates, it will never panic. /// /// ``` /// use jiff::{civil::date, ToSpan}; /// /// let earlier = date(2006, 8, 24); /// let later = date(2019, 1, 31); /// assert_eq!(later - earlier, 4543.days().fieldwise()); /// // Equivalent to: /// assert_eq!(later.since(earlier).unwrap(), 4543.days().fieldwise()); /// ``` #[inline] pub fn since>( self, other: A, ) -> Result { let args: DateDifference = other.into(); let span = -args.until_with_largest_unit(self)?; if args.rounding_may_change_span() { span.round(args.round.relative(self)) } else { Ok(span) } } /// Returns an absolute duration representing the elapsed time from this /// date until the given `other` date. /// /// When `other` occurs before this date, then the duration returned will /// be negative. /// /// Unlike [`Date::until`], this returns a duration corresponding to a /// 96-bit integer of nanoseconds between two dates. In this case of /// computing durations between civil dates where all days are assumed to /// be 24 hours long, the duration returned will always be divisible by /// 24 hours. (That is, `24 * 60 * 60 * 1_000_000_000` nanoseconds.) /// /// # Fallibility /// /// This routine never panics or returns an error. Since there are no /// configuration options that can be incorrectly provided, no error is /// possible when calling this routine. In contrast, [`Date::until`] can /// return an error in some cases due to misconfiguration. But like this /// routine, [`Date::until`] never panics or returns an error in its /// default configuration. /// /// # When should I use this versus [`Date::until`]? /// /// See the type documentation for [`SignedDuration`] for the section on /// when one should use [`Span`] and when one should use `SignedDuration`. /// In short, use `Span` (and therefore `Date::until`) unless you have a /// specific reason to do otherwise. /// /// # Example /// /// ``` /// use jiff::{civil::date, SignedDuration}; /// /// let earlier = date(2006, 8, 24); /// let later = date(2019, 1, 31); /// assert_eq!( /// earlier.duration_until(later), /// SignedDuration::from_hours(4543 * 24), /// ); /// ``` /// /// # Example: difference with [`Date::until`] /// /// The main difference between this routine and `Date::until` is that the /// latter can return units other than a 96-bit integer of nanoseconds. /// While a 96-bit integer of nanoseconds can be converted into other /// units like hours, this can only be done for uniform units. (Uniform /// units are units for which each individual unit always corresponds to /// the same elapsed time regardless of the datetime it is relative to.) /// This can't be done for units like years or months. /// /// ``` /// use jiff::{civil::date, SignedDuration, Span, SpanRound, ToSpan, Unit}; /// /// let d1 = date(2024, 1, 1); /// let d2 = date(2025, 4, 1); /// /// let span = d1.until((Unit::Year, d2))?; /// assert_eq!(span, 1.year().months(3).fieldwise()); /// /// let duration = d1.duration_until(d2); /// assert_eq!(duration, SignedDuration::from_hours(456 * 24)); /// // There's no way to extract years or months from the signed /// // duration like one might extract hours (because every hour /// // is the same length). Instead, you actually have to convert /// // it to a span and then balance it by providing a relative date! /// let options = SpanRound::new().largest(Unit::Year).relative(d1); /// let span = Span::try_from(duration)?.round(options)?; /// assert_eq!(span, 1.year().months(3).fieldwise()); /// /// # Ok::<(), Box>(()) /// ``` /// /// # Example: getting an unsigned duration /// /// If you're looking to find the duration between two dates as a /// [`std::time::Duration`], you'll need to use this method to get a /// [`SignedDuration`] and then convert it to a `std::time::Duration`: /// /// ``` /// use std::time::Duration; /// /// use jiff::{civil::date, SignedDuration}; /// /// let d1 = date(2024, 7, 1); /// let d2 = date(2024, 8, 1); /// let duration = Duration::try_from(d1.duration_until(d2))?; /// assert_eq!(duration, Duration::from_secs(31 * 24 * 60 * 60)); /// /// // Note that unsigned durations cannot represent all /// // possible differences! If the duration would be negative, /// // then the conversion fails: /// assert!(Duration::try_from(d2.duration_until(d1)).is_err()); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn duration_until(self, other: Date) -> SignedDuration { SignedDuration::date_until(self, other) } /// This routine is identical to [`Date::duration_until`], but the order of /// the parameters is flipped. /// /// # Example /// /// ``` /// use jiff::{civil::date, SignedDuration}; /// /// let earlier = date(2006, 8, 24); /// let later = date(2019, 1, 31); /// assert_eq!( /// later.duration_since(earlier), /// SignedDuration::from_hours(4543 * 24), /// ); /// ``` #[inline] pub fn duration_since(self, other: Date) -> SignedDuration { SignedDuration::date_until(other, self) } /// Return an iterator of periodic dates determined by the given span. /// /// The given span may be negative, in which case, the iterator will move /// backwards through time. The iterator won't stop until either the span /// itself overflows, or it would otherwise exceed the minimum or maximum /// `Date` value. /// /// # Example: Halloween day of the week /// /// As a kid, I always hoped for Halloween to fall on a weekend. With this /// program, we can print the day of the week for all Halloweens in the /// 2020s. /// /// ``` /// use jiff::{civil::{Weekday, date}, ToSpan}; /// /// let start = date(2020, 10, 31); /// let mut halloween_days_of_week = vec![]; /// for halloween in start.series(1.years()).take(10) { /// halloween_days_of_week.push( /// (halloween.year(), halloween.weekday()), /// ); /// } /// assert_eq!(halloween_days_of_week, vec![ /// (2020, Weekday::Saturday), /// (2021, Weekday::Sunday), /// (2022, Weekday::Monday), /// (2023, Weekday::Tuesday), /// (2024, Weekday::Thursday), /// (2025, Weekday::Friday), /// (2026, Weekday::Saturday), /// (2027, Weekday::Sunday), /// (2028, Weekday::Tuesday), /// (2029, Weekday::Wednesday), /// ]); /// ``` /// /// # Example: how many times do I mow the lawn in a year? /// /// I mow the lawn about every week and a half from the beginning of May /// to the end of October. About how many times will I mow the lawn in /// 2024? /// /// ``` /// use jiff::{ToSpan, civil::date}; /// /// let start = date(2024, 5, 1); /// let end = date(2024, 10, 31); /// let mows = start /// .series(1.weeks().days(3).hours(12)) /// .take_while(|&d| d <= end) /// .count(); /// assert_eq!(mows, 18); /// ``` /// /// # Example: a period less than a day /// /// Using a period less than a day works, but since this type exists at the /// granularity of a day, some dates may be repeated. /// /// ``` /// use jiff::{civil::{Date, date}, ToSpan}; /// /// let start = date(2024, 3, 11); /// let every_five_hours: Vec = /// start.series(15.hours()).take(7).collect(); /// assert_eq!(every_five_hours, vec![ /// date(2024, 3, 11), /// date(2024, 3, 11), /// date(2024, 3, 12), /// date(2024, 3, 12), /// date(2024, 3, 13), /// date(2024, 3, 14), /// date(2024, 3, 14), /// ]); /// ``` /// /// # Example: finding the most recent Friday the 13th /// /// When did the most recent Friday the 13th occur? /// /// ``` /// use jiff::{civil::{Weekday, date}, ToSpan}; /// /// let start = date(2024, 3, 13); /// let mut found = None; /// for date in start.series(-1.months()) { /// if date.weekday() == Weekday::Friday { /// found = Some(date); /// break; /// } /// } /// assert_eq!(found, Some(date(2023, 10, 13))); /// ``` #[inline] pub fn series(self, period: Span) -> DateSeries { DateSeries { start: self, period, step: 0 } } } /// Parsing and formatting using a "printf"-style API. impl Date { /// Parses a civil date in `input` matching the given `format`. /// /// The format string uses a "printf"-style API where conversion /// specifiers can be used as place holders to match components of /// a datetime. For details on the specifiers supported, see the /// [`fmt::strtime`] module documentation. /// /// # Errors /// /// This returns an error when parsing failed. This might happen because /// the format string itself was invalid, or because the input didn't match /// the format string. /// /// This also returns an error if there wasn't sufficient information to /// construct a civil date. For example, if an offset wasn't parsed. /// /// # Example /// /// This example shows how to parse a civil date: /// /// ``` /// use jiff::civil::Date; /// /// // Parse an American date with a two-digit year. /// let date = Date::strptime("%m/%d/%y", "7/14/24")?; /// assert_eq!(date.to_string(), "2024-07-14"); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn strptime( format: impl AsRef<[u8]>, input: impl AsRef<[u8]>, ) -> Result { fmt::strtime::parse(format, input).and_then(|tm| tm.to_date()) } /// Formats this civil date according to the given `format`. /// /// The format string uses a "printf"-style API where conversion /// specifiers can be used as place holders to format components of /// a datetime. For details on the specifiers supported, see the /// [`fmt::strtime`] module documentation. /// /// # Errors and panics /// /// While this routine itself does not error or panic, using the value /// returned may result in a panic if formatting fails. See the /// documentation on [`fmt::strtime::Display`] for more information. /// /// To format in a way that surfaces errors without panicking, use either /// [`fmt::strtime::format`] or [`fmt::strtime::BrokenDownTime::format`]. /// /// # Example /// /// This example shows how to format a civil date: /// /// ``` /// use jiff::civil::date; /// /// let date = date(2024, 7, 15); /// let string = date.strftime("%Y-%m-%d is a %A").to_string(); /// assert_eq!(string, "2024-07-15 is a Monday"); /// ``` #[inline] pub fn strftime<'f, F: 'f + ?Sized + AsRef<[u8]>>( &self, format: &'f F, ) -> fmt::strtime::Display<'f> { fmt::strtime::Display { fmt: format.as_ref(), tm: (*self).into() } } } /// Deprecated APIs. impl Date { /// A deprecated equivalent to [`Date::iso_week_date`]. /// /// This method will be removed in `jiff 0.2`. This was done to make naming /// more consistent throughout the crate. #[deprecated(since = "0.1.23", note = "use Date::iso_week_date instead")] #[inline] pub fn to_iso_week_date(self) -> ISOWeekDate { self.iso_week_date() } /// A deprecated equivalent to [`Date::in_tz`]. /// /// This will be removed in `jiff 0.2`. The method was renamed to make /// it clearer that the name stood for "in time zone." #[deprecated(since = "0.1.25", note = "use Date::in_tz instead")] #[inline] pub fn intz(self, time_zone_name: &str) -> Result { let tz = crate::tz::db().get(time_zone_name)?; self.to_zoned(tz) } } // Constants used for converting between Gregorian calendar dates and Unix // epoch days. // // See: http://howardhinnant.github.io/date_algorithms.html const DAYS_IN_ERA: Constant = Constant(146_097); const DAYS_FROM_0000_01_01_TO_1970_01_01: Constant = Constant(719_468); /// Internal APIs using ranged integers. impl Date { #[inline] pub(crate) fn new_ranged( year: impl RInto, month: impl RInto, day: impl RInto, ) -> Result { let (year, month, day) = (year.rinto(), month.rinto(), day.rinto()); let max_day = days_in_month(year, month); if day > max_day { return Err(day.to_error_with_bounds("day", 1, max_day)); } Ok(Date { year, month, day }) } #[inline] fn constrain_ranged( year: impl RInto, month: impl RInto, day: impl RInto, ) -> Date { let (year, month, mut day) = (year.rinto(), month.rinto(), day.rinto()); day = saturate_day_in_month(year, month, day); Date { year, month, day } } #[inline] pub(crate) fn year_ranged(self) -> Year { self.year } #[inline] pub(crate) fn month_ranged(self) -> Month { self.month } #[inline] pub(crate) fn day_ranged(self) -> Day { self.day } #[inline] pub(crate) fn days_in_month_ranged(self) -> Day { days_in_month(self.year_ranged(), self.month_ranged()) } #[inline] pub(crate) fn since_days_ranged(self, other: Date) -> t::SpanDays { -self.until_days_ranged(other) } #[inline] pub(crate) fn until_days_ranged(self, other: Date) -> t::SpanDays { if self == other { return C(0).rinto(); } let start = self.to_unix_epoch_days(); let end = other.to_unix_epoch_days(); (end - start).rinto() } #[inline] pub(crate) fn to_unix_epoch_days(self) -> UnixEpochDays { // ref: http://howardhinnant.github.io/date_algorithms.html let year = UnixEpochDays::rfrom(self.year); let month = UnixEpochDays::rfrom(self.month); let day = UnixEpochDays::rfrom(self.day); let year = UnixEpochDays::vary([month, year], |[month, year]| { if month <= 2 { year - C(1) } else { year } }); let month = UnixEpochDays::vary([month], |[month]| { if month > 2 { month - C(3) } else { month + C(9) } }); let era = year / C(400); let year_of_era = year % C(400); let day_of_year = (C(153) * month + C(2)) / C(5) + day - C(1); let day_of_era = year_of_era * C(365) + year_of_era / C(4) - year_of_era / C(100) + day_of_year; let epoch_days = era * DAYS_IN_ERA + day_of_era - DAYS_FROM_0000_01_01_TO_1970_01_01; epoch_days } #[inline] pub(crate) fn from_unix_epoch_days( days: impl RInto, ) -> Date { // ref: http://howardhinnant.github.io/date_algorithms.html let days = days.rinto(); let days = days + DAYS_FROM_0000_01_01_TO_1970_01_01; let era = days / DAYS_IN_ERA; let day_of_era = days % DAYS_IN_ERA; let year_of_era = (day_of_era - day_of_era / C(1_460) + day_of_era / C(36_524) - day_of_era / (DAYS_IN_ERA - C(1))) / C(365); let year = year_of_era + era * C(400); let day_of_year = day_of_era - (C(365) * year_of_era + year_of_era / C(4) - year_of_era / C(100)); let month = (day_of_year * C(5) + C(2)) / C(153); let day = day_of_year - (C(153) * month + C(2)) / C(5) + C(1); let month = UnixEpochDays::vary([month], |[month]| { if month < 10 { month + C(3) } else { month - C(9) } }); let year = UnixEpochDays::vary([month, year], |[month, year]| { if month <= 2 { year + C(1) } else { year } }); Date { year: year.rinto(), month: month.rinto(), day: day.rinto() } } } impl Eq for Date {} impl PartialEq for Date { #[inline] fn eq(&self, other: &Date) -> bool { // We roll our own PartialEq impl so that we call 'get' on the // underlying ranged integer. This forces bugs in boundary conditions // to result in panics when 'debug_assertions' is enabled. self.day.get() == other.day.get() && self.month.get() == other.month.get() && self.year.get() == other.year.get() } } impl Ord for Date { #[inline] fn cmp(&self, other: &Date) -> core::cmp::Ordering { (self.year.get(), self.month.get(), self.day.get()).cmp(&( other.year.get(), other.month.get(), other.day.get(), )) } } impl PartialOrd for Date { #[inline] fn partial_cmp(&self, other: &Date) -> Option { Some(self.cmp(other)) } } impl Default for Date { fn default() -> Date { Date::ZERO } } impl core::fmt::Debug for Date { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { core::fmt::Display::fmt(self, f) } } impl core::fmt::Display for Date { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { use crate::fmt::StdFmtWrite; DEFAULT_DATETIME_PRINTER .print_date(self, StdFmtWrite(f)) .map_err(|_| core::fmt::Error) } } impl core::str::FromStr for Date { type Err = Error; fn from_str(string: &str) -> Result { DEFAULT_DATETIME_PARSER.parse_date(string) } } impl From for Date { #[inline] fn from(weekdate: ISOWeekDate) -> Date { Date::from_iso_week_date(weekdate) } } impl From for Date { #[inline] fn from(dt: DateTime) -> Date { dt.date() } } impl From for Date { #[inline] fn from(zdt: Zoned) -> Date { zdt.datetime().date() } } impl<'a> From<&'a Zoned> for Date { #[inline] fn from(zdt: &'a Zoned) -> Date { zdt.datetime().date() } } /// Adds a span of time to a date. /// /// This uses checked arithmetic and panics on overflow. To handle overflow /// without panics, use [`Date::checked_add`]. impl core::ops::Add for Date { type Output = Date; #[inline] fn add(self, rhs: Span) -> Date { self.checked_add(rhs).expect("adding span to date overflowed") } } /// Adds a span of time to a date in place. /// /// This uses checked arithmetic and panics on overflow. To handle overflow /// without panics, use [`Date::checked_add`]. impl core::ops::AddAssign for Date { #[inline] fn add_assign(&mut self, rhs: Span) { *self = *self + rhs; } } /// Subtracts a span of time from a date. /// /// This uses checked arithmetic and panics on overflow. To handle overflow /// without panics, use [`Date::checked_sub`]. impl core::ops::Sub for Date { type Output = Date; #[inline] fn sub(self, rhs: Span) -> Date { self.checked_sub(rhs).expect("subing span to date overflowed") } } /// Subtracts a span of time from a date in place. /// /// This uses checked arithmetic and panics on overflow. To handle overflow /// without panics, use [`Date::checked_sub`]. impl core::ops::SubAssign for Date { #[inline] fn sub_assign(&mut self, rhs: Span) { *self = *self - rhs; } } /// Computes the span of time between two dates. /// /// This will return a negative span when the date being subtracted is greater. /// /// Since this uses the default configuration for calculating a span between /// two date (no rounding and largest units is days), this will never panic or /// fail in any way. /// /// To configure the largest unit or enable rounding, use [`Date::since`]. impl core::ops::Sub for Date { type Output = Span; #[inline] fn sub(self, rhs: Date) -> Span { self.since(rhs).expect("since never fails when given Date") } } /// Adds a signed duration of time to a date. /// /// This uses checked arithmetic and panics on overflow. To handle overflow /// without panics, use [`Date::checked_add`]. impl core::ops::Add for Date { type Output = Date; #[inline] fn add(self, rhs: SignedDuration) -> Date { self.checked_add(rhs) .expect("adding signed duration to date overflowed") } } /// Adds a signed duration of time to a date in place. /// /// This uses checked arithmetic and panics on overflow. To handle overflow /// without panics, use [`Date::checked_add`]. impl core::ops::AddAssign for Date { #[inline] fn add_assign(&mut self, rhs: SignedDuration) { *self = *self + rhs; } } /// Subtracts a signed duration of time from a date. /// /// This uses checked arithmetic and panics on overflow. To handle overflow /// without panics, use [`Date::checked_sub`]. impl core::ops::Sub for Date { type Output = Date; #[inline] fn sub(self, rhs: SignedDuration) -> Date { self.checked_sub(rhs) .expect("subing signed duration to date overflowed") } } /// Subtracts a signed duration of time from a date in place. /// /// This uses checked arithmetic and panics on overflow. To handle overflow /// without panics, use [`Date::checked_sub`]. impl core::ops::SubAssign for Date { #[inline] fn sub_assign(&mut self, rhs: SignedDuration) { *self = *self - rhs; } } /// Adds an unsigned duration of time to a date. /// /// This uses checked arithmetic and panics on overflow. To handle overflow /// without panics, use [`Date::checked_add`]. impl core::ops::Add for Date { type Output = Date; #[inline] fn add(self, rhs: UnsignedDuration) -> Date { self.checked_add(rhs) .expect("adding unsigned duration to date overflowed") } } /// Adds an unsigned duration of time to a date in place. /// /// This uses checked arithmetic and panics on overflow. To handle overflow /// without panics, use [`Date::checked_add`]. impl core::ops::AddAssign for Date { #[inline] fn add_assign(&mut self, rhs: UnsignedDuration) { *self = *self + rhs; } } /// Subtracts an unsigned duration of time from a date. /// /// This uses checked arithmetic and panics on overflow. To handle overflow /// without panics, use [`Date::checked_sub`]. impl core::ops::Sub for Date { type Output = Date; #[inline] fn sub(self, rhs: UnsignedDuration) -> Date { self.checked_sub(rhs) .expect("subing unsigned duration to date overflowed") } } /// Subtracts an unsigned duration of time from a date in place. /// /// This uses checked arithmetic and panics on overflow. To handle overflow /// without panics, use [`Date::checked_sub`]. impl core::ops::SubAssign for Date { #[inline] fn sub_assign(&mut self, rhs: UnsignedDuration) { *self = *self - rhs; } } #[cfg(feature = "serde")] impl serde::Serialize for Date { #[inline] fn serialize( &self, serializer: S, ) -> Result { serializer.collect_str(self) } } #[cfg(feature = "serde")] impl<'de> serde::Deserialize<'de> for Date { #[inline] fn deserialize>( deserializer: D, ) -> Result { use serde::de; struct DateVisitor; impl<'de> de::Visitor<'de> for DateVisitor { type Value = Date; fn expecting( &self, f: &mut core::fmt::Formatter, ) -> core::fmt::Result { f.write_str("a date string") } #[inline] fn visit_bytes( self, value: &[u8], ) -> Result { DEFAULT_DATETIME_PARSER .parse_date(value) .map_err(de::Error::custom) } #[inline] fn visit_str(self, value: &str) -> Result { self.visit_bytes(value.as_bytes()) } } deserializer.deserialize_str(DateVisitor) } } #[cfg(test)] impl quickcheck::Arbitrary for Date { fn arbitrary(g: &mut quickcheck::Gen) -> Date { let year = Year::arbitrary(g); let month = Month::arbitrary(g); let day = Day::arbitrary(g); Date::constrain_ranged(year, month, day) } fn shrink(&self) -> alloc::boxed::Box> { alloc::boxed::Box::new( (self.year_ranged(), self.month_ranged(), self.day_ranged()) .shrink() .map(|(year, month, day)| { Date::constrain_ranged(year, month, day) }), ) } } /// An iterator over periodic dates, created by [`Date::series`]. /// /// It is exhausted when the next value would exceed a [`Span`] or [`Date`] /// value. #[derive(Clone, Debug)] pub struct DateSeries { start: Date, period: Span, step: i64, } impl Iterator for DateSeries { type Item = Date; #[inline] fn next(&mut self) -> Option { let span = self.period.checked_mul(self.step).ok()?; self.step = self.step.checked_add(1)?; let date = self.start.checked_add(span).ok()?; Some(date) } } /// Options for [`Date::checked_add`] and [`Date::checked_sub`]. /// /// This type provides a way to ergonomically add one of a few different /// duration types to a [`Date`]. /// /// The main way to construct values of this type is with its `From` trait /// implementations: /// /// * `From for DateArithmetic` adds (or subtracts) the given span to the /// receiver date. /// * `From for DateArithmetic` adds (or subtracts) /// the given signed duration to the receiver date. /// * `From for DateArithmetic` adds (or subtracts) /// the given unsigned duration to the receiver date. /// /// # Example /// /// ``` /// use std::time::Duration; /// /// use jiff::{civil::date, SignedDuration, ToSpan}; /// /// let d = date(2024, 2, 29); /// assert_eq!(d.checked_add(1.year())?, date(2025, 2, 28)); /// assert_eq!(d.checked_add(SignedDuration::from_hours(24))?, date(2024, 3, 1)); /// assert_eq!(d.checked_add(Duration::from_secs(24 * 60 * 60))?, date(2024, 3, 1)); /// /// # Ok::<(), Box>(()) /// ``` #[derive(Clone, Copy, Debug)] pub struct DateArithmetic { duration: Duration, } impl DateArithmetic { #[inline] fn checked_add(self, date: Date) -> Result { match self.duration.to_signed()? { SDuration::Span(span) => date.checked_add_span(span), SDuration::Absolute(sdur) => date.checked_add_duration(sdur), } } #[inline] fn checked_neg(self) -> Result { let duration = self.duration.checked_neg()?; Ok(DateArithmetic { duration }) } #[inline] fn is_negative(&self) -> bool { self.duration.is_negative() } } impl From for DateArithmetic { fn from(span: Span) -> DateArithmetic { let duration = Duration::from(span); DateArithmetic { duration } } } impl From for DateArithmetic { fn from(sdur: SignedDuration) -> DateArithmetic { let duration = Duration::from(sdur); DateArithmetic { duration } } } impl From for DateArithmetic { fn from(udur: UnsignedDuration) -> DateArithmetic { let duration = Duration::from(udur); DateArithmetic { duration } } } impl<'a> From<&'a Span> for DateArithmetic { fn from(span: &'a Span) -> DateArithmetic { DateArithmetic::from(*span) } } impl<'a> From<&'a SignedDuration> for DateArithmetic { fn from(sdur: &'a SignedDuration) -> DateArithmetic { DateArithmetic::from(*sdur) } } impl<'a> From<&'a UnsignedDuration> for DateArithmetic { fn from(udur: &'a UnsignedDuration) -> DateArithmetic { DateArithmetic::from(*udur) } } /// Options for [`Date::since`] and [`Date::until`]. /// /// This type provides a way to configure the calculation of spans between two /// [`Date`] values. In particular, both `Date::since` and `Date::until` accept /// anything that implements `Into`. There are a few key trait /// implementations that make this convenient: /// /// * `From for DateDifference` will construct a configuration consisting /// of just the date. So for example, `date1.until(date2)` will return the span /// from `date1` to `date2`. /// * `From for DateDifference` will construct a configuration /// consisting of just the date from the given datetime. So for example, /// `date.since(datetime)` returns the span from `datetime.date()` to `date`. /// * `From<(Unit, Date)>` is a convenient way to specify the largest units /// that should be present on the span returned. By default, the largest units /// are days. Using this trait implementation is equivalent to /// `DateDifference::new(date).largest(unit)`. /// * `From<(Unit, DateTime)>` is like the one above, but with the date from /// the given datetime. /// /// One can also provide a `DateDifference` value directly. Doing so is /// necessary to use the rounding features of calculating a span. For example, /// setting the smallest unit (defaults to [`Unit::Day`]), the rounding mode /// (defaults to [`RoundMode::Trunc`]) and the rounding increment (defaults to /// `1`). The defaults are selected such that no rounding occurs. /// /// Rounding a span as part of calculating it is provided as a convenience. /// Callers may choose to round the span as a distinct step via /// [`Span::round`], but callers may need to provide a reference date /// for rounding larger units. By coupling rounding with routines like /// [`Date::since`], the reference date can be set automatically based on /// the input to `Date::since`. /// /// # Example /// /// This example shows how to round a span between two date to the nearest /// year, with ties breaking away from zero. /// /// ``` /// use jiff::{civil::{Date, DateDifference}, RoundMode, ToSpan, Unit}; /// /// let d1 = "2024-03-15".parse::()?; /// let d2 = "2030-09-13".parse::()?; /// let span = d1.until( /// DateDifference::new(d2) /// .smallest(Unit::Year) /// .mode(RoundMode::HalfExpand), /// )?; /// assert_eq!(span, 6.years().fieldwise()); /// /// // If the span were one day longer, it would round up to 7 years. /// let d2 = "2030-09-14".parse::()?; /// let span = d1.until( /// DateDifference::new(d2) /// .smallest(Unit::Year) /// .mode(RoundMode::HalfExpand), /// )?; /// assert_eq!(span, 7.years().fieldwise()); /// /// # Ok::<(), Box>(()) /// ``` #[derive(Clone, Copy, Debug)] pub struct DateDifference { date: Date, round: SpanRound<'static>, } impl DateDifference { /// Create a new default configuration for computing the span between /// the given date and some other date (specified as the receiver in /// [`Date::since`] or [`Date::until`]). #[inline] pub fn new(date: Date) -> DateDifference { // We use truncation rounding by default since it seems that's // what is generally expected when computing the difference between // datetimes. // // See: https://github.com/tc39/proposal-temporal/issues/1122 let round = SpanRound::new().mode(RoundMode::Trunc); DateDifference { date, round } } /// Set the smallest units allowed in the span returned. /// /// When a largest unit is not specified, then the largest unit is /// automatically set to be equal to the smallest unit. /// /// # Errors /// /// The smallest units must be no greater than the largest units. If this /// is violated, then computing a span with this configuration will result /// in an error. /// /// # Example /// /// This shows how to round a span between two date to the nearest /// number of weeks. /// /// ``` /// use jiff::{civil::{Date, DateDifference}, RoundMode, ToSpan, Unit}; /// /// let d1 = "2024-03-15".parse::()?; /// let d2 = "2030-11-22".parse::()?; /// let span = d1.until( /// DateDifference::new(d2) /// .smallest(Unit::Week) /// .largest(Unit::Week) /// .mode(RoundMode::HalfExpand), /// )?; /// assert_eq!(span, 349.weeks().fieldwise()); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn smallest(self, unit: Unit) -> DateDifference { DateDifference { round: self.round.smallest(unit), ..self } } /// Set the largest units allowed in the span returned. /// /// When a largest unit is not specified, then the largest unit is /// automatically set to be equal to the smallest unit. Otherwise, when the /// largest unit is not specified, it is set to days. /// /// Once a largest unit is set, there is no way to change this rounding /// configuration back to using the "automatic" default. Instead, callers /// must create a new configuration. /// /// # Errors /// /// The largest units, when set, must be at least as big as the smallest /// units (which defaults to [`Unit::Day`]). If this is violated, then /// computing a span with this configuration will result in an error. /// /// # Example /// /// This shows how to round a span between two date to units no /// bigger than months. /// /// ``` /// use jiff::{civil::{Date, DateDifference}, ToSpan, Unit}; /// /// let d1 = "2024-03-15".parse::()?; /// let d2 = "2030-11-22".parse::()?; /// let span = d1.until( /// DateDifference::new(d2).largest(Unit::Month), /// )?; /// assert_eq!(span, 80.months().days(7).fieldwise()); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn largest(self, unit: Unit) -> DateDifference { DateDifference { round: self.round.largest(unit), ..self } } /// Set the rounding mode. /// /// This defaults to [`RoundMode::Trunc`] since it's plausible that /// rounding "up" in the context of computing the span between two date /// could be surprising in a number of cases. The [`RoundMode::HalfExpand`] /// mode corresponds to typical rounding you might have learned about in /// school. But a variety of other rounding modes exist. /// /// # Example /// /// This shows how to always round "up" towards positive infinity. /// /// ``` /// use jiff::{civil::{Date, DateDifference}, RoundMode, ToSpan, Unit}; /// /// let d1 = "2024-01-15".parse::()?; /// let d2 = "2024-08-16".parse::()?; /// let span = d1.until( /// DateDifference::new(d2) /// .smallest(Unit::Month) /// .mode(RoundMode::Ceil), /// )?; /// // Only 7 months and 1 day elapsed, but we asked to always round up! /// assert_eq!(span, 8.months().fieldwise()); /// /// // Since `Ceil` always rounds toward positive infinity, the behavior /// // flips for a negative span. /// let span = d1.since( /// DateDifference::new(d2) /// .smallest(Unit::Month) /// .mode(RoundMode::Ceil), /// )?; /// assert_eq!(span, -7.months().fieldwise()); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn mode(self, mode: RoundMode) -> DateDifference { DateDifference { round: self.round.mode(mode), ..self } } /// Set the rounding increment for the smallest unit. /// /// The default value is `1`. Other values permit rounding the smallest /// unit to the nearest integer increment specified. For example, if the /// smallest unit is set to [`Unit::Month`], then a rounding increment of /// `2` would result in rounding in increments of every other month. /// /// # Example /// /// This shows how to round the span between two date to the nearest even /// month. /// /// ``` /// use jiff::{civil::{Date, DateDifference}, RoundMode, ToSpan, Unit}; /// /// let d1 = "2024-01-15".parse::()?; /// let d2 = "2024-08-15".parse::()?; /// let span = d1.until( /// DateDifference::new(d2) /// .smallest(Unit::Month) /// .increment(2) /// .mode(RoundMode::HalfExpand), /// )?; /// assert_eq!(span, 8.months().fieldwise()); /// /// // If our second date was just one day less, rounding would truncate /// // down to 6 months! /// let d2 = "2024-08-14".parse::()?; /// let span = d1.until( /// DateDifference::new(d2) /// .smallest(Unit::Month) /// .increment(2) /// .mode(RoundMode::HalfExpand), /// )?; /// assert_eq!(span, 6.months().fieldwise()); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn increment(self, increment: i64) -> DateDifference { DateDifference { round: self.round.increment(increment), ..self } } /// Returns true if and only if this configuration could change the span /// via rounding. #[inline] fn rounding_may_change_span(&self) -> bool { self.round.rounding_may_change_span_ignore_largest() } /// Returns the span of time from `d1` to the date in this configuration. /// The biggest units allowed are determined by the `smallest` and /// `largest` settings, but defaults to `Unit::Day`. #[inline] fn until_with_largest_unit(&self, d1: Date) -> Result { let d2 = self.date; let largest = self .round .get_largest() .unwrap_or_else(|| self.round.get_smallest().max(Unit::Day)); if largest < Unit::Day { // This is the only error case when not rounding! Somewhat // unfortunate. I did consider making this a panic instead, because // we're so close to it being infallible (I think), but I decided // that would be too inconsistent with how we handle invalid units // in other places. (It's just that, in other places, invalid units // are one of a few different kinds of possible errors.) // // Another option would be to just assume `largest` is `Unit::Day` // when it's a smaller unit. // // Yet another option is to split `Unit` into `DateUnit` and // `TimeUnit`, but I found that to be quite awkward (it was the // design I started with). // // NOTE: I take the above back. It's actually possible for the // months component to overflow when largest=month. return Err(err!( "rounding the span between two dates must use days \ or bigger for its units, but found {units}", units = largest.plural(), )); } if largest <= Unit::Week { let mut weeks = t::SpanWeeks::rfrom(C(0)); let mut days = d1.until_days_ranged(d2); if largest == Unit::Week { weeks = days.div_ceil(C(7)).rinto(); days = days.rem_ceil(C(7)); } return Ok(Span::new().weeks_ranged(weeks).days_ranged(days)); } let year1 = d1.year_ranged(); let month1 = d1.month_ranged(); let day1 = d1.day_ranged(); let mut year2 = d2.year_ranged(); let mut month2 = d2.month_ranged(); let day2 = d2.day_ranged(); let mut years = t::SpanYears::rfrom(year2) - t::SpanYears::rfrom(year1); let mut months = t::SpanMonths::rfrom(month2) - t::SpanMonths::rfrom(month1); let mut days = t::SpanDays::rfrom(day2) - t::SpanMonths::rfrom(day1); if years != 0 || months != 0 { let sign = if years != 0 { Sign::rfrom(years.signum()) } else { Sign::rfrom(months.signum()) }; let mut days_in_month1 = t::SpanDays::rfrom(days_in_month(year2, month2)); let mut day_correct = t::SpanDays::N::<0>(); if days.signum() == -sign { let original_days_in_month1 = days_in_month1; let (y, m) = month_add_one(year2, month2, -sign).unwrap(); year2 = y; month2 = m; years = t::SpanYears::rfrom(year2) - t::SpanYears::rfrom(year1); months = t::SpanMonths::rfrom(month2) - t::SpanMonths::rfrom(month1); days_in_month1 = days_in_month(year2, month2).rinto(); day_correct = if sign < 0 { -original_days_in_month1 } else { days_in_month1 }; } let day0_trunc = t::SpanDays::rfrom(day1.min(days_in_month1)); days = t::SpanDays::rfrom(day2) - day0_trunc + day_correct; if years != 0 { months = t::SpanMonths::rfrom(month2) - t::SpanMonths::rfrom(month1); if months.signum() == -sign { let month_correct = if sign < 0 { -t::MONTHS_PER_YEAR } else { t::MONTHS_PER_YEAR }; year2 -= sign; years = t::SpanYears::rfrom(year2) - t::SpanYears::rfrom(year1); months = t::SpanMonths::rfrom(month2) - t::SpanMonths::rfrom(month1) + month_correct; } } } if largest == Unit::Month && years != 0 { months = months.try_checked_add( "months", t::SpanMonths::rfrom(years) * t::MONTHS_PER_YEAR, )?; years = C(0).rinto(); } Ok(Span::new() .years_ranged(years) .months_ranged(months) .days_ranged(days)) } } impl From for DateDifference { #[inline] fn from(date: Date) -> DateDifference { DateDifference::new(date) } } impl From for DateDifference { #[inline] fn from(dt: DateTime) -> DateDifference { DateDifference::from(Date::from(dt)) } } impl From for DateDifference { #[inline] fn from(zdt: Zoned) -> DateDifference { DateDifference::from(Date::from(zdt)) } } impl<'a> From<&'a Zoned> for DateDifference { #[inline] fn from(zdt: &'a Zoned) -> DateDifference { DateDifference::from(zdt.datetime()) } } impl From<(Unit, Date)> for DateDifference { #[inline] fn from((largest, date): (Unit, Date)) -> DateDifference { DateDifference::from(date).largest(largest) } } impl From<(Unit, DateTime)> for DateDifference { #[inline] fn from((largest, dt): (Unit, DateTime)) -> DateDifference { DateDifference::from((largest, Date::from(dt))) } } impl From<(Unit, Zoned)> for DateDifference { #[inline] fn from((largest, zdt): (Unit, Zoned)) -> DateDifference { DateDifference::from((largest, Date::from(zdt))) } } impl<'a> From<(Unit, &'a Zoned)> for DateDifference { #[inline] fn from((largest, zdt): (Unit, &'a Zoned)) -> DateDifference { DateDifference::from((largest, zdt.datetime())) } } /// A builder for setting the fields on a [`Date`]. /// /// This builder is constructed via [`Date::with`]. /// /// # Example /// /// The builder ensures one can chain together the individual components /// of a date without it failing at an intermediate step. For example, /// if you had a date of `2024-10-31` and wanted to change both the day /// and the month, and each setting was validated independent of the other, /// you would need to be careful to set the day first and then the month. /// In some cases, you would need to set the month first and then the day! /// /// But with the builder, you can set values in any order: /// /// ``` /// use jiff::civil::date; /// /// let d1 = date(2024, 10, 31); /// let d2 = d1.with().month(11).day(30).build()?; /// assert_eq!(d2, date(2024, 11, 30)); /// /// let d1 = date(2024, 4, 30); /// let d2 = d1.with().day(31).month(7).build()?; /// assert_eq!(d2, date(2024, 7, 31)); /// /// # Ok::<(), Box>(()) /// ``` #[derive(Clone, Copy, Debug)] pub struct DateWith { original: Date, year: Option, month: Option, day: Option, } impl DateWith { #[inline] fn new(original: Date) -> DateWith { DateWith { original, year: None, month: None, day: None } } /// Create a new `Date` from the fields set on this configuration. /// /// An error occurs when the fields combine to an invalid date. /// /// For any fields not set on this configuration, the values are taken from /// the [`Date`] that originally created this configuration. When no values /// are set, this routine is guaranteed to succeed and will always return /// the original date without modification. /// /// # Example /// /// This creates a date corresponding to the last day in the year: /// /// ``` /// use jiff::civil::date; /// /// assert_eq!( /// date(2023, 1, 1).with().day_of_year_no_leap(365).build()?, /// date(2023, 12, 31), /// ); /// // It also works with leap years for the same input: /// assert_eq!( /// date(2024, 1, 1).with().day_of_year_no_leap(365).build()?, /// date(2024, 12, 31), /// ); /// /// # Ok::<(), Box>(()) /// ``` /// /// # Example: error for invalid date /// /// If the fields combine to form an invalid date, then an error is /// returned: /// /// ``` /// use jiff::civil::date; /// /// let d = date(2024, 11, 30); /// assert!(d.with().day(31).build().is_err()); /// /// let d = date(2024, 2, 29); /// assert!(d.with().year(2023).build().is_err()); /// ``` #[inline] pub fn build(self) -> Result { let year = match self.year { None => self.original.year_ranged(), Some(DateWithYear::Jiff(year)) => Year::try_new("year", year)?, Some(DateWithYear::EraYear(year, Era::CE)) => { let year_ce = t::YearCE::try_new("CE year", year)?; t::Year::try_rfrom("CE year", year_ce)? } Some(DateWithYear::EraYear(year, Era::BCE)) => { let year_bce = t::YearBCE::try_new("BCE year", year)?; t::Year::try_rfrom("BCE year", -year_bce + C(1))? } }; let month = match self.month { None => self.original.month_ranged(), Some(month) => Month::try_new("month", month)?, }; let day = match self.day { None => self.original.day_ranged(), Some(DateWithDay::OfMonth(day)) => Day::try_new("day", day)?, Some(DateWithDay::OfYear(day)) => { return day_of_year(year, day); } Some(DateWithDay::OfYearNoLeap(mut day)) => { type DayOfYear = ri16<1, 365>; let _ = DayOfYear::try_new("day-of-year", day)?; if is_leap_year(year) && day >= 60 { day += 1; } return day_of_year(year, day); } }; Date::new_ranged(year, month, day) } /// Set the year field on a [`Date`]. /// /// One can access this value via [`Date::year`]. /// /// This overrides any previous year settings. /// /// # Errors /// /// This returns an error when [`DateWith::build`] is called if the given /// year is outside the range `-9999..=9999`. This can also return an error /// if the resulting date is otherwise invalid. /// /// # Example /// /// This shows how to create a new date with a different year: /// /// ``` /// use jiff::civil::date; /// /// let d1 = date(2005, 11, 5); /// assert_eq!(d1.year(), 2005); /// let d2 = d1.with().year(2007).build()?; /// assert_eq!(d2.year(), 2007); /// /// # Ok::<(), Box>(()) /// ``` /// /// # Example: only changing the year can fail /// /// For example, while `2024-02-29` is valid, `2023-02-29` is not: /// /// ``` /// use jiff::civil::date; /// /// let d1 = date(2024, 2, 29); /// assert!(d1.with().year(2023).build().is_err()); /// ``` #[inline] pub fn year(self, year: i16) -> DateWith { DateWith { year: Some(DateWithYear::Jiff(year)), ..self } } /// Set year of a date via its era and its non-negative numeric component. /// /// One can access this value via [`Date::era_year`]. /// /// # Errors /// /// This returns an error when [`DateWith::build`] is called if the year is /// outside the range for the era specified. For [`Era::BCE`], the range is /// `1..=10000`. For [`Era::CE`], the range is `1..=9999`. /// /// # Example /// /// This shows that `CE` years are equivalent to the years used by this /// crate: /// /// ``` /// use jiff::civil::{Era, date}; /// /// let d1 = date(2005, 11, 5); /// assert_eq!(d1.year(), 2005); /// let d2 = d1.with().era_year(2007, Era::CE).build()?; /// assert_eq!(d2.year(), 2007); /// /// // CE years are always positive and can be at most 9999: /// assert!(d1.with().era_year(-5, Era::CE).build().is_err()); /// assert!(d1.with().era_year(10_000, Era::CE).build().is_err()); /// /// # Ok::<(), Box>(()) /// ``` /// /// But `BCE` years always correspond to years less than or equal to `0` /// in this crate: /// /// ``` /// use jiff::civil::{Era, date}; /// /// let d1 = date(-27, 7, 1); /// assert_eq!(d1.year(), -27); /// assert_eq!(d1.era_year(), (28, Era::BCE)); /// /// let d2 = d1.with().era_year(509, Era::BCE).build()?; /// assert_eq!(d2.year(), -508); /// assert_eq!(d2.era_year(), (509, Era::BCE)); /// /// let d2 = d1.with().era_year(10_000, Era::BCE).build()?; /// assert_eq!(d2.year(), -9_999); /// assert_eq!(d2.era_year(), (10_000, Era::BCE)); /// /// // BCE years are always positive and can be at most 10000: /// assert!(d1.with().era_year(-5, Era::BCE).build().is_err()); /// assert!(d1.with().era_year(10_001, Era::BCE).build().is_err()); /// /// # Ok::<(), Box>(()) /// ``` /// /// # Example: overrides `DateWith::year` /// /// Setting this option will override any previous `DateWith::year` /// option: /// /// ``` /// use jiff::civil::{Era, date}; /// /// let d1 = date(2024, 7, 2); /// let d2 = d1.with().year(2000).era_year(1900, Era::CE).build()?; /// assert_eq!(d2, date(1900, 7, 2)); /// /// # Ok::<(), Box>(()) /// ``` /// /// Similarly, `DateWith::year` will override any previous call to /// `DateWith::era_year`: /// /// ``` /// use jiff::civil::{Era, date}; /// /// let d1 = date(2024, 7, 2); /// let d2 = d1.with().era_year(1900, Era::CE).year(2000).build()?; /// assert_eq!(d2, date(2000, 7, 2)); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn era_year(self, year: i16, era: Era) -> DateWith { DateWith { year: Some(DateWithYear::EraYear(year, era)), ..self } } /// Set the month field on a [`Date`]. /// /// One can access this value via [`Date::month`]. /// /// This overrides any previous month settings. /// /// # Errors /// /// This returns an error when [`DateWith::build`] is called if the given /// month is outside the range `1..=12`. This can also return an error if /// the resulting date is otherwise invalid. /// /// # Example /// /// This shows how to create a new date with a different month: /// /// ``` /// use jiff::civil::date; /// /// let d1 = date(2005, 11, 5); /// assert_eq!(d1.month(), 11); /// let d2 = d1.with().month(6).build()?; /// assert_eq!(d2.month(), 6); /// /// # Ok::<(), Box>(()) /// ``` /// /// # Example: only changing the month can fail /// /// For example, while `2024-10-31` is valid, `2024-11-31` is not: /// /// ``` /// use jiff::civil::date; /// /// let d = date(2024, 10, 31); /// assert!(d.with().month(11).build().is_err()); /// ``` #[inline] pub fn month(self, month: i8) -> DateWith { DateWith { month: Some(month), ..self } } /// Set the day field on a [`Date`]. /// /// One can access this value via [`Date::day`]. /// /// This overrides any previous day settings. /// /// # Errors /// /// This returns an error when [`DateWith::build`] is called if the given /// given day is outside of allowable days for the corresponding year and /// month fields. /// /// # Example /// /// This shows some examples of setting the day, including a leap day: /// /// ``` /// use jiff::civil::date; /// /// let d1 = date(2024, 2, 5); /// assert_eq!(d1.day(), 5); /// let d2 = d1.with().day(10).build()?; /// assert_eq!(d2.day(), 10); /// let d3 = d1.with().day(29).build()?; /// assert_eq!(d3.day(), 29); /// /// # Ok::<(), Box>(()) /// ``` /// /// # Example: changing only the day can fail /// /// This shows some examples that will fail: /// /// ``` /// use jiff::civil::date; /// /// let d1 = date(2023, 2, 5); /// // 2023 is not a leap year /// assert!(d1.with().day(29).build().is_err()); /// /// // September has 30 days, not 31. /// let d1 = date(2023, 9, 5); /// assert!(d1.with().day(31).build().is_err()); /// ``` #[inline] pub fn day(self, day: i8) -> DateWith { DateWith { day: Some(DateWithDay::OfMonth(day)), ..self } } /// Set the day field on a [`Date`] via the ordinal number of a day within /// a year. /// /// When used, any settings for month are ignored since the month is /// determined by the day of the year. /// /// The valid values for `day` are `1..=366`. Note though that `366` is /// only valid for leap years. /// /// This overrides any previous day settings. /// /// # Errors /// /// This returns an error when [`DateWith::build`] is called if the given /// day is outside the allowed range of `1..=366`, or when a value of `366` /// is given for a non-leap year. /// /// # Example /// /// This demonstrates that if a year is a leap year, then `60` corresponds /// to February 29: /// /// ``` /// use jiff::civil::date; /// /// let d = date(2024, 1, 1); /// assert_eq!(d.with().day_of_year(60).build()?, date(2024, 2, 29)); /// /// # Ok::<(), Box>(()) /// ``` /// /// But for non-leap years, day 60 is March 1: /// /// ``` /// use jiff::civil::date; /// /// let d = date(2023, 1, 1); /// assert_eq!(d.with().day_of_year(60).build()?, date(2023, 3, 1)); /// /// # Ok::<(), Box>(()) /// ``` /// /// And using `366` for a non-leap year will result in an error, since /// non-leap years only have 365 days: /// /// ``` /// use jiff::civil::date; /// /// let d = date(2023, 1, 1); /// assert!(d.with().day_of_year(366).build().is_err()); /// // The maximal year is not a leap year, so it returns an error too. /// let d = date(9999, 1, 1); /// assert!(d.with().day_of_year(366).build().is_err()); /// ``` #[inline] pub fn day_of_year(self, day: i16) -> DateWith { DateWith { day: Some(DateWithDay::OfYear(day)), ..self } } /// Set the day field on a [`Date`] via the ordinal number of a day within /// a year, but ignoring leap years. /// /// When used, any settings for month are ignored since the month is /// determined by the day of the year. /// /// The valid values for `day` are `1..=365`. The value `365` always /// corresponds to the last day of the year, even for leap years. It is /// impossible for this routine to return a date corresponding to February /// 29. /// /// This overrides any previous day settings. /// /// # Errors /// /// This returns an error when [`DateWith::build`] is called if the given /// day is outside the allowed range of `1..=365`. /// /// # Example /// /// This demonstrates that `60` corresponds to March 1, regardless of /// whether the year is a leap year or not: /// /// ``` /// use jiff::civil::date; /// /// assert_eq!( /// date(2023, 1, 1).with().day_of_year_no_leap(60).build()?, /// date(2023, 3, 1), /// ); /// /// assert_eq!( /// date(2024, 1, 1).with().day_of_year_no_leap(60).build()?, /// date(2024, 3, 1), /// ); /// /// # Ok::<(), Box>(()) /// ``` /// /// And using `365` for any year will always yield the last day of the /// year: /// /// ``` /// use jiff::civil::date; /// /// let d = date(2023, 1, 1); /// assert_eq!( /// d.with().day_of_year_no_leap(365).build()?, /// d.last_of_year(), /// ); /// /// let d = date(2024, 1, 1); /// assert_eq!( /// d.with().day_of_year_no_leap(365).build()?, /// d.last_of_year(), /// ); /// /// let d = date(9999, 1, 1); /// assert_eq!( /// d.with().day_of_year_no_leap(365).build()?, /// d.last_of_year(), /// ); /// /// # Ok::<(), Box>(()) /// ``` /// /// A value of `366` is out of bounds, even for leap years: /// /// ``` /// use jiff::civil::date; /// /// let d = date(2024, 1, 1); /// assert!(d.with().day_of_year_no_leap(366).build().is_err()); /// ``` #[inline] pub fn day_of_year_no_leap(self, day: i16) -> DateWith { DateWith { day: Some(DateWithDay::OfYearNoLeap(day)), ..self } } } /// Encodes the "with year" option of [`DateWith`]. /// /// This encodes the invariant that `DateWith::year` and `DateWith::era_year` /// are mutually exclusive and override each other. #[derive(Clone, Copy, Debug)] enum DateWithYear { Jiff(i16), EraYear(i16, Era), } /// Encodes the "with day" option of [`DateWith`]. /// /// This encodes the invariant that `DateWith::day`, `DateWith::day_of_year` /// and `DateWith::day_of_year_no_leap` are all mutually exclusive and override /// each other. /// /// Note that when "day of year" or "day of year no leap" are used, then if a /// day of month is set, it is ignored. #[derive(Clone, Copy, Debug)] enum DateWithDay { OfMonth(i8), OfYear(i16), OfYearNoLeap(i16), } /// Returns the Unix epoch day corresponding to the first day in the ISO 8601 /// week year given. /// /// Ref: http://howardhinnant.github.io/date_algorithms.html fn iso_week_start_from_year(year: impl RInto) -> UnixEpochDays { let year = year.rinto(); // A week's year always corresponds to the Gregorian year in which the // Thursday of that week falls. Therefore, Jan 4 is *always* in the first // week of any ISO week year. let date_in_first_week = Date::new_ranged(year, C(1), C(4)) .expect("Jan 4 is valid for all valid years"); // The start of the first week is a Monday, so find the number of days // since Monday from a date that we know is in the first ISO week of // `year`. let diff_from_monday = date_in_first_week.weekday().since_ranged(Weekday::Monday); date_in_first_week.to_unix_epoch_days() - diff_from_monday } /// Returns the weekday for the given number of days since the Unix epoch. fn weekday_from_unix_epoch_days(days: impl RInto) -> Weekday { // Based on Hinnant's approach here, although we use ISO weekday numbering // by default. Basically, this works by using the knowledge that 1970-01-01 // was a Thursday. // // Ref: http://howardhinnant.github.io/date_algorithms.html let days = days.rinto(); Weekday::from_monday_zero_offset_ranged((days + C(3)) % C(7)) } /// Adds or subtracts `sign` from the given `year`/`month`. /// /// If month overflows in either direction, then the `year` returned is /// adjusted as appropriate. fn month_add_one( year: impl RInto, month: impl RInto, sign: impl RInto, ) -> Result<(Year, Month), Error> { let mut year = year.rinto(); let mut month = month.rinto(); let delta = sign.rinto(); month += delta; if month < 1 { year -= C(1); month += t::MONTHS_PER_YEAR; } else if month > t::MONTHS_PER_YEAR { year += C(1); month -= t::MONTHS_PER_YEAR; } let year = Year::try_rfrom("year", year)?; let month = Month::try_rfrom("year", month)?; Ok((year, month)) } /// Adds the given span of months to the `month` given. /// /// If adding (or subtracting) would result in overflowing the `month` value, /// then the amount by which it overflowed, in units of years, is returned. For /// example, adding 14 months to the month `3` (March) will result in returning /// the month `5` (May) with `1` year of overflow. fn month_add_overflowing( month: impl RInto, span: impl RInto, ) -> (t::Month, t::SpanYears) { let month = t::SpanMonths::rfrom(month.rinto()); let span = span.rinto(); let total = month - C(1) + span; let years = total / C(12); let month = (total % C(12)) + C(1); (month.rinto(), years.rinto()) } fn day_of_year(year: Year, day: i16) -> Result { let day = t::DayOfYear::try_new("day-of-year", day)?; let span = Span::new().days_ranged(day - C(1)); let start = Date::new_ranged(year, C(1), C(1))?; let end = start.checked_add(span)?; // If we overflowed into the next year, then `day` is too big. if start.year() != end.year() { // Can only happen given day=366 and this is a leap year. debug_assert_eq!(day, 366); debug_assert!(!start.in_leap_year()); return Err(Error::range("day-of-year", day, 1, 365)); } Ok(end) } #[cfg(test)] mod tests { use std::io::Cursor; use crate::{civil::date, span::span_eq, tz::TimeZone, Timestamp, ToSpan}; use super::*; #[test] fn t_from_unix() { fn date_from_timestamp(timestamp: Timestamp) -> Date { timestamp.to_zoned(TimeZone::UTC).datetime().date() } assert_eq!( date(1970, 1, 1), date_from_timestamp(Timestamp::new(0, 0).unwrap()), ); assert_eq!( date(1969, 12, 31), date_from_timestamp(Timestamp::new(-1, 0).unwrap()), ); assert_eq!( date(1969, 12, 31), date_from_timestamp(Timestamp::new(-86_400, 0).unwrap()), ); assert_eq!( date(1969, 12, 30), date_from_timestamp(Timestamp::new(-86_401, 0).unwrap()), ); assert_eq!( date(-9999, 1, 2), date_from_timestamp( Timestamp::new(t::UnixSeconds::MIN_REPR, 0).unwrap() ), ); assert_eq!( date(9999, 12, 30), date_from_timestamp( Timestamp::new(t::UnixSeconds::MAX_REPR, 0).unwrap() ), ); } #[test] fn all_days_to_date_roundtrip() { for rd in -100_000..=100_000 { let rd = UnixEpochDays::new(rd).unwrap(); let date = Date::from_unix_epoch_days(rd); let got = date.to_unix_epoch_days(); assert_eq!(rd, got, "for date {date:?}"); } } #[test] fn all_date_to_days_roundtrip() { use crate::util::common::days_in_month; let year_range = 2000..=2500; for year in year_range { let year = Year::new(year).unwrap(); for month in Month::MIN_REPR..=Month::MAX_REPR { let month = Month::new(month).unwrap(); for day in 1..=days_in_month(year, month).get() { let date = date(year.get(), month.get(), day); let rd = date.to_unix_epoch_days(); let got = Date::from_unix_epoch_days(rd); assert_eq!(date, got, "for date {date:?}"); } } } } #[test] fn all_date_to_iso_week_date_roundtrip() { use crate::util::common::days_in_month; let year_range = 2000..=2500; for year in year_range { let year = Year::new(year).unwrap(); for month in [1, 2, 4] { let month = Month::new(month).unwrap(); for day in 20..=days_in_month(year, month).get() { let date = date(year.get(), month.get(), day); let wd = date.iso_week_date(); let got = wd.date(); assert_eq!( date, got, "for date {date:?}, and ISO week date {wd:?}" ); } } } } #[test] fn add_constrained() { use crate::ToSpan; let d1 = date(2023, 3, 31); let d2 = d1.checked_add(1.months().days(1)).unwrap(); assert_eq!(d2, date(2023, 5, 1)); } #[test] fn since_years() { let d1 = date(2023, 4, 15); let d2 = date(2019, 2, 22); let span = d1.since((Unit::Year, d2)).unwrap(); span_eq!(span, 4.years().months(1).days(21)); let span = d2.since((Unit::Year, d1)).unwrap(); span_eq!(span, -4.years().months(1).days(24)); let d1 = date(2023, 2, 22); let d2 = date(2019, 4, 15); let span = d1.since((Unit::Year, d2)).unwrap(); span_eq!(span, 3.years().months(10).days(7)); let span = d2.since((Unit::Year, d1)).unwrap(); span_eq!(span, -3.years().months(10).days(7)); let d1 = date(9999, 12, 31); let d2 = date(-9999, 1, 1); let span = d1.since((Unit::Year, d2)).unwrap(); span_eq!(span, 19998.years().months(11).days(30)); let span = d2.since((Unit::Year, d1)).unwrap(); span_eq!(span, -19998.years().months(11).days(30)); } #[test] fn since_months() { let d1 = date(2024, 7, 24); let d2 = date(2024, 2, 22); let span = d1.since((Unit::Month, d2)).unwrap(); span_eq!(span, 5.months().days(2)); let span = d2.since((Unit::Month, d1)).unwrap(); span_eq!(span, -5.months().days(2)); assert_eq!(d2, d1.checked_sub(5.months().days(2)).unwrap()); assert_eq!(d1, d2.checked_sub(-5.months().days(2)).unwrap()); let d1 = date(2024, 7, 15); let d2 = date(2024, 2, 22); let span = d1.since((Unit::Month, d2)).unwrap(); span_eq!(span, 4.months().days(22)); let span = d2.since((Unit::Month, d1)).unwrap(); span_eq!(span, -4.months().days(23)); assert_eq!(d2, d1.checked_sub(4.months().days(22)).unwrap()); assert_eq!(d1, d2.checked_sub(-4.months().days(23)).unwrap()); let d1 = date(2023, 4, 15); let d2 = date(2023, 2, 22); let span = d1.since((Unit::Month, d2)).unwrap(); span_eq!(span, 1.month().days(21)); let span = d2.since((Unit::Month, d1)).unwrap(); span_eq!(span, -1.month().days(24)); assert_eq!(d2, d1.checked_sub(1.month().days(21)).unwrap()); assert_eq!(d1, d2.checked_sub(-1.month().days(24)).unwrap()); let d1 = date(2023, 4, 15); let d2 = date(2019, 2, 22); let span = d1.since((Unit::Month, d2)).unwrap(); span_eq!(span, 49.months().days(21)); let span = d2.since((Unit::Month, d1)).unwrap(); span_eq!(span, -49.months().days(24)); } #[test] fn since_weeks() { let d1 = date(2024, 7, 15); let d2 = date(2024, 6, 22); let span = d1.since((Unit::Week, d2)).unwrap(); span_eq!(span, 3.weeks().days(2)); let span = d2.since((Unit::Week, d1)).unwrap(); span_eq!(span, -3.weeks().days(2)); } #[test] fn since_days() { let d1 = date(2024, 7, 15); let d2 = date(2024, 2, 22); let span = d1.since((Unit::Day, d2)).unwrap(); span_eq!(span, 144.days()); let span = d2.since((Unit::Day, d1)).unwrap(); span_eq!(span, -144.days()); } #[test] fn until_month_lengths() { let jan1 = date(2020, 1, 1); let feb1 = date(2020, 2, 1); let mar1 = date(2020, 3, 1); span_eq!(jan1.until(feb1).unwrap(), 31.days()); span_eq!(jan1.until((Unit::Month, feb1)).unwrap(), 1.month()); span_eq!(feb1.until(mar1).unwrap(), 29.days()); span_eq!(feb1.until((Unit::Month, mar1)).unwrap(), 1.month()); span_eq!(jan1.until(mar1).unwrap(), 60.days()); span_eq!(jan1.until((Unit::Month, mar1)).unwrap(), 2.months()); } // Ref: https://github.com/tc39/proposal-temporal/issues/2845#issuecomment-2121057896 #[test] fn since_until_not_commutative() { // Temporal.PlainDate.from("2020-04-30").since("2020-02-29", {largestUnit: "months"}) // // => P2M // Temporal.PlainDate.from("2020-02-29").until("2020-04-30", {largestUnit: "months"}) // // => P2M1D let d1 = date(2020, 4, 30); let d2 = date(2020, 2, 29); let since = d1.since((Unit::Month, d2)).unwrap(); span_eq!(since, 2.months()); let until = d2.until((Unit::Month, d1)).unwrap(); span_eq!(until, 2.months().days(1)); } // Ref: https://github.com/tc39/proposal-temporal/issues/2893 #[test] fn until_weeks_round() { use crate::{RoundMode, SpanRound}; let earlier = date(2019, 1, 8); let later = date(2021, 9, 7); let span = earlier.until((Unit::Week, later)).unwrap(); span_eq!(span, 139.weeks()); let options = SpanRound::new() .smallest(Unit::Week) .mode(RoundMode::HalfExpand) .relative(earlier.to_datetime(Time::midnight())); let rounded = span.round(options).unwrap(); span_eq!(rounded, 139.weeks()); } // This test checks current behavior, but I think it's wrong. I think the // results below should be 11 months and 1 month. // // Ref: https://github.com/tc39/proposal-temporal/issues/2919 #[test] fn until_months_no_balance() { let sp = date(2023, 5, 31).until((Unit::Month, date(2024, 4, 30))).unwrap(); span_eq!(sp, 10.months().days(30)); let sp = date(2023, 5, 31).until((Unit::Month, date(2023, 6, 30))).unwrap(); span_eq!(sp, 30.days()); } #[test] fn test_month_add() { let add = |year: i16, month: i8, delta: i8| -> Result<(i16, i8), Error> { let year = Year::new(year).unwrap(); let month = Month::new(month).unwrap(); let delta = Sign::new(delta).unwrap(); let (year, month) = month_add_one(year, month, delta)?; Ok((year.get(), month.get())) }; assert_eq!(add(2024, 1, 1).unwrap(), (2024, 2)); assert_eq!(add(2024, 1, -1).unwrap(), (2023, 12)); assert_eq!(add(2024, 12, 1).unwrap(), (2025, 1)); assert_eq!(add(9999, 12, -1).unwrap(), (9999, 11)); assert_eq!(add(-9999, 1, 1).unwrap(), (-9999, 2)); assert!(add(9999, 12, 1).is_err()); assert!(add(-9999, 1, -1).is_err()); } #[test] fn test_month_add_overflowing() { let month_add = |month, span| { let month = t::Month::new(month).unwrap(); let span = t::SpanMonths::new(span).unwrap(); let (month, years) = month_add_overflowing(month, span); (month.get(), years.get()) }; assert_eq!((1, 0), month_add(1, 0)); assert_eq!((12, 0), month_add(1, 11)); assert_eq!((1, 1), month_add(1, 12)); assert_eq!((2, 1), month_add(1, 13)); assert_eq!((9, 1), month_add(1, 20)); assert_eq!((12, 19998), month_add(12, t::SpanMonths::MAX_REPR)); assert_eq!((12, -1), month_add(1, -1)); assert_eq!((11, -1), month_add(1, -2)); assert_eq!((1, -1), month_add(1, -12)); assert_eq!((12, -2), month_add(1, -13)); } #[test] fn date_size() { #[cfg(debug_assertions)] { assert_eq!(12, core::mem::size_of::()); } #[cfg(not(debug_assertions))] { assert_eq!(4, core::mem::size_of::()); } } quickcheck::quickcheck! { fn prop_checked_add_then_sub( d1: Date, span: Span ) -> quickcheck::TestResult { // Force our span to have no units greater than days. let span = if span.largest_unit() <= Unit::Day { span } else { let round = SpanRound::new().largest(Unit::Day).relative(d1); let Ok(span) = span.round(round) else { return quickcheck::TestResult::discard(); }; span }; let Ok(d2) = d1.checked_add(span) else { return quickcheck::TestResult::discard(); }; let got = d2.checked_sub(span).unwrap(); quickcheck::TestResult::from_bool(d1 == got) } fn prop_checked_sub_then_add( d1: Date, span: Span ) -> quickcheck::TestResult { // Force our span to have no units greater than days. let span = if span.largest_unit() <= Unit::Day { span } else { let round = SpanRound::new().largest(Unit::Day).relative(d1); let Ok(span) = span.round(round) else { return quickcheck::TestResult::discard(); }; span }; let Ok(d2) = d1.checked_sub(span) else { return quickcheck::TestResult::discard(); }; let got = d2.checked_add(span).unwrap(); quickcheck::TestResult::from_bool(d1 == got) } fn prop_since_then_add(d1: Date, d2: Date) -> bool { let span = d1.since(d2).unwrap(); let got = d2.checked_add(span).unwrap(); d1 == got } fn prop_until_then_sub(d1: Date, d2: Date) -> bool { let span = d1.until(d2).unwrap(); let got = d2.checked_sub(span).unwrap(); d1 == got } } /// # `serde` deserializer compatibility test /// /// Serde YAML used to be unable to deserialize `jiff` types, /// as deserializing from bytes is not supported by the deserializer. /// /// - /// - #[test] fn civil_date_deserialize_yaml() { let expected = date(2024, 10, 31); let deserialized: Date = serde_yaml::from_str("2024-10-31").unwrap(); assert_eq!(deserialized, expected); let deserialized: Date = serde_yaml::from_slice("2024-10-31".as_bytes()).unwrap(); assert_eq!(deserialized, expected); let cursor = Cursor::new(b"2024-10-31"); let deserialized: Date = serde_yaml::from_reader(cursor).unwrap(); assert_eq!(deserialized, expected); } } jiff-0.1.28/src/civil/datetime.rs000064400000000000000000004535361046102023000147110ustar 00000000000000use core::time::Duration as UnsignedDuration; use crate::{ civil::{ datetime, Date, DateWith, Era, ISOWeekDate, Time, TimeWith, Weekday, }, duration::{Duration, SDuration}, error::{err, Error, ErrorContext}, fmt::{ self, temporal::{self, DEFAULT_DATETIME_PARSER}, }, tz::TimeZone, util::{ rangeint::{RFrom, RInto}, round::increment, t::{self, C}, }, zoned::Zoned, RoundMode, SignedDuration, Span, SpanRound, Unit, }; /// A representation of a civil datetime in the Gregorian calendar. /// /// A `DateTime` value corresponds to a pair of a [`Date`] and a [`Time`]. /// That is, a datetime contains a year, month, day, hour, minute, second and /// the fractional number of nanoseconds. /// /// A `DateTime` value is guaranteed to contain a valid date and time. For /// example, neither `2023-02-29T00:00:00` nor `2015-06-30T23:59:60` are /// valid `DateTime` values. /// /// # Civil datetimes /// /// A `DateTime` value behaves without regard to daylight saving time or time /// zones in general. When doing arithmetic on datetimes with spans defined in /// units of time (such as with [`DateTime::checked_add`]), days are considered /// to always be precisely `86,400` seconds long. /// /// # Parsing and printing /// /// The `DateTime` type provides convenient trait implementations of /// [`std::str::FromStr`] and [`std::fmt::Display`]: /// /// ``` /// use jiff::civil::DateTime; /// /// let dt: DateTime = "2024-06-19 15:22:45".parse()?; /// assert_eq!(dt.to_string(), "2024-06-19T15:22:45"); /// /// # Ok::<(), Box>(()) /// ``` /// /// A civil `DateTime` can also be parsed from something that _contains_ a /// datetime, but with perhaps other data (such as an offset or time zone): /// /// ``` /// use jiff::civil::DateTime; /// /// let dt: DateTime = "2024-06-19T15:22:45-04[America/New_York]".parse()?; /// assert_eq!(dt.to_string(), "2024-06-19T15:22:45"); /// /// # Ok::<(), Box>(()) /// ``` /// /// For more information on the specific format supported, see the /// [`fmt::temporal`](crate::fmt::temporal) module documentation. /// /// # Default value /// /// For convenience, this type implements the `Default` trait. Its default /// value corresponds to `0000-01-01T00:00:00.000000000`. That is, it is /// the datetime corresponding to `DateTime::from_parts(Date::default(), /// Time::default())`. One can also access this value via the `DateTime::ZERO` /// constant. /// /// # Leap seconds /// /// Jiff does not support leap seconds. Jiff behaves as if they don't exist. /// The only exception is that if one parses a datetime with a second component /// of `60`, then it is automatically constrained to `59`: /// /// ``` /// use jiff::civil::{DateTime, date}; /// /// let dt: DateTime = "2016-12-31 23:59:60".parse()?; /// assert_eq!(dt, date(2016, 12, 31).at(23, 59, 59, 0)); /// /// # Ok::<(), Box>(()) /// ``` /// /// # Comparisons /// /// The `DateTime` type provides both `Eq` and `Ord` trait implementations to /// facilitate easy comparisons. When a datetime `dt1` occurs before a datetime /// `dt2`, then `dt1 < dt2`. For example: /// /// ``` /// use jiff::civil::date; /// /// let dt1 = date(2024, 3, 11).at(1, 25, 15, 0); /// let dt2 = date(2025, 1, 31).at(0, 30, 0, 0); /// assert!(dt1 < dt2); /// ``` /// /// # Arithmetic /// /// This type provides routines for adding and subtracting spans of time, as /// well as computing the span of time between two `DateTime` values. /// /// For adding or subtracting spans of time, one can use any of the following /// routines: /// /// * [`DateTime::checked_add`] or [`DateTime::checked_sub`] for checked /// arithmetic. /// * [`DateTime::saturating_add`] or [`DateTime::saturating_sub`] for /// saturating arithmetic. /// /// Additionally, checked arithmetic is available via the `Add` and `Sub` /// trait implementations. When the result overflows, a panic occurs. /// /// ``` /// use jiff::{civil::date, ToSpan}; /// /// let start = date(2024, 2, 25).at(15, 45, 0, 0); /// let one_week_later = start + 1.weeks(); /// assert_eq!(one_week_later, date(2024, 3, 3).at(15, 45, 0, 0)); /// ``` /// /// One can compute the span of time between two datetimes using either /// [`DateTime::until`] or [`DateTime::since`]. It's also possible to subtract /// two `DateTime` values directly via a `Sub` trait implementation: /// /// ``` /// use jiff::{civil::date, ToSpan}; /// /// let datetime1 = date(2024, 5, 3).at(23, 30, 0, 0); /// let datetime2 = date(2024, 2, 25).at(7, 0, 0, 0); /// assert_eq!( /// datetime1 - datetime2, /// 68.days().hours(16).minutes(30).fieldwise(), /// ); /// ``` /// /// The `until` and `since` APIs are polymorphic and allow re-balancing and /// rounding the span returned. For example, the default largest unit is days /// (as exemplified above), but we can ask for bigger units: /// /// ``` /// use jiff::{civil::date, ToSpan, Unit}; /// /// let datetime1 = date(2024, 5, 3).at(23, 30, 0, 0); /// let datetime2 = date(2024, 2, 25).at(7, 0, 0, 0); /// assert_eq!( /// datetime1.since((Unit::Year, datetime2))?, /// 2.months().days(7).hours(16).minutes(30).fieldwise(), /// ); /// /// # Ok::<(), Box>(()) /// ``` /// /// Or even round the span returned: /// /// ``` /// use jiff::{civil::{DateTimeDifference, date}, RoundMode, ToSpan, Unit}; /// /// let datetime1 = date(2024, 5, 3).at(23, 30, 0, 0); /// let datetime2 = date(2024, 2, 25).at(7, 0, 0, 0); /// assert_eq!( /// datetime1.since( /// DateTimeDifference::new(datetime2) /// .smallest(Unit::Day) /// .largest(Unit::Year), /// )?, /// 2.months().days(7).fieldwise(), /// ); /// // `DateTimeDifference` uses truncation as a rounding mode by default, /// // but you can set the rounding mode to break ties away from zero: /// assert_eq!( /// datetime1.since( /// DateTimeDifference::new(datetime2) /// .smallest(Unit::Day) /// .largest(Unit::Year) /// .mode(RoundMode::HalfExpand), /// )?, /// // Rounds up to 8 days. /// 2.months().days(8).fieldwise(), /// ); /// /// # Ok::<(), Box>(()) /// ``` /// /// # Rounding /// /// A `DateTime` can be rounded based on a [`DateTimeRound`] configuration of /// smallest units, rounding increment and rounding mode. Here's an example /// showing how to round to the nearest third hour: /// /// ``` /// use jiff::{civil::{DateTimeRound, date}, Unit}; /// /// let dt = date(2024, 6, 19).at(16, 27, 29, 999_999_999); /// assert_eq!( /// dt.round(DateTimeRound::new().smallest(Unit::Hour).increment(3))?, /// date(2024, 6, 19).at(15, 0, 0, 0), /// ); /// // Or alternatively, make use of the `From<(Unit, i64)> for DateTimeRound` /// // trait implementation: /// assert_eq!( /// dt.round((Unit::Hour, 3))?, /// date(2024, 6, 19).at(15, 0, 0, 0), /// ); /// /// # Ok::<(), Box>(()) /// ``` /// /// See [`DateTime::round`] for more details. #[derive(Clone, Copy, Eq, Hash, PartialEq, PartialOrd, Ord)] pub struct DateTime { date: Date, time: Time, } impl DateTime { /// The minimum representable Gregorian datetime. /// /// The minimum is chosen such that any [`Timestamp`](crate::Timestamp) /// combined with any valid time zone offset can be infallibly converted to /// this type. pub const MIN: DateTime = datetime(-9999, 1, 1, 0, 0, 0, 0); /// The maximum representable Gregorian datetime. /// /// The maximum is chosen such that any [`Timestamp`](crate::Timestamp) /// combined with any valid time zone offset can be infallibly converted to /// this type. pub const MAX: DateTime = datetime(9999, 12, 31, 23, 59, 59, 999_999_999); /// The first day of the zeroth year. /// /// This is guaranteed to be equivalent to `DateTime::default()`. /// /// # Example /// /// ``` /// use jiff::civil::DateTime; /// /// assert_eq!(DateTime::ZERO, DateTime::default()); /// ``` pub const ZERO: DateTime = DateTime::from_parts(Date::ZERO, Time::MIN); /// Creates a new `DateTime` value from its component year, month, day, /// hour, minute, second and fractional subsecond (up to nanosecond /// precision) values. /// /// To create a new datetime from another with a particular component, use /// the methods on [`DateTimeWith`] via [`DateTime::with`]. /// /// # Errors /// /// This returns an error when the given components do not correspond to a /// valid datetime. Namely, all of the following must be true: /// /// * The year must be in the range `-9999..=9999`. /// * The month must be in the range `1..=12`. /// * The day must be at least `1` and must be at most the number of days /// in the corresponding month. So for example, `2024-02-29` is valid but /// `2023-02-29` is not. /// * `0 <= hour <= 23` /// * `0 <= minute <= 59` /// * `0 <= second <= 59` /// * `0 <= subsec_nanosecond <= 999,999,999` /// /// # Example /// /// This shows an example of a valid datetime: /// /// ``` /// use jiff::civil::DateTime; /// /// let d = DateTime::new(2024, 2, 29, 21, 30, 5, 123_456_789).unwrap(); /// assert_eq!(d.year(), 2024); /// assert_eq!(d.month(), 2); /// assert_eq!(d.day(), 29); /// assert_eq!(d.hour(), 21); /// assert_eq!(d.minute(), 30); /// assert_eq!(d.second(), 5); /// assert_eq!(d.millisecond(), 123); /// assert_eq!(d.microsecond(), 456); /// assert_eq!(d.nanosecond(), 789); /// ``` /// /// This shows some examples of invalid datetimes: /// /// ``` /// use jiff::civil::DateTime; /// /// assert!(DateTime::new(2023, 2, 29, 21, 30, 5, 0).is_err()); /// assert!(DateTime::new(2015, 6, 30, 23, 59, 60, 0).is_err()); /// assert!(DateTime::new(2024, 6, 20, 19, 58, 0, 1_000_000_000).is_err()); /// ``` #[inline] pub fn new( year: i16, month: i8, day: i8, hour: i8, minute: i8, second: i8, subsec_nanosecond: i32, ) -> Result { let date = Date::new(year, month, day)?; let time = Time::new(hour, minute, second, subsec_nanosecond)?; Ok(DateTime { date, time }) } /// Creates a new `DateTime` value in a `const` context. /// /// Note that an alternative syntax that is terser and perhaps easier to /// read for the same operation is to combine /// [`civil::date`](crate::civil::date()) with [`Date::at`]. /// /// # Panics /// /// This routine panics when [`DateTime::new`] would return an error. That /// is, when the given components do not correspond to a valid datetime. /// Namely, all of the following must be true: /// /// * The year must be in the range `-9999..=9999`. /// * The month must be in the range `1..=12`. /// * The day must be at least `1` and must be at most the number of days /// in the corresponding month. So for example, `2024-02-29` is valid but /// `2023-02-29` is not. /// * `0 <= hour <= 23` /// * `0 <= minute <= 59` /// * `0 <= second <= 59` /// * `0 <= subsec_nanosecond <= 999,999,999` /// /// Similarly, when used in a const context, invalid parameters will /// prevent your Rust program from compiling. /// /// # Example /// /// ``` /// use jiff::civil::DateTime; /// /// let dt = DateTime::constant(2024, 2, 29, 21, 30, 5, 123_456_789); /// assert_eq!(dt.year(), 2024); /// assert_eq!(dt.month(), 2); /// assert_eq!(dt.day(), 29); /// assert_eq!(dt.hour(), 21); /// assert_eq!(dt.minute(), 30); /// assert_eq!(dt.second(), 5); /// assert_eq!(dt.millisecond(), 123); /// assert_eq!(dt.microsecond(), 456); /// assert_eq!(dt.nanosecond(), 789); /// ``` /// /// Or alternatively: /// /// ``` /// use jiff::civil::date; /// /// let dt = date(2024, 2, 29).at(21, 30, 5, 123_456_789); /// assert_eq!(dt.year(), 2024); /// assert_eq!(dt.month(), 2); /// assert_eq!(dt.day(), 29); /// assert_eq!(dt.hour(), 21); /// assert_eq!(dt.minute(), 30); /// assert_eq!(dt.second(), 5); /// assert_eq!(dt.millisecond(), 123); /// assert_eq!(dt.microsecond(), 456); /// assert_eq!(dt.nanosecond(), 789); /// ``` #[inline] pub const fn constant( year: i16, month: i8, day: i8, hour: i8, minute: i8, second: i8, subsec_nanosecond: i32, ) -> DateTime { let date = Date::constant(year, month, day); let time = Time::constant(hour, minute, second, subsec_nanosecond); DateTime { date, time } } /// Creates a `DateTime` from its constituent parts. /// /// Any combination of a valid `Date` and a valid `Time` results in a valid /// `DateTime`. /// /// # Example /// /// This example shows how to build a datetime from its parts: /// /// ``` /// use jiff::civil::{DateTime, date, time}; /// /// let dt = DateTime::from_parts(date(2024, 6, 6), time(6, 0, 0, 0)); /// assert_eq!(dt, date(2024, 6, 6).at(6, 0, 0, 0)); /// ``` #[inline] pub const fn from_parts(date: Date, time: Time) -> DateTime { DateTime { date, time } } /// Create a builder for constructing a new `DateTime` from the fields of /// this datetime. /// /// See the methods on [`DateTimeWith`] for the different ways one can set /// the fields of a new `DateTime`. /// /// # Example /// /// The builder ensures one can chain together the individual components of /// a datetime without it failing at an intermediate step. For example, if /// you had a date of `2024-10-31T00:00:00` and wanted to change both the /// day and the month, and each setting was validated independent of the /// other, you would need to be careful to set the day first and then the /// month. In some cases, you would need to set the month first and then /// the day! /// /// But with the builder, you can set values in any order: /// /// ``` /// use jiff::civil::date; /// /// let dt1 = date(2024, 10, 31).at(0, 0, 0, 0); /// let dt2 = dt1.with().month(11).day(30).build()?; /// assert_eq!(dt2, date(2024, 11, 30).at(0, 0, 0, 0)); /// /// let dt1 = date(2024, 4, 30).at(0, 0, 0, 0); /// let dt2 = dt1.with().day(31).month(7).build()?; /// assert_eq!(dt2, date(2024, 7, 31).at(0, 0, 0, 0)); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn with(self) -> DateTimeWith { DateTimeWith::new(self) } /// Returns the year for this datetime. /// /// The value returned is guaranteed to be in the range `-9999..=9999`. /// /// # Example /// /// ``` /// use jiff::civil::date; /// /// let dt1 = date(2024, 3, 9).at(7, 30, 0, 0); /// assert_eq!(dt1.year(), 2024); /// /// let dt2 = date(-2024, 3, 9).at(7, 30, 0, 0); /// assert_eq!(dt2.year(), -2024); /// /// let dt3 = date(0, 3, 9).at(7, 30, 0, 0); /// assert_eq!(dt3.year(), 0); /// ``` #[inline] pub fn year(self) -> i16 { self.date().year() } /// Returns the year and its era. /// /// This crate specifically allows years to be negative or `0`, where as /// years written for the Gregorian calendar are always positive and /// greater than `0`. In the Gregorian calendar, the era labels `BCE` and /// `CE` are used to disambiguate between years less than or equal to `0` /// and years greater than `0`, respectively. /// /// The crate is designed this way so that years in the latest era (that /// is, `CE`) are aligned with years in this crate. /// /// The year returned is guaranteed to be in the range `1..=10000`. /// /// # Example /// /// ``` /// use jiff::civil::{Era, date}; /// /// let dt = date(2024, 10, 3).at(7, 30, 0, 0); /// assert_eq!(dt.era_year(), (2024, Era::CE)); /// /// let dt = date(1, 10, 3).at(7, 30, 0, 0); /// assert_eq!(dt.era_year(), (1, Era::CE)); /// /// let dt = date(0, 10, 3).at(7, 30, 0, 0); /// assert_eq!(dt.era_year(), (1, Era::BCE)); /// /// let dt = date(-1, 10, 3).at(7, 30, 0, 0); /// assert_eq!(dt.era_year(), (2, Era::BCE)); /// /// let dt = date(-10, 10, 3).at(7, 30, 0, 0); /// assert_eq!(dt.era_year(), (11, Era::BCE)); /// /// let dt = date(-9_999, 10, 3).at(7, 30, 0, 0); /// assert_eq!(dt.era_year(), (10_000, Era::BCE)); /// ``` #[inline] pub fn era_year(self) -> (i16, Era) { self.date().era_year() } /// Returns the month for this datetime. /// /// The value returned is guaranteed to be in the range `1..=12`. /// /// # Example /// /// ``` /// use jiff::civil::date; /// /// let dt1 = date(2024, 3, 9).at(7, 30, 0, 0); /// assert_eq!(dt1.month(), 3); /// ``` #[inline] pub fn month(self) -> i8 { self.date().month() } /// Returns the day for this datetime. /// /// The value returned is guaranteed to be in the range `1..=31`. /// /// # Example /// /// ``` /// use jiff::civil::date; /// /// let dt1 = date(2024, 2, 29).at(7, 30, 0, 0); /// assert_eq!(dt1.day(), 29); /// ``` #[inline] pub fn day(self) -> i8 { self.date().day() } /// Returns the "hour" component of this datetime. /// /// The value returned is guaranteed to be in the range `0..=23`. /// /// # Example /// /// ``` /// use jiff::civil::date; /// /// let dt = date(2000, 1, 2).at(3, 4, 5, 123_456_789); /// assert_eq!(dt.hour(), 3); /// ``` #[inline] pub fn hour(self) -> i8 { self.time().hour() } /// Returns the "minute" component of this datetime. /// /// The value returned is guaranteed to be in the range `0..=59`. /// /// # Example /// /// ``` /// use jiff::civil::date; /// /// let dt = date(2000, 1, 2).at(3, 4, 5, 123_456_789); /// assert_eq!(dt.minute(), 4); /// ``` #[inline] pub fn minute(self) -> i8 { self.time().minute() } /// Returns the "second" component of this datetime. /// /// The value returned is guaranteed to be in the range `0..=59`. /// /// # Example /// /// ``` /// use jiff::civil::date; /// /// let dt = date(2000, 1, 2).at(3, 4, 5, 123_456_789); /// assert_eq!(dt.second(), 5); /// ``` #[inline] pub fn second(self) -> i8 { self.time().second() } /// Returns the "millisecond" component of this datetime. /// /// The value returned is guaranteed to be in the range `0..=999`. /// /// # Example /// /// ``` /// use jiff::civil::date; /// /// let dt = date(2000, 1, 2).at(3, 4, 5, 123_456_789); /// assert_eq!(dt.millisecond(), 123); /// ``` #[inline] pub fn millisecond(self) -> i16 { self.time().millisecond() } /// Returns the "microsecond" component of this datetime. /// /// The value returned is guaranteed to be in the range `0..=999`. /// /// # Example /// /// ``` /// use jiff::civil::date; /// /// let dt = date(2000, 1, 2).at(3, 4, 5, 123_456_789); /// assert_eq!(dt.microsecond(), 456); /// ``` #[inline] pub fn microsecond(self) -> i16 { self.time().microsecond() } /// Returns the "nanosecond" component of this datetime. /// /// The value returned is guaranteed to be in the range `0..=999`. /// /// # Example /// /// ``` /// use jiff::civil::date; /// /// let dt = date(2000, 1, 2).at(3, 4, 5, 123_456_789); /// assert_eq!(dt.nanosecond(), 789); /// ``` #[inline] pub fn nanosecond(self) -> i16 { self.time().nanosecond() } /// Returns the fractional nanosecond for this `DateTime` value. /// /// If you want to set this value on `DateTime`, then use /// [`DateTimeWith::subsec_nanosecond`] via [`DateTime::with`]. /// /// The value returned is guaranteed to be in the range `0..=999_999_999`. /// /// # Example /// /// This shows the relationship between constructing a `DateTime` value /// with routines like `with().millisecond()` and accessing the entire /// fractional part as a nanosecond: /// /// ``` /// use jiff::civil::date; /// /// let dt1 = date(2000, 1, 2).at(3, 4, 5, 123_456_789); /// assert_eq!(dt1.subsec_nanosecond(), 123_456_789); /// let dt2 = dt1.with().millisecond(333).build()?; /// assert_eq!(dt2.subsec_nanosecond(), 333_456_789); /// /// # Ok::<(), Box>(()) /// ``` /// /// # Example: nanoseconds from a timestamp /// /// This shows how the fractional nanosecond part of a `DateTime` value /// manifests from a specific timestamp. /// /// ``` /// use jiff::{civil, Timestamp}; /// /// // 1,234 nanoseconds after the Unix epoch. /// let zdt = Timestamp::new(0, 1_234)?.in_tz("UTC")?; /// let dt = zdt.datetime(); /// assert_eq!(dt.subsec_nanosecond(), 1_234); /// /// // 1,234 nanoseconds before the Unix epoch. /// let zdt = Timestamp::new(0, -1_234)?.in_tz("UTC")?; /// let dt = zdt.datetime(); /// // The nanosecond is equal to `1_000_000_000 - 1_234`. /// assert_eq!(dt.subsec_nanosecond(), 999998766); /// // Looking at the other components of the time value might help. /// assert_eq!(dt.hour(), 23); /// assert_eq!(dt.minute(), 59); /// assert_eq!(dt.second(), 59); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn subsec_nanosecond(self) -> i32 { self.time().subsec_nanosecond() } /// Returns the weekday corresponding to this datetime. /// /// # Example /// /// ``` /// use jiff::civil::{Weekday, date}; /// /// // The Unix epoch was on a Thursday. /// let dt = date(1970, 1, 1).at(7, 30, 0, 0); /// assert_eq!(dt.weekday(), Weekday::Thursday); /// // One can also get the weekday as an offset in a variety of schemes. /// assert_eq!(dt.weekday().to_monday_zero_offset(), 3); /// assert_eq!(dt.weekday().to_monday_one_offset(), 4); /// assert_eq!(dt.weekday().to_sunday_zero_offset(), 4); /// assert_eq!(dt.weekday().to_sunday_one_offset(), 5); /// ``` #[inline] pub fn weekday(self) -> Weekday { self.date().weekday() } /// Returns the ordinal day of the year that this datetime resides in. /// /// For leap years, this always returns a value in the range `1..=366`. /// Otherwise, the value is in the range `1..=365`. /// /// # Example /// /// ``` /// use jiff::civil::date; /// /// let dt = date(2006, 8, 24).at(7, 30, 0, 0); /// assert_eq!(dt.day_of_year(), 236); /// /// let dt = date(2023, 12, 31).at(7, 30, 0, 0); /// assert_eq!(dt.day_of_year(), 365); /// /// let dt = date(2024, 12, 31).at(7, 30, 0, 0); /// assert_eq!(dt.day_of_year(), 366); /// ``` #[inline] pub fn day_of_year(self) -> i16 { self.date().day_of_year() } /// Returns the ordinal day of the year that this datetime resides in, but /// ignores leap years. /// /// That is, the range of possible values returned by this routine is /// `1..=365`, even if this date resides in a leap year. If this date is /// February 29, then this routine returns `None`. /// /// The value `365` always corresponds to the last day in the year, /// December 31, even for leap years. /// /// # Example /// /// ``` /// use jiff::civil::date; /// /// let dt = date(2006, 8, 24).at(7, 30, 0, 0); /// assert_eq!(dt.day_of_year_no_leap(), Some(236)); /// /// let dt = date(2023, 12, 31).at(7, 30, 0, 0); /// assert_eq!(dt.day_of_year_no_leap(), Some(365)); /// /// let dt = date(2024, 12, 31).at(7, 30, 0, 0); /// assert_eq!(dt.day_of_year_no_leap(), Some(365)); /// /// let dt = date(2024, 2, 29).at(7, 30, 0, 0); /// assert_eq!(dt.day_of_year_no_leap(), None); /// ``` #[inline] pub fn day_of_year_no_leap(self) -> Option { self.date().day_of_year_no_leap() } /// Returns the beginning of the day that this datetime resides in. /// /// That is, the datetime returned always keeps the same date, but its /// time is always `00:00:00` (midnight). /// /// # Example /// /// ``` /// use jiff::civil::date; /// /// let dt = date(2024, 7, 3).at(7, 30, 10, 123_456_789); /// assert_eq!(dt.start_of_day(), date(2024, 7, 3).at(0, 0, 0, 0)); /// ``` #[inline] pub fn start_of_day(&self) -> DateTime { DateTime::from_parts(self.date(), Time::MIN) } /// Returns the end of the day that this datetime resides in. /// /// That is, the datetime returned always keeps the same date, but its /// time is always `23:59:59.999999999`. /// /// # Example /// /// ``` /// use jiff::civil::date; /// /// let dt = date(2024, 7, 3).at(7, 30, 10, 123_456_789); /// assert_eq!( /// dt.end_of_day(), /// date(2024, 7, 3).at(23, 59, 59, 999_999_999), /// ); /// ``` #[inline] pub fn end_of_day(&self) -> DateTime { DateTime::from_parts(self.date(), Time::MAX) } /// Returns the first date of the month that this datetime resides in. /// /// The time in the datetime returned remains unchanged. /// /// # Example /// /// ``` /// use jiff::civil::date; /// /// let dt = date(2024, 2, 29).at(7, 30, 0, 0); /// assert_eq!(dt.first_of_month(), date(2024, 2, 1).at(7, 30, 0, 0)); /// ``` #[inline] pub fn first_of_month(self) -> DateTime { DateTime::from_parts(self.date().first_of_month(), self.time()) } /// Returns the last date of the month that this datetime resides in. /// /// The time in the datetime returned remains unchanged. /// /// # Example /// /// ``` /// use jiff::civil::date; /// /// let dt = date(2024, 2, 5).at(7, 30, 0, 0); /// assert_eq!(dt.last_of_month(), date(2024, 2, 29).at(7, 30, 0, 0)); /// ``` #[inline] pub fn last_of_month(self) -> DateTime { DateTime::from_parts(self.date().last_of_month(), self.time()) } /// Returns the total number of days in the the month in which this /// datetime resides. /// /// This is guaranteed to always return one of the following values, /// depending on the year and the month: 28, 29, 30 or 31. /// /// # Example /// /// ``` /// use jiff::civil::date; /// /// let dt = date(2024, 2, 10).at(7, 30, 0, 0); /// assert_eq!(dt.days_in_month(), 29); /// /// let dt = date(2023, 2, 10).at(7, 30, 0, 0); /// assert_eq!(dt.days_in_month(), 28); /// /// let dt = date(2024, 8, 15).at(7, 30, 0, 0); /// assert_eq!(dt.days_in_month(), 31); /// ``` #[inline] pub fn days_in_month(self) -> i8 { self.date().days_in_month() } /// Returns the first date of the year that this datetime resides in. /// /// The time in the datetime returned remains unchanged. /// /// # Example /// /// ``` /// use jiff::civil::date; /// /// let dt = date(2024, 2, 29).at(7, 30, 0, 0); /// assert_eq!(dt.first_of_year(), date(2024, 1, 1).at(7, 30, 0, 0)); /// ``` #[inline] pub fn first_of_year(self) -> DateTime { DateTime::from_parts(self.date().first_of_year(), self.time()) } /// Returns the last date of the year that this datetime resides in. /// /// The time in the datetime returned remains unchanged. /// /// # Example /// /// ``` /// use jiff::civil::date; /// /// let dt = date(2024, 2, 5).at(7, 30, 0, 0); /// assert_eq!(dt.last_of_year(), date(2024, 12, 31).at(7, 30, 0, 0)); /// ``` #[inline] pub fn last_of_year(self) -> DateTime { DateTime::from_parts(self.date().last_of_year(), self.time()) } /// Returns the total number of days in the the year in which this datetime /// resides. /// /// This is guaranteed to always return either `365` or `366`. /// /// # Example /// /// ``` /// use jiff::civil::date; /// /// let dt = date(2024, 7, 10).at(7, 30, 0, 0); /// assert_eq!(dt.days_in_year(), 366); /// /// let dt = date(2023, 7, 10).at(7, 30, 0, 0); /// assert_eq!(dt.days_in_year(), 365); /// ``` #[inline] pub fn days_in_year(self) -> i16 { self.date().days_in_year() } /// Returns true if and only if the year in which this datetime resides is /// a leap year. /// /// # Example /// /// ``` /// use jiff::civil::date; /// /// assert!(date(2024, 1, 1).at(7, 30, 0, 0).in_leap_year()); /// assert!(!date(2023, 12, 31).at(7, 30, 0, 0).in_leap_year()); /// ``` #[inline] pub fn in_leap_year(self) -> bool { self.date().in_leap_year() } /// Returns the datetime with a date immediately following this one. /// /// The time in the datetime returned remains unchanged. /// /// # Errors /// /// This returns an error when this datetime's date is the maximum value. /// /// # Example /// /// ``` /// use jiff::civil::{DateTime, date}; /// /// let dt = date(2024, 2, 28).at(7, 30, 0, 0); /// assert_eq!(dt.tomorrow()?, date(2024, 2, 29).at(7, 30, 0, 0)); /// /// // The max doesn't have a tomorrow. /// assert!(DateTime::MAX.tomorrow().is_err()); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn tomorrow(self) -> Result { Ok(DateTime::from_parts(self.date().tomorrow()?, self.time())) } /// Returns the datetime with a date immediately preceding this one. /// /// The time in the datetime returned remains unchanged. /// /// # Errors /// /// This returns an error when this datetime's date is the minimum value. /// /// # Example /// /// ``` /// use jiff::civil::{DateTime, date}; /// /// let dt = date(2024, 3, 1).at(7, 30, 0, 0); /// assert_eq!(dt.yesterday()?, date(2024, 2, 29).at(7, 30, 0, 0)); /// /// // The min doesn't have a yesterday. /// assert!(DateTime::MIN.yesterday().is_err()); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn yesterday(self) -> Result { Ok(DateTime::from_parts(self.date().yesterday()?, self.time())) } /// Returns the "nth" weekday from the beginning or end of the month in /// which this datetime resides. /// /// The `nth` parameter can be positive or negative. A positive value /// computes the "nth" weekday from the beginning of the month. A negative /// value computes the "nth" weekday from the end of the month. So for /// example, use `-1` to "find the last weekday" in this date's month. /// /// The time in the datetime returned remains unchanged. /// /// # Errors /// /// This returns an error when `nth` is `0`, or if it is `5` or `-5` and /// there is no 5th weekday from the beginning or end of the month. /// /// # Example /// /// This shows how to get the nth weekday in a month, starting from the /// beginning of the month: /// /// ``` /// use jiff::civil::{Weekday, date}; /// /// let dt = date(2017, 3, 1).at(7, 30, 0, 0); /// let second_friday = dt.nth_weekday_of_month(2, Weekday::Friday)?; /// assert_eq!(second_friday, date(2017, 3, 10).at(7, 30, 0, 0)); /// /// # Ok::<(), Box>(()) /// ``` /// /// This shows how to do the reverse of the above. That is, the nth _last_ /// weekday in a month: /// /// ``` /// use jiff::civil::{Weekday, date}; /// /// let dt = date(2024, 3, 1).at(7, 30, 0, 0); /// let last_thursday = dt.nth_weekday_of_month(-1, Weekday::Thursday)?; /// assert_eq!(last_thursday, date(2024, 3, 28).at(7, 30, 0, 0)); /// let second_last_thursday = dt.nth_weekday_of_month( /// -2, /// Weekday::Thursday, /// )?; /// assert_eq!(second_last_thursday, date(2024, 3, 21).at(7, 30, 0, 0)); /// /// # Ok::<(), Box>(()) /// ``` /// /// This routine can return an error if there isn't an `nth` weekday /// for this month. For example, March 2024 only has 4 Mondays: /// /// ``` /// use jiff::civil::{Weekday, date}; /// /// let dt = date(2024, 3, 25).at(7, 30, 0, 0); /// let fourth_monday = dt.nth_weekday_of_month(4, Weekday::Monday)?; /// assert_eq!(fourth_monday, date(2024, 3, 25).at(7, 30, 0, 0)); /// // There is no 5th Monday. /// assert!(dt.nth_weekday_of_month(5, Weekday::Monday).is_err()); /// // Same goes for counting backwards. /// assert!(dt.nth_weekday_of_month(-5, Weekday::Monday).is_err()); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn nth_weekday_of_month( self, nth: i8, weekday: Weekday, ) -> Result { let date = self.date().nth_weekday_of_month(nth, weekday)?; Ok(DateTime::from_parts(date, self.time())) } /// Returns the "nth" weekday from this datetime, not including itself. /// /// The `nth` parameter can be positive or negative. A positive value /// computes the "nth" weekday starting at the day after this date and /// going forwards in time. A negative value computes the "nth" weekday /// starting at the day before this date and going backwards in time. /// /// For example, if this datetime's weekday is a Sunday and the first /// Sunday is asked for (that is, `dt.nth_weekday(1, Weekday::Sunday)`), /// then the result is a week from this datetime corresponding to the /// following Sunday. /// /// The time in the datetime returned remains unchanged. /// /// # Errors /// /// This returns an error when `nth` is `0`, or if it would otherwise /// result in a date that overflows the minimum/maximum values of /// `DateTime`. /// /// # Example /// /// This example shows how to find the "nth" weekday going forwards in /// time: /// /// ``` /// use jiff::civil::{Weekday, date}; /// /// // Use a Sunday in March as our start date. /// let dt = date(2024, 3, 10).at(7, 30, 0, 0); /// assert_eq!(dt.weekday(), Weekday::Sunday); /// /// // The first next Monday is tomorrow! /// let next_monday = dt.nth_weekday(1, Weekday::Monday)?; /// assert_eq!(next_monday, date(2024, 3, 11).at(7, 30, 0, 0)); /// /// // But the next Sunday is a week away, because this doesn't /// // include the current weekday. /// let next_sunday = dt.nth_weekday(1, Weekday::Sunday)?; /// assert_eq!(next_sunday, date(2024, 3, 17).at(7, 30, 0, 0)); /// /// // "not this Thursday, but next Thursday" /// let next_next_thursday = dt.nth_weekday(2, Weekday::Thursday)?; /// assert_eq!(next_next_thursday, date(2024, 3, 21).at(7, 30, 0, 0)); /// /// # Ok::<(), Box>(()) /// ``` /// /// This example shows how to find the "nth" weekday going backwards in /// time: /// /// ``` /// use jiff::civil::{Weekday, date}; /// /// // Use a Sunday in March as our start date. /// let dt = date(2024, 3, 10).at(7, 30, 0, 0); /// assert_eq!(dt.weekday(), Weekday::Sunday); /// /// // "last Saturday" was yesterday! /// let last_saturday = dt.nth_weekday(-1, Weekday::Saturday)?; /// assert_eq!(last_saturday, date(2024, 3, 9).at(7, 30, 0, 0)); /// /// // "last Sunday" was a week ago. /// let last_sunday = dt.nth_weekday(-1, Weekday::Sunday)?; /// assert_eq!(last_sunday, date(2024, 3, 3).at(7, 30, 0, 0)); /// /// // "not last Thursday, but the one before" /// let prev_prev_thursday = dt.nth_weekday(-2, Weekday::Thursday)?; /// assert_eq!(prev_prev_thursday, date(2024, 2, 29).at(7, 30, 0, 0)); /// /// # Ok::<(), Box>(()) /// ``` /// /// This example shows that overflow results in an error in either /// direction: /// /// ``` /// use jiff::civil::{DateTime, Weekday}; /// /// let dt = DateTime::MAX; /// assert_eq!(dt.weekday(), Weekday::Friday); /// assert!(dt.nth_weekday(1, Weekday::Saturday).is_err()); /// /// let dt = DateTime::MIN; /// assert_eq!(dt.weekday(), Weekday::Monday); /// assert!(dt.nth_weekday(-1, Weekday::Sunday).is_err()); /// ``` /// /// # Example: the start of Israeli summer time /// /// Israeli law says (at present, as of 2024-03-11) that DST or /// "summer time" starts on the Friday before the last Sunday in /// March. We can find that date using both `nth_weekday` and /// [`DateTime::nth_weekday_of_month`]: /// /// ``` /// use jiff::civil::{Weekday, date}; /// /// let march = date(2024, 3, 1).at(0, 0, 0, 0); /// let last_sunday = march.nth_weekday_of_month(-1, Weekday::Sunday)?; /// let dst_starts_on = last_sunday.nth_weekday(-1, Weekday::Friday)?; /// assert_eq!(dst_starts_on, date(2024, 3, 29).at(0, 0, 0, 0)); /// /// # Ok::<(), Box>(()) /// ``` /// /// # Example: getting the start of the week /// /// Given a date, one can use `nth_weekday` to determine the start of the /// week in which the date resides in. This might vary based on whether /// the weeks start on Sunday or Monday. This example shows how to handle /// both. /// /// ``` /// use jiff::civil::{Weekday, date}; /// /// let dt = date(2024, 3, 15).at(7, 30, 0, 0); /// // For weeks starting with Sunday. /// let start_of_week = dt.tomorrow()?.nth_weekday(-1, Weekday::Sunday)?; /// assert_eq!(start_of_week, date(2024, 3, 10).at(7, 30, 0, 0)); /// // For weeks starting with Monday. /// let start_of_week = dt.tomorrow()?.nth_weekday(-1, Weekday::Monday)?; /// assert_eq!(start_of_week, date(2024, 3, 11).at(7, 30, 0, 0)); /// /// # Ok::<(), Box>(()) /// ``` /// /// In the above example, we first get the date after the current one /// because `nth_weekday` does not consider itself when counting. This /// works as expected even at the boundaries of a week: /// /// ``` /// use jiff::civil::{Time, Weekday, date}; /// /// // The start of the week. /// let dt = date(2024, 3, 10).at(0, 0, 0, 0); /// let start_of_week = dt.tomorrow()?.nth_weekday(-1, Weekday::Sunday)?; /// assert_eq!(start_of_week, date(2024, 3, 10).at(0, 0, 0, 0)); /// // The end of the week. /// let dt = date(2024, 3, 16).at(23, 59, 59, 999_999_999); /// let start_of_week = dt /// .tomorrow()? /// .nth_weekday(-1, Weekday::Sunday)? /// .with().time(Time::midnight()).build()?; /// assert_eq!(start_of_week, date(2024, 3, 10).at(0, 0, 0, 0)); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn nth_weekday( self, nth: i32, weekday: Weekday, ) -> Result { let date = self.date().nth_weekday(nth, weekday)?; Ok(DateTime::from_parts(date, self.time())) } /// Returns the date component of this datetime. /// /// # Example /// /// ``` /// use jiff::civil::date; /// /// let dt = date(2024, 3, 14).at(18, 45, 0, 0); /// assert_eq!(dt.date(), date(2024, 3, 14)); /// ``` #[inline] pub fn date(self) -> Date { self.date } /// Returns the time component of this datetime. /// /// # Example /// /// ``` /// use jiff::civil::{date, time}; /// /// let dt = date(2024, 3, 14).at(18, 45, 0, 0); /// assert_eq!(dt.time(), time(18, 45, 0, 0)); /// ``` #[inline] pub fn time(self) -> Time { self.time } /// Construct an [ISO 8601 week date] from this datetime. /// /// The [`ISOWeekDate`] type describes itself in more detail, but in /// brief, the ISO week date calendar system eschews months in favor of /// weeks. /// /// This routine is equivalent to /// [`ISOWeekDate::from_date(dt.date())`](ISOWeekDate::from_date). /// /// [ISO 8601 week date]: https://en.wikipedia.org/wiki/ISO_week_date /// /// # Example /// /// This shows a number of examples demonstrating the conversion from a /// Gregorian date to an ISO 8601 week date: /// /// ``` /// use jiff::civil::{Date, Time, Weekday, date}; /// /// let dt = date(1995, 1, 1).at(18, 45, 0, 0); /// let weekdate = dt.iso_week_date(); /// assert_eq!(weekdate.year(), 1994); /// assert_eq!(weekdate.week(), 52); /// assert_eq!(weekdate.weekday(), Weekday::Sunday); /// /// let dt = date(1996, 12, 31).at(18, 45, 0, 0); /// let weekdate = dt.iso_week_date(); /// assert_eq!(weekdate.year(), 1997); /// assert_eq!(weekdate.week(), 1); /// assert_eq!(weekdate.weekday(), Weekday::Tuesday); /// /// let dt = date(2019, 12, 30).at(18, 45, 0, 0); /// let weekdate = dt.iso_week_date(); /// assert_eq!(weekdate.year(), 2020); /// assert_eq!(weekdate.week(), 1); /// assert_eq!(weekdate.weekday(), Weekday::Monday); /// /// let dt = date(2024, 3, 9).at(18, 45, 0, 0); /// let weekdate = dt.iso_week_date(); /// assert_eq!(weekdate.year(), 2024); /// assert_eq!(weekdate.week(), 10); /// assert_eq!(weekdate.weekday(), Weekday::Saturday); /// /// let dt = Date::MIN.to_datetime(Time::MIN); /// let weekdate = dt.iso_week_date(); /// assert_eq!(weekdate.year(), -9999); /// assert_eq!(weekdate.week(), 1); /// assert_eq!(weekdate.weekday(), Weekday::Monday); /// /// let dt = Date::MAX.to_datetime(Time::MAX); /// let weekdate = dt.iso_week_date(); /// assert_eq!(weekdate.year(), 9999); /// assert_eq!(weekdate.week(), 52); /// assert_eq!(weekdate.weekday(), Weekday::Friday); /// ``` #[inline] pub fn iso_week_date(self) -> ISOWeekDate { self.date().iso_week_date() } /// Converts a civil datetime to a [`Zoned`] datetime by adding the given /// time zone. /// /// The name given is resolved to a [`TimeZone`] by using the default /// [`TimeZoneDatabase`](crate::tz::TimeZoneDatabase) created by /// [`tz::db`](crate::tz::db). Indeed, this is a convenience function for /// [`DateTime::to_zoned`] where the time zone database lookup is done /// automatically. /// /// In some cases, a civil datetime may be ambiguous in a /// particular time zone. This routine automatically utilizes the /// [`Disambiguation::Compatible`](crate::tz::Disambiguation) strategy /// for resolving ambiguities. That is, if a civil datetime occurs in a /// backward transition (called a fold), then the earlier time is selected. /// Or if a civil datetime occurs in a forward transition (called a gap), /// then the later time is selected. /// /// To convert a datetime to a `Zoned` using a different disambiguation /// strategy, use [`TimeZone::to_ambiguous_zoned`]. /// /// # Errors /// /// This returns an error when the given time zone name could not be found /// in the default time zone database. /// /// This also returns an error if this datetime could not be represented as /// an instant. This can occur in some cases near the minimum and maximum /// boundaries of a `DateTime`. /// /// # Example /// /// This is a simple example of converting a civil datetime (a "wall" or /// "local" or "naive" datetime) to a datetime that is aware of its time /// zone: /// /// ``` /// use jiff::civil::DateTime; /// /// let dt: DateTime = "2024-06-20 15:06".parse()?; /// let zdt = dt.in_tz("America/New_York")?; /// assert_eq!(zdt.to_string(), "2024-06-20T15:06:00-04:00[America/New_York]"); /// /// # Ok::<(), Box>(()) /// ``` /// /// # Example: dealing with ambiguity /// /// In the `America/New_York` time zone, there was a forward transition /// at `2024-03-10 02:00:00` civil time, and a backward transition at /// `2024-11-03 01:00:00` civil time. In the former case, a gap was /// created such that the 2 o'clock hour never appeared on clocks for folks /// in the `America/New_York` time zone. In the latter case, a fold was /// created such that the 1 o'clock hour was repeated. Thus, March 10, 2024 /// in New York was 23 hours long, while November 3, 2024 in New York was /// 25 hours long. /// /// This example shows how datetimes in these gaps and folds are resolved /// by default: /// /// ``` /// use jiff::civil::DateTime; /// /// // This is the gap, where by default we select the later time. /// let dt: DateTime = "2024-03-10 02:30".parse()?; /// let zdt = dt.in_tz("America/New_York")?; /// assert_eq!(zdt.to_string(), "2024-03-10T03:30:00-04:00[America/New_York]"); /// /// // This is the fold, where by default we select the earlier time. /// let dt: DateTime = "2024-11-03 01:30".parse()?; /// let zdt = dt.in_tz("America/New_York")?; /// // Since this is a fold, the wall clock time is repeated. It might be /// // hard to see that this is the earlier time, but notice the offset: /// // it is the offset for DST time in New York. The later time, or the /// // repetition of the 1 o'clock hour, would occur in standard time, /// // which is an offset of -05 for New York. /// assert_eq!(zdt.to_string(), "2024-11-03T01:30:00-04:00[America/New_York]"); /// /// # Ok::<(), Box>(()) /// ``` /// /// # Example: errors /// /// This routine can return an error when the time zone is unrecognized: /// /// ``` /// use jiff::civil::date; /// /// let dt = date(2024, 6, 20).at(15, 6, 0, 0); /// assert!(dt.in_tz("does not exist").is_err()); /// ``` /// /// Note that even if a time zone exists in, say, the IANA database, there /// may have been a problem reading it from your system's installation of /// that database. To see what wrong, enable Jiff's `logging` crate feature /// and install a logger. If there was a failure, then a `WARN` level log /// message should be emitted. /// /// This routine can also fail if this datetime cannot be represented /// within the allowable timestamp limits: /// /// ``` /// use jiff::{civil::DateTime, tz::{Offset, TimeZone}}; /// /// let dt = DateTime::MAX; /// // All errors because the combination of the offset and the datetime /// // isn't enough to fit into timestamp limits. /// assert!(dt.in_tz("UTC").is_err()); /// assert!(dt.in_tz("America/New_York").is_err()); /// assert!(dt.in_tz("Australia/Tasmania").is_err()); /// // In fact, the only valid offset one can use to turn the maximum civil /// // datetime into a Zoned value is the maximum offset: /// let tz = Offset::from_seconds(93_599).unwrap().to_time_zone(); /// assert!(dt.to_zoned(tz).is_ok()); /// // One second less than the maximum offset results in a failure at the /// // maximum datetime boundary. /// let tz = Offset::from_seconds(93_598).unwrap().to_time_zone(); /// assert!(dt.to_zoned(tz).is_err()); /// ``` /// /// This behavior exists because it guarantees that every possible `Zoned` /// value can be converted into a civil datetime, but not every possible /// combination of civil datetime and offset can be converted into a /// `Zoned` value. There isn't a way to make every possible roundtrip /// lossless in both directions, so Jiff chooses to ensure that there is /// always a way to convert a `Zoned` instant to a human readable wall /// clock time. #[inline] pub fn in_tz(self, time_zone_name: &str) -> Result { let tz = crate::tz::db().get(time_zone_name)?; self.to_zoned(tz) } /// Converts a civil datetime to a [`Zoned`] datetime by adding the given /// [`TimeZone`]. /// /// In some cases, a civil datetime may be ambiguous in a /// particular time zone. This routine automatically utilizes the /// [`Disambiguation::Compatible`](crate::tz::Disambiguation) strategy /// for resolving ambiguities. That is, if a civil datetime occurs in a /// backward transition (called a fold), then the earlier time is selected. /// Or if a civil datetime occurs in a forward transition (called a gap), /// then the later time is selected. /// /// To convert a datetime to a `Zoned` using a different disambiguation /// strategy, use [`TimeZone::to_ambiguous_zoned`]. /// /// In the common case of a time zone being represented as a name string, /// like `Australia/Tasmania`, consider using [`DateTime::in_tz`] /// instead. /// /// # Errors /// /// This returns an error if this datetime could not be represented as an /// instant. This can occur in some cases near the minimum and maximum /// boundaries of a `DateTime`. /// /// # Example /// /// This example shows how to create a zoned value with a fixed time zone /// offset: /// /// ``` /// use jiff::{civil::date, tz::{self, TimeZone}}; /// /// let tz = TimeZone::fixed(tz::offset(-4)); /// let zdt = date(2024, 6, 20).at(17, 3, 0, 0).to_zoned(tz)?; /// // A time zone annotation is still included in the printable version /// // of the Zoned value, but it is fixed to a particular offset. /// assert_eq!(zdt.to_string(), "2024-06-20T17:03:00-04:00[-04:00]"); /// /// # Ok::<(), Box>(()) /// ``` /// /// # Example: POSIX time zone strings /// /// And this example shows how to create a time zone from a POSIX time /// zone string that describes the transition to and from daylight saving /// time for `America/St_Johns`. In particular, this rule uses non-zero /// minutes, which is atypical. /// /// ``` /// use jiff::{civil::date, tz::TimeZone}; /// /// let tz = TimeZone::posix("NST3:30NDT,M3.2.0,M11.1.0")?; /// let zdt = date(2024, 6, 20).at(17, 3, 0, 0).to_zoned(tz)?; /// // There isn't any agreed upon mechanism for transmitting a POSIX time /// // zone string within an RFC 9557 TZ annotation, so Jiff just emits the /// // offset. In practice, POSIX TZ strings are rarely user facing anyway. /// // (They are still in widespread use as an implementation detail of the /// // IANA Time Zone Database however.) /// assert_eq!(zdt.to_string(), "2024-06-20T17:03:00-02:30[-02:30]"); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn to_zoned(self, tz: TimeZone) -> Result { tz.into_ambiguous_zoned(self).compatible() } /// Add the given span of time to this datetime. If the sum would overflow /// the minimum or maximum datetime values, then an error is returned. /// /// This operation accepts three different duration types: [`Span`], /// [`SignedDuration`] or [`std::time::Duration`]. This is achieved via /// `From` trait implementations for the [`DateTimeArithmetic`] type. /// /// # Properties /// /// This routine is _not_ reversible because some additions may /// be ambiguous. For example, adding `1 month` to the datetime /// `2024-03-31T00:00:00` will produce `2024-04-30T00:00:00` since April /// has only 30 days in a month. Moreover, subtracting `1 month` from /// `2024-04-30T00:00:00` will produce `2024-03-30T00:00:00`, which is not /// the date we started with. /// /// If spans of time are limited to units of days (or less), then this /// routine _is_ reversible. This also implies that all operations with a /// [`SignedDuration`] or a [`std::time::Duration`] are reversible. /// /// # Errors /// /// If the span added to this datetime would result in a datetime that /// exceeds the range of a `DateTime`, then this will return an error. /// /// # Example /// /// This shows a few examples of adding spans of time to various dates. /// We make use of the [`ToSpan`](crate::ToSpan) trait for convenient /// creation of spans. /// /// ``` /// use jiff::{civil::date, ToSpan}; /// /// let dt = date(1995, 12, 7).at(3, 24, 30, 3_500); /// let got = dt.checked_add(20.years().months(4).nanoseconds(500))?; /// assert_eq!(got, date(2016, 4, 7).at(3, 24, 30, 4_000)); /// /// let dt = date(2019, 1, 31).at(15, 30, 0, 0); /// let got = dt.checked_add(1.months())?; /// assert_eq!(got, date(2019, 2, 28).at(15, 30, 0, 0)); /// /// # Ok::<(), Box>(()) /// ``` /// /// # Example: available via addition operator /// /// This routine can be used via the `+` operator. Note though that if it /// fails, it will result in a panic. /// /// ``` /// use jiff::{civil::date, ToSpan}; /// /// let dt = date(1995, 12, 7).at(3, 24, 30, 3_500); /// let got = dt + 20.years().months(4).nanoseconds(500); /// assert_eq!(got, date(2016, 4, 7).at(3, 24, 30, 4_000)); /// ``` /// /// # Example: negative spans are supported /// /// ``` /// use jiff::{civil::date, ToSpan}; /// /// let dt = date(2024, 3, 31).at(19, 5, 59, 999_999_999); /// assert_eq!( /// dt.checked_add(-1.months())?, /// date(2024, 2, 29).at(19, 5, 59, 999_999_999), /// ); /// /// # Ok::<(), Box>(()) /// ``` /// /// # Example: error on overflow /// /// ``` /// use jiff::{civil::date, ToSpan}; /// /// let dt = date(2024, 3, 31).at(13, 13, 13, 13); /// assert!(dt.checked_add(9000.years()).is_err()); /// assert!(dt.checked_add(-19000.years()).is_err()); /// ``` /// /// # Example: adding absolute durations /// /// This shows how to add signed and unsigned absolute durations to a /// `DateTime`. /// /// ``` /// use std::time::Duration; /// /// use jiff::{civil::date, SignedDuration}; /// /// let dt = date(2024, 2, 29).at(0, 0, 0, 0); /// /// let dur = SignedDuration::from_hours(25); /// assert_eq!(dt.checked_add(dur)?, date(2024, 3, 1).at(1, 0, 0, 0)); /// assert_eq!(dt.checked_add(-dur)?, date(2024, 2, 27).at(23, 0, 0, 0)); /// /// let dur = Duration::from_secs(25 * 60 * 60); /// assert_eq!(dt.checked_add(dur)?, date(2024, 3, 1).at(1, 0, 0, 0)); /// // One cannot negate an unsigned duration, /// // but you can subtract it! /// assert_eq!(dt.checked_sub(dur)?, date(2024, 2, 27).at(23, 0, 0, 0)); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn checked_add>( self, duration: A, ) -> Result { let duration: DateTimeArithmetic = duration.into(); duration.checked_add(self) } #[inline] fn checked_add_span(self, span: Span) -> Result { let (date, time) = (self.date(), self.time()); let span_date = span.without_lower(Unit::Day); let span_time = span.only_lower(Unit::Day); let (new_time, leftovers) = time .overflowing_add(span_time) .with_context(|| err!("failed to add {span_time} to {time}"))?; let new_date = date .checked_add(span_date) .with_context(|| err!("failed to add {span_date} to {date}"))?; let new_date = new_date.checked_add(leftovers).with_context(|| { err!( "failed to add overflowing span, {leftovers}, \ from adding {span_time} to {time}, \ to {new_date}", ) })?; Ok(DateTime::from_parts(new_date, new_time)) } #[inline] fn checked_add_duration( self, duration: SignedDuration, ) -> Result { let (date, time) = (self.date(), self.time()); let (new_time, leftovers) = time.overflowing_add_duration(duration)?; let new_date = date.checked_add(leftovers).with_context(|| { err!( "failed to add overflowing signed duration, {leftovers:?}, \ from adding {duration:?} to {time}, to {date}", ) })?; Ok(DateTime::from_parts(new_date, new_time)) } /// This routine is identical to [`DateTime::checked_add`] with the /// duration negated. /// /// # Errors /// /// This has the same error conditions as [`DateTime::checked_add`]. /// /// # Example /// /// This routine can be used via the `-` operator. Note though that if it /// fails, it will result in a panic. /// /// ``` /// use std::time::Duration; /// /// use jiff::{civil::date, SignedDuration, ToSpan}; /// /// let dt = date(1995, 12, 7).at(3, 24, 30, 3_500); /// assert_eq!( /// dt - 20.years().months(4).nanoseconds(500), /// date(1975, 8, 7).at(3, 24, 30, 3_000), /// ); /// /// let dur = SignedDuration::new(24 * 60 * 60, 3_500); /// assert_eq!(dt - dur, date(1995, 12, 6).at(3, 24, 30, 0)); /// /// let dur = Duration::new(24 * 60 * 60, 3_500); /// assert_eq!(dt - dur, date(1995, 12, 6).at(3, 24, 30, 0)); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn checked_sub>( self, duration: A, ) -> Result { let duration: DateTimeArithmetic = duration.into(); duration.checked_neg().and_then(|dta| dta.checked_add(self)) } /// This routine is identical to [`DateTime::checked_add`], except the /// result saturates on overflow. That is, instead of overflow, either /// [`DateTime::MIN`] or [`DateTime::MAX`] is returned. /// /// # Example /// /// ``` /// use jiff::{civil::{DateTime, date}, SignedDuration, ToSpan}; /// /// let dt = date(2024, 3, 31).at(13, 13, 13, 13); /// assert_eq!(DateTime::MAX, dt.saturating_add(9000.years())); /// assert_eq!(DateTime::MIN, dt.saturating_add(-19000.years())); /// assert_eq!(DateTime::MAX, dt.saturating_add(SignedDuration::MAX)); /// assert_eq!(DateTime::MIN, dt.saturating_add(SignedDuration::MIN)); /// assert_eq!(DateTime::MAX, dt.saturating_add(std::time::Duration::MAX)); /// ``` #[inline] pub fn saturating_add>( self, duration: A, ) -> DateTime { let duration: DateTimeArithmetic = duration.into(); self.checked_add(duration).unwrap_or_else(|_| { if duration.is_negative() { DateTime::MIN } else { DateTime::MAX } }) } /// This routine is identical to [`DateTime::saturating_add`] with the span /// parameter negated. /// /// # Example /// /// ``` /// use jiff::{civil::{DateTime, date}, SignedDuration, ToSpan}; /// /// let dt = date(2024, 3, 31).at(13, 13, 13, 13); /// assert_eq!(DateTime::MIN, dt.saturating_sub(19000.years())); /// assert_eq!(DateTime::MAX, dt.saturating_sub(-9000.years())); /// assert_eq!(DateTime::MIN, dt.saturating_sub(SignedDuration::MAX)); /// assert_eq!(DateTime::MAX, dt.saturating_sub(SignedDuration::MIN)); /// assert_eq!(DateTime::MIN, dt.saturating_sub(std::time::Duration::MAX)); /// ``` #[inline] pub fn saturating_sub>( self, duration: A, ) -> DateTime { let duration: DateTimeArithmetic = duration.into(); let Ok(duration) = duration.checked_neg() else { return DateTime::MIN; }; self.saturating_add(duration) } /// Returns a span representing the elapsed time from this datetime until /// the given `other` datetime. /// /// When `other` occurs before this datetime, then the span returned will /// be negative. /// /// Depending on the input provided, the span returned is rounded. It may /// also be balanced up to bigger units than the default. By default, the /// span returned is balanced such that the biggest possible unit is days. /// /// This operation is configured by providing a [`DateTimeDifference`] /// value. Since this routine accepts anything that implements /// `Into`, once can pass a `DateTime` directly. /// One can also pass a `(Unit, DateTime)`, where `Unit` is treated as /// [`DateTimeDifference::largest`]. /// /// # Properties /// /// It is guaranteed that if the returned span is subtracted from `other`, /// and if no rounding is requested, and if the largest unit requested is /// at most `Unit::Day`, then the original datetime will be returned. /// /// This routine is equivalent to `self.since(other).map(|span| -span)` /// if no rounding options are set. If rounding options are set, then /// it's equivalent to /// `self.since(other_without_rounding_options).map(|span| -span)`, /// followed by a call to [`Span::round`] with the appropriate rounding /// options set. This is because the negation of a span can result in /// different rounding results depending on the rounding mode. /// /// # Errors /// /// An error can occur in some cases when the requested configuration would /// result in a span that is beyond allowable limits. For example, the /// nanosecond component of a span cannot the span of time between the /// minimum and maximum datetime supported by Jiff. Therefore, if one /// requests a span with its largest unit set to [`Unit::Nanosecond`], then /// it's possible for this routine to fail. /// /// It is guaranteed that if one provides a datetime with the default /// [`DateTimeDifference`] configuration, then this routine will never /// fail. /// /// # Example /// /// ``` /// use jiff::{civil::date, ToSpan}; /// /// let earlier = date(2006, 8, 24).at(22, 30, 0, 0); /// let later = date(2019, 1, 31).at(21, 0, 0, 0); /// assert_eq!( /// earlier.until(later)?, /// 4542.days().hours(22).minutes(30).fieldwise(), /// ); /// /// // Flipping the dates is fine, but you'll get a negative span. /// assert_eq!( /// later.until(earlier)?, /// -4542.days().hours(22).minutes(30).fieldwise(), /// ); /// /// # Ok::<(), Box>(()) /// ``` /// /// # Example: using bigger units /// /// This example shows how to expand the span returned to bigger units. /// This makes use of a `From<(Unit, DateTime)> for DateTimeDifference` /// trait implementation. /// /// ``` /// use jiff::{civil::date, Unit, ToSpan}; /// /// let dt1 = date(1995, 12, 07).at(3, 24, 30, 3500); /// let dt2 = date(2019, 01, 31).at(15, 30, 0, 0); /// /// // The default limits durations to using "days" as the biggest unit. /// let span = dt1.until(dt2)?; /// assert_eq!(span.to_string(), "P8456DT12H5M29.9999965S"); /// /// // But we can ask for units all the way up to years. /// let span = dt1.until((Unit::Year, dt2))?; /// assert_eq!(span.to_string(), "P23Y1M24DT12H5M29.9999965S"); /// # Ok::<(), Box>(()) /// ``` /// /// # Example: rounding the result /// /// This shows how one might find the difference between two datetimes and /// have the result rounded such that sub-seconds are removed. /// /// In this case, we need to hand-construct a [`DateTimeDifference`] /// in order to gain full configurability. /// /// ``` /// use jiff::{civil::{DateTimeDifference, date}, Unit, ToSpan}; /// /// let dt1 = date(1995, 12, 07).at(3, 24, 30, 3500); /// let dt2 = date(2019, 01, 31).at(15, 30, 0, 0); /// /// let span = dt1.until( /// DateTimeDifference::from(dt2).smallest(Unit::Second), /// )?; /// assert_eq!(format!("{span:#}"), "8456d 12h 5m 29s"); /// /// // We can combine smallest and largest units too! /// let span = dt1.until( /// DateTimeDifference::from(dt2) /// .smallest(Unit::Second) /// .largest(Unit::Year), /// )?; /// assert_eq!(span.to_string(), "P23Y1M24DT12H5M29S"); /// # Ok::<(), Box>(()) /// ``` /// /// # Example: units biggers than days inhibit reversibility /// /// If you ask for units bigger than days, then subtracting the span /// returned from the `other` datetime is not guaranteed to result in the /// original datetime. For example: /// /// ``` /// use jiff::{civil::date, Unit, ToSpan}; /// /// let dt1 = date(2024, 3, 2).at(0, 0, 0, 0); /// let dt2 = date(2024, 5, 1).at(0, 0, 0, 0); /// /// let span = dt1.until((Unit::Month, dt2))?; /// assert_eq!(span, 1.month().days(29).fieldwise()); /// let maybe_original = dt2.checked_sub(span)?; /// // Not the same as the original datetime! /// assert_eq!(maybe_original, date(2024, 3, 3).at(0, 0, 0, 0)); /// /// // But in the default configuration, days are always the biggest unit /// // and reversibility is guaranteed. /// let span = dt1.until(dt2)?; /// assert_eq!(span, 60.days().fieldwise()); /// let is_original = dt2.checked_sub(span)?; /// assert_eq!(is_original, dt1); /// /// # Ok::<(), Box>(()) /// ``` /// /// This occurs because span are added as if by adding the biggest units /// first, and then the smaller units. Because months vary in length, /// their meaning can change depending on how the span is added. In this /// case, adding one month to `2024-03-02` corresponds to 31 days, but /// subtracting one month from `2024-05-01` corresponds to 30 days. #[inline] pub fn until>( self, other: A, ) -> Result { let args: DateTimeDifference = other.into(); let span = args.until_with_largest_unit(self)?; if args.rounding_may_change_span() { span.round(args.round.relative(self)) } else { Ok(span) } } /// This routine is identical to [`DateTime::until`], but the order of the /// parameters is flipped. /// /// # Errors /// /// This has the same error conditions as [`DateTime::until`]. /// /// # Example /// /// This routine can be used via the `-` operator. Since the default /// configuration is used and because a `Span` can represent the difference /// between any two possible datetimes, it will never panic. /// /// ``` /// use jiff::{civil::date, ToSpan}; /// /// let earlier = date(2006, 8, 24).at(22, 30, 0, 0); /// let later = date(2019, 1, 31).at(21, 0, 0, 0); /// assert_eq!( /// later - earlier, /// 4542.days().hours(22).minutes(30).fieldwise(), /// ); /// ``` #[inline] pub fn since>( self, other: A, ) -> Result { let args: DateTimeDifference = other.into(); let span = -args.until_with_largest_unit(self)?; if args.rounding_may_change_span() { span.round(args.round.relative(self)) } else { Ok(span) } } /// Returns an absolute duration representing the elapsed time from this /// datetime until the given `other` datetime. /// /// When `other` occurs before this datetime, then the duration returned /// will be negative. /// /// Unlike [`DateTime::until`], this returns a duration corresponding to a /// 96-bit integer of nanoseconds between two datetimes. /// /// # Fallibility /// /// This routine never panics or returns an error. Since there are no /// configuration options that can be incorrectly provided, no error is /// possible when calling this routine. In contrast, [`DateTime::until`] /// can return an error in some cases due to misconfiguration. But like /// this routine, [`DateTime::until`] never panics or returns an error in /// its default configuration. /// /// # When should I use this versus [`DateTime::until`]? /// /// See the type documentation for [`SignedDuration`] for the section on /// when one should use [`Span`] and when one should use `SignedDuration`. /// In short, use `Span` (and therefore `DateTime::until`) unless you have /// a specific reason to do otherwise. /// /// # Example /// /// ``` /// use jiff::{civil::date, SignedDuration}; /// /// let earlier = date(2006, 8, 24).at(22, 30, 0, 0); /// let later = date(2019, 1, 31).at(21, 0, 0, 0); /// assert_eq!( /// earlier.duration_until(later), /// SignedDuration::from_hours(4542 * 24) /// + SignedDuration::from_hours(22) /// + SignedDuration::from_mins(30), /// ); /// // Flipping the datetimes is fine, but you'll get a negative duration. /// assert_eq!( /// later.duration_until(earlier), /// -SignedDuration::from_hours(4542 * 24) /// - SignedDuration::from_hours(22) /// - SignedDuration::from_mins(30), /// ); /// ``` /// /// # Example: difference with [`DateTime::until`] /// /// The main difference between this routine and `DateTime::until` is that /// the latter can return units other than a 96-bit integer of nanoseconds. /// While a 96-bit integer of nanoseconds can be converted into other units /// like hours, this can only be done for uniform units. (Uniform units are /// units for which each individual unit always corresponds to the same /// elapsed time regardless of the datetime it is relative to.) This can't /// be done for units like years or months. /// /// ``` /// use jiff::{civil::date, SignedDuration, Span, SpanRound, ToSpan, Unit}; /// /// let dt1 = date(2024, 1, 1).at(0, 0, 0, 0); /// let dt2 = date(2025, 4, 1).at(0, 0, 0, 0); /// /// let span = dt1.until((Unit::Year, dt2))?; /// assert_eq!(span, 1.year().months(3).fieldwise()); /// /// let duration = dt1.duration_until(dt2); /// assert_eq!(duration, SignedDuration::from_hours(456 * 24)); /// // There's no way to extract years or months from the signed /// // duration like one might extract hours (because every hour /// // is the same length). Instead, you actually have to convert /// // it to a span and then balance it by providing a relative date! /// let options = SpanRound::new().largest(Unit::Year).relative(dt1); /// let span = Span::try_from(duration)?.round(options)?; /// assert_eq!(span, 1.year().months(3).fieldwise()); /// /// # Ok::<(), Box>(()) /// ``` /// /// # Example: getting an unsigned duration /// /// If you're looking to find the duration between two datetimes as a /// [`std::time::Duration`], you'll need to use this method to get a /// [`SignedDuration`] and then convert it to a `std::time::Duration`: /// /// ``` /// use std::time::Duration; /// /// use jiff::civil::date; /// /// let dt1 = date(2024, 7, 1).at(0, 0, 0, 0); /// let dt2 = date(2024, 8, 1).at(0, 0, 0, 0); /// let duration = Duration::try_from(dt1.duration_until(dt2))?; /// assert_eq!(duration, Duration::from_secs(31 * 24 * 60 * 60)); /// /// // Note that unsigned durations cannot represent all /// // possible differences! If the duration would be negative, /// // then the conversion fails: /// assert!(Duration::try_from(dt2.duration_until(dt1)).is_err()); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn duration_until(self, other: DateTime) -> SignedDuration { SignedDuration::datetime_until(self, other) } /// This routine is identical to [`DateTime::duration_until`], but the /// order of the parameters is flipped. /// /// # Example /// /// ``` /// use jiff::{civil::date, SignedDuration}; /// /// let earlier = date(2006, 8, 24).at(22, 30, 0, 0); /// let later = date(2019, 1, 31).at(21, 0, 0, 0); /// assert_eq!( /// later.duration_since(earlier), /// SignedDuration::from_hours(4542 * 24) /// + SignedDuration::from_hours(22) /// + SignedDuration::from_mins(30), /// ); /// ``` #[inline] pub fn duration_since(self, other: DateTime) -> SignedDuration { SignedDuration::datetime_until(other, self) } /// Rounds this datetime according to the [`DateTimeRound`] configuration /// given. /// /// The principal option is [`DateTimeRound::smallest`], which allows one /// to configure the smallest units in the returned datetime. Rounding /// is what determines whether that unit should keep its current value /// or whether it should be incremented. Moreover, the amount it should /// be incremented can be configured via [`DateTimeRound::increment`]. /// Finally, the rounding strategy itself can be configured via /// [`DateTimeRound::mode`]. /// /// Note that this routine is generic and accepts anything that /// implements `Into`. Some notable implementations are: /// /// * `From for DateTimeRound`, which will automatically create a /// `DateTimeRound::new().smallest(unit)` from the unit provided. /// * `From<(Unit, i64)> for DateTimeRound`, which will automatically /// create a `DateTimeRound::new().smallest(unit).increment(number)` from /// the unit and increment provided. /// /// # Errors /// /// This returns an error if the smallest unit configured on the given /// [`DateTimeRound`] is bigger than days. An error is also returned if /// the rounding increment is greater than 1 when the units are days. /// (Currently, rounding to the nearest week, month or year is not /// supported.) /// /// When the smallest unit is less than days, the rounding increment must /// divide evenly into the next highest unit after the smallest unit /// configured (and must not be equivalent to it). For example, if the /// smallest unit is [`Unit::Nanosecond`], then *some* of the valid values /// for the rounding increment are `1`, `2`, `4`, `5`, `100` and `500`. /// Namely, any integer that divides evenly into `1,000` nanoseconds since /// there are `1,000` nanoseconds in the next highest unit (microseconds). /// /// This can also return an error in some cases where rounding would /// require arithmetic that exceeds the maximum datetime value. /// /// # Example /// /// This is a basic example that demonstrates rounding a datetime to the /// nearest day. This also demonstrates calling this method with the /// smallest unit directly, instead of constructing a `DateTimeRound` /// manually. /// /// ``` /// use jiff::{civil::date, Unit}; /// /// let dt = date(2024, 6, 19).at(15, 0, 0, 0); /// assert_eq!(dt.round(Unit::Day)?, date(2024, 6, 20).at(0, 0, 0, 0)); /// let dt = date(2024, 6, 19).at(10, 0, 0, 0); /// assert_eq!(dt.round(Unit::Day)?, date(2024, 6, 19).at(0, 0, 0, 0)); /// /// # Ok::<(), Box>(()) /// ``` /// /// # Example: changing the rounding mode /// /// The default rounding mode is [`RoundMode::HalfExpand`], which /// breaks ties by rounding away from zero. But other modes like /// [`RoundMode::Trunc`] can be used too: /// /// ``` /// use jiff::{civil::{DateTimeRound, date}, RoundMode, Unit}; /// /// let dt = date(2024, 6, 19).at(15, 0, 0, 0); /// assert_eq!(dt.round(Unit::Day)?, date(2024, 6, 20).at(0, 0, 0, 0)); /// // The default will round up to the next day for any time past noon, /// // but using truncation rounding will always round down. /// assert_eq!( /// dt.round( /// DateTimeRound::new().smallest(Unit::Day).mode(RoundMode::Trunc), /// )?, /// date(2024, 6, 19).at(0, 0, 0, 0), /// ); /// /// # Ok::<(), Box>(()) /// ``` /// /// # Example: rounding to the nearest 5 minute increment /// /// ``` /// use jiff::{civil::date, Unit}; /// /// // rounds down /// let dt = date(2024, 6, 19).at(15, 27, 29, 999_999_999); /// assert_eq!( /// dt.round((Unit::Minute, 5))?, /// date(2024, 6, 19).at(15, 25, 0, 0), /// ); /// // rounds up /// let dt = date(2024, 6, 19).at(15, 27, 30, 0); /// assert_eq!( /// dt.round((Unit::Minute, 5))?, /// date(2024, 6, 19).at(15, 30, 0, 0), /// ); /// /// # Ok::<(), Box>(()) /// ``` /// /// # Example: overflow error /// /// This example demonstrates that it's possible for this operation to /// result in an error from datetime arithmetic overflow. /// /// ``` /// use jiff::{civil::DateTime, Unit}; /// /// let dt = DateTime::MAX; /// assert!(dt.round(Unit::Day).is_err()); /// ``` /// /// This occurs because rounding to the nearest day for the maximum /// datetime would result in rounding up to the next day. But the next day /// is greater than the maximum, and so this returns an error. /// /// If one were to use a rounding mode like [`RoundMode::Trunc`] (which /// will never round up), always set a correct increment and always used /// units less than or equal to days, then this routine is guaranteed to /// never fail: /// /// ``` /// use jiff::{civil::{DateTime, DateTimeRound, date}, RoundMode, Unit}; /// /// let round = DateTimeRound::new() /// .smallest(Unit::Day) /// .mode(RoundMode::Trunc); /// assert_eq!( /// DateTime::MAX.round(round)?, /// date(9999, 12, 31).at(0, 0, 0, 0), /// ); /// assert_eq!( /// DateTime::MIN.round(round)?, /// date(-9999, 1, 1).at(0, 0, 0, 0), /// ); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn round>( self, options: R, ) -> Result { let options: DateTimeRound = options.into(); options.round(t::NANOS_PER_CIVIL_DAY, self) } /// Return an iterator of periodic datetimes determined by the given span. /// /// The given span may be negative, in which case, the iterator will move /// backwards through time. The iterator won't stop until either the span /// itself overflows, or it would otherwise exceed the minimum or maximum /// `DateTime` value. /// /// # Example: when to check a glucose monitor /// /// When my cat had diabetes, my veterinarian installed a glucose monitor /// and instructed me to scan it about every 5 hours. This example lists /// all of the times I need to scan it for the 2 days following its /// installation: /// /// ``` /// use jiff::{civil::datetime, ToSpan}; /// /// let start = datetime(2023, 7, 15, 16, 30, 0, 0); /// let end = start.checked_add(2.days())?; /// let mut scan_times = vec![]; /// for dt in start.series(5.hours()).take_while(|&dt| dt <= end) { /// scan_times.push(dt); /// } /// assert_eq!(scan_times, vec![ /// datetime(2023, 7, 15, 16, 30, 0, 0), /// datetime(2023, 7, 15, 21, 30, 0, 0), /// datetime(2023, 7, 16, 2, 30, 0, 0), /// datetime(2023, 7, 16, 7, 30, 0, 0), /// datetime(2023, 7, 16, 12, 30, 0, 0), /// datetime(2023, 7, 16, 17, 30, 0, 0), /// datetime(2023, 7, 16, 22, 30, 0, 0), /// datetime(2023, 7, 17, 3, 30, 0, 0), /// datetime(2023, 7, 17, 8, 30, 0, 0), /// datetime(2023, 7, 17, 13, 30, 0, 0), /// ]); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn series(self, period: Span) -> DateTimeSeries { DateTimeSeries { start: self, period, step: 0 } } /// Converts this datetime to a nanosecond timestamp assuming a Zulu time /// zone offset and where all days are exactly 24 hours long. #[inline] fn to_nanosecond(self) -> t::NoUnits128 { let day_nano = self.date().to_unix_epoch_days(); let time_nano = self.time().to_nanosecond(); (t::NoUnits128::rfrom(day_nano) * t::NANOS_PER_CIVIL_DAY) + time_nano } } /// Parsing and formatting using a "printf"-style API. impl DateTime { /// Parses a civil datetime in `input` matching the given `format`. /// /// The format string uses a "printf"-style API where conversion /// specifiers can be used as place holders to match components of /// a datetime. For details on the specifiers supported, see the /// [`fmt::strtime`] module documentation. /// /// # Errors /// /// This returns an error when parsing failed. This might happen because /// the format string itself was invalid, or because the input didn't match /// the format string. /// /// This also returns an error if there wasn't sufficient information to /// construct a civil datetime. For example, if an offset wasn't parsed. /// /// # Example /// /// This example shows how to parse a civil datetime: /// /// ``` /// use jiff::civil::DateTime; /// /// let dt = DateTime::strptime("%F %H:%M", "2024-07-14 21:14")?; /// assert_eq!(dt.to_string(), "2024-07-14T21:14:00"); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn strptime( format: impl AsRef<[u8]>, input: impl AsRef<[u8]>, ) -> Result { fmt::strtime::parse(format, input).and_then(|tm| tm.to_datetime()) } /// Formats this civil datetime according to the given `format`. /// /// The format string uses a "printf"-style API where conversion /// specifiers can be used as place holders to format components of /// a datetime. For details on the specifiers supported, see the /// [`fmt::strtime`] module documentation. /// /// # Errors and panics /// /// While this routine itself does not error or panic, using the value /// returned may result in a panic if formatting fails. See the /// documentation on [`fmt::strtime::Display`] for more information. /// /// To format in a way that surfaces errors without panicking, use either /// [`fmt::strtime::format`] or [`fmt::strtime::BrokenDownTime::format`]. /// /// # Example /// /// This example shows how to format a civil datetime: /// /// ``` /// use jiff::civil::date; /// /// let dt = date(2024, 7, 15).at(16, 24, 59, 0); /// let string = dt.strftime("%A, %B %e, %Y at %H:%M:%S").to_string(); /// assert_eq!(string, "Monday, July 15, 2024 at 16:24:59"); /// ``` #[inline] pub fn strftime<'f, F: 'f + ?Sized + AsRef<[u8]>>( &self, format: &'f F, ) -> fmt::strtime::Display<'f> { fmt::strtime::Display { fmt: format.as_ref(), tm: (*self).into() } } } /// Deprecated APIs. impl DateTime { /// A deprecated equivalent to [`DateTime::in_tz`]. /// /// This will be removed in `jiff 0.2`. The method was renamed to make /// it clearer that the name stood for "in time zone." #[deprecated(since = "0.1.25", note = "use DateTime::in_tz instead")] #[inline] pub fn intz(self, time_zone_name: &str) -> Result { let tz = crate::tz::db().get(time_zone_name)?; self.to_zoned(tz) } } impl Default for DateTime { #[inline] fn default() -> DateTime { DateTime::ZERO } } /// Converts a `DateTime` into a human readable datetime string. /// /// (This `Debug` representation currently emits the same string as the /// `Display` representation, but this is not a guarantee.) /// /// Options currently supported: /// /// * [`std::fmt::Formatter::precision`] can be set to control the precision /// of the fractional second component. /// /// # Example /// /// ``` /// use jiff::civil::date; /// /// let dt = date(2024, 6, 15).at(7, 0, 0, 123_000_000); /// assert_eq!(format!("{dt:.6?}"), "2024-06-15T07:00:00.123000"); /// // Precision values greater than 9 are clamped to 9. /// assert_eq!(format!("{dt:.300?}"), "2024-06-15T07:00:00.123000000"); /// // A precision of 0 implies the entire fractional /// // component is always truncated. /// assert_eq!(format!("{dt:.0?}"), "2024-06-15T07:00:00"); /// /// # Ok::<(), Box>(()) /// ``` impl core::fmt::Debug for DateTime { #[inline] fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { core::fmt::Display::fmt(self, f) } } /// Converts a `DateTime` into an ISO 8601 compliant string. /// /// Options currently supported: /// /// * [`std::fmt::Formatter::precision`] can be set to control the precision /// of the fractional second component. /// /// # Example /// /// ``` /// use jiff::civil::date; /// /// let dt = date(2024, 6, 15).at(7, 0, 0, 123_000_000); /// assert_eq!(format!("{dt:.6}"), "2024-06-15T07:00:00.123000"); /// // Precision values greater than 9 are clamped to 9. /// assert_eq!(format!("{dt:.300}"), "2024-06-15T07:00:00.123000000"); /// // A precision of 0 implies the entire fractional /// // component is always truncated. /// assert_eq!(format!("{dt:.0}"), "2024-06-15T07:00:00"); /// /// # Ok::<(), Box>(()) /// ``` impl core::fmt::Display for DateTime { #[inline] fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { use crate::fmt::StdFmtWrite; let precision = f.precision().map(|p| u8::try_from(p).unwrap_or(u8::MAX)); temporal::DateTimePrinter::new() .precision(precision) .print_datetime(self, StdFmtWrite(f)) .map_err(|_| core::fmt::Error) } } impl core::str::FromStr for DateTime { type Err = Error; #[inline] fn from_str(string: &str) -> Result { DEFAULT_DATETIME_PARSER.parse_datetime(string) } } /// Converts a [`Date`] to a [`DateTime`] with the time set to midnight. impl From for DateTime { #[inline] fn from(date: Date) -> DateTime { date.to_datetime(Time::midnight()) } } /// Converts a [`Zoned`] to a [`DateTime`]. impl From for DateTime { #[inline] fn from(zdt: Zoned) -> DateTime { zdt.datetime() } } /// Converts a [`&Zoned`](Zoned) to a [`DateTime`]. impl<'a> From<&'a Zoned> for DateTime { #[inline] fn from(zdt: &'a Zoned) -> DateTime { zdt.datetime() } } /// Adds a span of time to a datetime. /// /// This uses checked arithmetic and panics on overflow. To handle overflow /// without panics, use [`DateTime::checked_add`]. impl core::ops::Add for DateTime { type Output = DateTime; #[inline] fn add(self, rhs: Span) -> DateTime { self.checked_add(rhs).expect("adding span to datetime overflowed") } } /// Adds a span of time to a datetime in place. /// /// This uses checked arithmetic and panics on overflow. To handle overflow /// without panics, use [`DateTime::checked_add`]. impl core::ops::AddAssign for DateTime { #[inline] fn add_assign(&mut self, rhs: Span) { *self = *self + rhs } } /// Subtracts a span of time from a datetime. /// /// This uses checked arithmetic and panics on overflow. To handle overflow /// without panics, use [`DateTime::checked_sub`]. impl core::ops::Sub for DateTime { type Output = DateTime; #[inline] fn sub(self, rhs: Span) -> DateTime { self.checked_sub(rhs) .expect("subtracting span from datetime overflowed") } } /// Subtracts a span of time from a datetime in place. /// /// This uses checked arithmetic and panics on overflow. To handle overflow /// without panics, use [`DateTime::checked_sub`]. impl core::ops::SubAssign for DateTime { #[inline] fn sub_assign(&mut self, rhs: Span) { *self = *self - rhs } } /// Computes the span of time between two datetimes. /// /// This will return a negative span when the datetime being subtracted is /// greater. /// /// Since this uses the default configuration for calculating a span between /// two datetimes (no rounding and largest units is days), this will never /// panic or fail in any way. /// /// To configure the largest unit or enable rounding, use [`DateTime::since`]. /// /// If you need a [`SignedDuration`] representing the span between two civil /// datetimes, then use [`DateTime::duration_since`]. impl core::ops::Sub for DateTime { type Output = Span; #[inline] fn sub(self, rhs: DateTime) -> Span { self.since(rhs).expect("since never fails when given DateTime") } } /// Adds a signed duration of time to a datetime. /// /// This uses checked arithmetic and panics on overflow. To handle overflow /// without panics, use [`DateTime::checked_add`]. impl core::ops::Add for DateTime { type Output = DateTime; #[inline] fn add(self, rhs: SignedDuration) -> DateTime { self.checked_add(rhs) .expect("adding signed duration to datetime overflowed") } } /// Adds a signed duration of time to a datetime in place. /// /// This uses checked arithmetic and panics on overflow. To handle overflow /// without panics, use [`DateTime::checked_add`]. impl core::ops::AddAssign for DateTime { #[inline] fn add_assign(&mut self, rhs: SignedDuration) { *self = *self + rhs } } /// Subtracts a signed duration of time from a datetime. /// /// This uses checked arithmetic and panics on overflow. To handle overflow /// without panics, use [`DateTime::checked_sub`]. impl core::ops::Sub for DateTime { type Output = DateTime; #[inline] fn sub(self, rhs: SignedDuration) -> DateTime { self.checked_sub(rhs) .expect("subtracting signed duration from datetime overflowed") } } /// Subtracts a signed duration of time from a datetime in place. /// /// This uses checked arithmetic and panics on overflow. To handle overflow /// without panics, use [`DateTime::checked_sub`]. impl core::ops::SubAssign for DateTime { #[inline] fn sub_assign(&mut self, rhs: SignedDuration) { *self = *self - rhs } } /// Adds an unsigned duration of time to a datetime. /// /// This uses checked arithmetic and panics on overflow. To handle overflow /// without panics, use [`DateTime::checked_add`]. impl core::ops::Add for DateTime { type Output = DateTime; #[inline] fn add(self, rhs: UnsignedDuration) -> DateTime { self.checked_add(rhs) .expect("adding unsigned duration to datetime overflowed") } } /// Adds an unsigned duration of time to a datetime in place. /// /// This uses checked arithmetic and panics on overflow. To handle overflow /// without panics, use [`DateTime::checked_add`]. impl core::ops::AddAssign for DateTime { #[inline] fn add_assign(&mut self, rhs: UnsignedDuration) { *self = *self + rhs } } /// Subtracts an unsigned duration of time from a datetime. /// /// This uses checked arithmetic and panics on overflow. To handle overflow /// without panics, use [`DateTime::checked_sub`]. impl core::ops::Sub for DateTime { type Output = DateTime; #[inline] fn sub(self, rhs: UnsignedDuration) -> DateTime { self.checked_sub(rhs) .expect("subtracting unsigned duration from datetime overflowed") } } /// Subtracts an unsigned duration of time from a datetime in place. /// /// This uses checked arithmetic and panics on overflow. To handle overflow /// without panics, use [`DateTime::checked_sub`]. impl core::ops::SubAssign for DateTime { #[inline] fn sub_assign(&mut self, rhs: UnsignedDuration) { *self = *self - rhs } } #[cfg(feature = "serde")] impl serde::Serialize for DateTime { #[inline] fn serialize( &self, serializer: S, ) -> Result { serializer.collect_str(self) } } #[cfg(feature = "serde")] impl<'de> serde::Deserialize<'de> for DateTime { #[inline] fn deserialize>( deserializer: D, ) -> Result { use serde::de; struct DateTimeVisitor; impl<'de> de::Visitor<'de> for DateTimeVisitor { type Value = DateTime; fn expecting( &self, f: &mut core::fmt::Formatter, ) -> core::fmt::Result { f.write_str("a datetime string") } #[inline] fn visit_bytes( self, value: &[u8], ) -> Result { DEFAULT_DATETIME_PARSER .parse_datetime(value) .map_err(de::Error::custom) } #[inline] fn visit_str( self, value: &str, ) -> Result { self.visit_bytes(value.as_bytes()) } } deserializer.deserialize_str(DateTimeVisitor) } } #[cfg(test)] impl quickcheck::Arbitrary for DateTime { fn arbitrary(g: &mut quickcheck::Gen) -> DateTime { let date = Date::arbitrary(g); let time = Time::arbitrary(g); DateTime::from_parts(date, time) } fn shrink(&self) -> alloc::boxed::Box> { alloc::boxed::Box::new( (self.date(), self.time()) .shrink() .map(|(date, time)| DateTime::from_parts(date, time)), ) } } /// An iterator over periodic datetimes, created by [`DateTime::series`]. /// /// It is exhausted when the next value would exceed a [`Span`] or [`DateTime`] /// value. #[derive(Clone, Debug)] pub struct DateTimeSeries { start: DateTime, period: Span, step: i64, } impl Iterator for DateTimeSeries { type Item = DateTime; #[inline] fn next(&mut self) -> Option { let span = self.period.checked_mul(self.step).ok()?; self.step = self.step.checked_add(1)?; let date = self.start.checked_add(span).ok()?; Some(date) } } /// Options for [`DateTime::checked_add`] and [`DateTime::checked_sub`]. /// /// This type provides a way to ergonomically add one of a few different /// duration types to a [`DateTime`]. /// /// The main way to construct values of this type is with its `From` trait /// implementations: /// /// * `From for DateTimeArithmetic` adds (or subtracts) the given span to /// the receiver datetime. /// * `From for DateTimeArithmetic` adds (or subtracts) /// the given signed duration to the receiver datetime. /// * `From for DateTimeArithmetic` adds (or subtracts) /// the given unsigned duration to the receiver datetime. /// /// # Example /// /// ``` /// use std::time::Duration; /// /// use jiff::{civil::date, SignedDuration, ToSpan}; /// /// let dt = date(2024, 2, 29).at(0, 0, 0, 0); /// assert_eq!( /// dt.checked_add(1.year())?, /// date(2025, 2, 28).at(0, 0, 0, 0), /// ); /// assert_eq!( /// dt.checked_add(SignedDuration::from_hours(24))?, /// date(2024, 3, 1).at(0, 0, 0, 0), /// ); /// assert_eq!( /// dt.checked_add(Duration::from_secs(24 * 60 * 60))?, /// date(2024, 3, 1).at(0, 0, 0, 0), /// ); /// /// # Ok::<(), Box>(()) /// ``` #[derive(Clone, Copy, Debug)] pub struct DateTimeArithmetic { duration: Duration, } impl DateTimeArithmetic { #[inline] fn checked_add(self, dt: DateTime) -> Result { match self.duration.to_signed()? { SDuration::Span(span) => dt.checked_add_span(span), SDuration::Absolute(sdur) => dt.checked_add_duration(sdur), } } #[inline] fn checked_neg(self) -> Result { let duration = self.duration.checked_neg()?; Ok(DateTimeArithmetic { duration }) } #[inline] fn is_negative(&self) -> bool { self.duration.is_negative() } } impl From for DateTimeArithmetic { fn from(span: Span) -> DateTimeArithmetic { let duration = Duration::from(span); DateTimeArithmetic { duration } } } impl From for DateTimeArithmetic { fn from(sdur: SignedDuration) -> DateTimeArithmetic { let duration = Duration::from(sdur); DateTimeArithmetic { duration } } } impl From for DateTimeArithmetic { fn from(udur: UnsignedDuration) -> DateTimeArithmetic { let duration = Duration::from(udur); DateTimeArithmetic { duration } } } impl<'a> From<&'a Span> for DateTimeArithmetic { fn from(span: &'a Span) -> DateTimeArithmetic { DateTimeArithmetic::from(*span) } } impl<'a> From<&'a SignedDuration> for DateTimeArithmetic { fn from(sdur: &'a SignedDuration) -> DateTimeArithmetic { DateTimeArithmetic::from(*sdur) } } impl<'a> From<&'a UnsignedDuration> for DateTimeArithmetic { fn from(udur: &'a UnsignedDuration) -> DateTimeArithmetic { DateTimeArithmetic::from(*udur) } } /// Options for [`DateTime::since`] and [`DateTime::until`]. /// /// This type provides a way to configure the calculation of /// spans between two [`DateTime`] values. In particular, both /// `DateTime::since` and `DateTime::until` accept anything that implements /// `Into`. There are a few key trait implementations that /// make this convenient: /// /// * `From for DateTimeDifference` will construct a configuration /// consisting of just the datetime. So for example, `dt1.since(dt2)` returns /// the span from `dt2` to `dt1`. /// * `From for DateTimeDifference` will construct a configuration /// consisting of just the datetime built from the date given at midnight on /// that day. /// * `From<(Unit, DateTime)>` is a convenient way to specify the largest units /// that should be present on the span returned. By default, the largest units /// are days. Using this trait implementation is equivalent to /// `DateTimeDifference::new(datetime).largest(unit)`. /// * `From<(Unit, Date)>` is like the one above, but with the time component /// fixed to midnight. /// /// One can also provide a `DateTimeDifference` value directly. Doing so /// is necessary to use the rounding features of calculating a span. For /// example, setting the smallest unit (defaults to [`Unit::Nanosecond`]), the /// rounding mode (defaults to [`RoundMode::Trunc`]) and the rounding increment /// (defaults to `1`). The defaults are selected such that no rounding occurs. /// /// Rounding a span as part of calculating it is provided as a convenience. /// Callers may choose to round the span as a distinct step via /// [`Span::round`], but callers may need to provide a reference date /// for rounding larger units. By coupling rounding with routines like /// [`DateTime::since`], the reference date can be set automatically based on /// the input to `DateTime::since`. /// /// # Example /// /// This example shows how to round a span between two datetimes to the nearest /// half-hour, with ties breaking away from zero. /// /// ``` /// use jiff::{civil::{DateTime, DateTimeDifference}, RoundMode, ToSpan, Unit}; /// /// let dt1 = "2024-03-15 08:14:00.123456789".parse::()?; /// let dt2 = "2030-03-22 15:00".parse::()?; /// let span = dt1.until( /// DateTimeDifference::new(dt2) /// .smallest(Unit::Minute) /// .largest(Unit::Year) /// .mode(RoundMode::HalfExpand) /// .increment(30), /// )?; /// assert_eq!(span, 6.years().days(7).hours(7).fieldwise()); /// /// # Ok::<(), Box>(()) /// ``` #[derive(Clone, Copy, Debug)] pub struct DateTimeDifference { datetime: DateTime, round: SpanRound<'static>, } impl DateTimeDifference { /// Create a new default configuration for computing the span between the /// given datetime and some other datetime (specified as the receiver in /// [`DateTime::since`] or [`DateTime::until`]). #[inline] pub fn new(datetime: DateTime) -> DateTimeDifference { // We use truncation rounding by default since it seems that's // what is generally expected when computing the difference between // datetimes. // // See: https://github.com/tc39/proposal-temporal/issues/1122 let round = SpanRound::new().mode(RoundMode::Trunc); DateTimeDifference { datetime, round } } /// Set the smallest units allowed in the span returned. /// /// When a largest unit is not specified and the smallest unit is days /// or greater, then the largest unit is automatically set to be equal to /// the smallest unit. /// /// # Errors /// /// The smallest units must be no greater than the largest units. If this /// is violated, then computing a span with this configuration will result /// in an error. /// /// # Example /// /// This shows how to round a span between two datetimes to the nearest /// number of weeks. /// /// ``` /// use jiff::{ /// civil::{DateTime, DateTimeDifference}, /// RoundMode, ToSpan, Unit, /// }; /// /// let dt1 = "2024-03-15 08:14".parse::()?; /// let dt2 = "2030-11-22 08:30".parse::()?; /// let span = dt1.until( /// DateTimeDifference::new(dt2) /// .smallest(Unit::Week) /// .largest(Unit::Week) /// .mode(RoundMode::HalfExpand), /// )?; /// assert_eq!(span, 349.weeks().fieldwise()); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn smallest(self, unit: Unit) -> DateTimeDifference { DateTimeDifference { round: self.round.smallest(unit), ..self } } /// Set the largest units allowed in the span returned. /// /// When a largest unit is not specified and the smallest unit is days /// or greater, then the largest unit is automatically set to be equal to /// the smallest unit. Otherwise, when the largest unit is not specified, /// it is set to days. /// /// Once a largest unit is set, there is no way to change this rounding /// configuration back to using the "automatic" default. Instead, callers /// must create a new configuration. /// /// # Errors /// /// The largest units, when set, must be at least as big as the smallest /// units (which defaults to [`Unit::Nanosecond`]). If this is violated, /// then computing a span with this configuration will result in an error. /// /// # Example /// /// This shows how to round a span between two datetimes to units no /// bigger than seconds. /// /// ``` /// use jiff::{civil::{DateTime, DateTimeDifference}, ToSpan, Unit}; /// /// let dt1 = "2024-03-15 08:14".parse::()?; /// let dt2 = "2030-11-22 08:30".parse::()?; /// let span = dt1.until( /// DateTimeDifference::new(dt2).largest(Unit::Second), /// )?; /// assert_eq!(span, 211076160.seconds().fieldwise()); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn largest(self, unit: Unit) -> DateTimeDifference { DateTimeDifference { round: self.round.largest(unit), ..self } } /// Set the rounding mode. /// /// This defaults to [`RoundMode::Trunc`] since it's plausible that /// rounding "up" in the context of computing the span between /// two datetimes could be surprising in a number of cases. The /// [`RoundMode::HalfExpand`] mode corresponds to typical rounding you /// might have learned about in school. But a variety of other rounding /// modes exist. /// /// # Example /// /// This shows how to always round "up" towards positive infinity. /// /// ``` /// use jiff::{ /// civil::{DateTime, DateTimeDifference}, /// RoundMode, ToSpan, Unit, /// }; /// /// let dt1 = "2024-03-15 08:10".parse::()?; /// let dt2 = "2024-03-15 08:11".parse::()?; /// let span = dt1.until( /// DateTimeDifference::new(dt2) /// .smallest(Unit::Hour) /// .mode(RoundMode::Ceil), /// )?; /// // Only one minute elapsed, but we asked to always round up! /// assert_eq!(span, 1.hour().fieldwise()); /// /// // Since `Ceil` always rounds toward positive infinity, the behavior /// // flips for a negative span. /// let span = dt1.since( /// DateTimeDifference::new(dt2) /// .smallest(Unit::Hour) /// .mode(RoundMode::Ceil), /// )?; /// assert_eq!(span, 0.hour().fieldwise()); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn mode(self, mode: RoundMode) -> DateTimeDifference { DateTimeDifference { round: self.round.mode(mode), ..self } } /// Set the rounding increment for the smallest unit. /// /// The default value is `1`. Other values permit rounding the smallest /// unit to the nearest integer increment specified. For example, if the /// smallest unit is set to [`Unit::Minute`], then a rounding increment of /// `30` would result in rounding in increments of a half hour. That is, /// the only minute value that could result would be `0` or `30`. /// /// # Errors /// /// When the smallest unit is less than days, the rounding increment must /// divide evenly into the next highest unit after the smallest unit /// configured (and must not be equivalent to it). For example, if the /// smallest unit is [`Unit::Nanosecond`], then *some* of the valid values /// for the rounding increment are `1`, `2`, `4`, `5`, `100` and `500`. /// Namely, any integer that divides evenly into `1,000` nanoseconds since /// there are `1,000` nanoseconds in the next highest unit (microseconds). /// /// The error will occur when computing the span, and not when setting /// the increment here. /// /// # Example /// /// This shows how to round the span between two datetimes to the nearest /// 5 minute increment. /// /// ``` /// use jiff::{ /// civil::{DateTime, DateTimeDifference}, /// RoundMode, ToSpan, Unit, /// }; /// /// let dt1 = "2024-03-15 08:19".parse::()?; /// let dt2 = "2024-03-15 12:52".parse::()?; /// let span = dt1.until( /// DateTimeDifference::new(dt2) /// .smallest(Unit::Minute) /// .increment(5) /// .mode(RoundMode::HalfExpand), /// )?; /// assert_eq!(span, 4.hour().minutes(35).fieldwise()); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn increment(self, increment: i64) -> DateTimeDifference { DateTimeDifference { round: self.round.increment(increment), ..self } } /// Returns true if and only if this configuration could change the span /// via rounding. #[inline] fn rounding_may_change_span(&self) -> bool { self.round.rounding_may_change_span_ignore_largest() } /// Returns the span of time from `dt1` to the datetime in this /// configuration. The biggest units allowed are determined by the /// `smallest` and `largest` settings, but defaults to `Unit::Day`. #[inline] fn until_with_largest_unit(&self, dt1: DateTime) -> Result { let dt2 = self.datetime; let largest = self .round .get_largest() .unwrap_or_else(|| self.round.get_smallest().max(Unit::Day)); if largest <= Unit::Day { let diff = dt2.to_nanosecond() - dt1.to_nanosecond(); // Note that this can fail! If largest unit is nanoseconds and the // datetimes are far enough apart, a single i64 won't be able to // represent the time difference. // // This is only true for nanoseconds. A single i64 in units of // microseconds can represent the interval between all valid // datetimes. (At time of writing.) return Span::from_invariant_nanoseconds(largest, diff); } let (d1, mut d2) = (dt1.date(), dt2.date()); let (t1, t2) = (dt1.time(), dt2.time()); let sign = t::sign(d2, d1); let mut time_diff = t1.until_nanoseconds(t2); if time_diff.signum() == -sign { // These unwraps will always succeed, but the argument for why is // subtle. The key here is that the only way, e.g., d2.tomorrow() // can fail is when d2 is the max date. But, if d2 is the max date, // then it's impossible for `sign < 0` since the max date is at // least as big as every other date. And thus, d2.tomorrow() is // never reached in cases where it would fail. if sign > 0 { d2 = d2.yesterday().unwrap(); } else if sign < 0 { d2 = d2.tomorrow().unwrap(); } time_diff += t::SpanNanoseconds::rfrom(t::NANOS_PER_CIVIL_DAY) * sign; } let date_span = d1.until((largest, d2))?; Ok(Span::from_invariant_nanoseconds(largest, time_diff) // Unlike in the <=Unit::Day case, this always succeeds because // every unit except for nanoseconds (which is not used here) can // represent all possible spans of time between any two civil // datetimes. .expect("difference between time always fits in span") .years_ranged(date_span.get_years_ranged()) .months_ranged(date_span.get_months_ranged()) .weeks_ranged(date_span.get_weeks_ranged()) .days_ranged(date_span.get_days_ranged())) } } impl From for DateTimeDifference { #[inline] fn from(dt: DateTime) -> DateTimeDifference { DateTimeDifference::new(dt) } } impl From for DateTimeDifference { #[inline] fn from(date: Date) -> DateTimeDifference { DateTimeDifference::from(DateTime::from(date)) } } impl From for DateTimeDifference { #[inline] fn from(zdt: Zoned) -> DateTimeDifference { DateTimeDifference::from(DateTime::from(zdt)) } } impl<'a> From<&'a Zoned> for DateTimeDifference { #[inline] fn from(zdt: &'a Zoned) -> DateTimeDifference { DateTimeDifference::from(zdt.datetime()) } } impl From<(Unit, DateTime)> for DateTimeDifference { #[inline] fn from((largest, dt): (Unit, DateTime)) -> DateTimeDifference { DateTimeDifference::from(dt).largest(largest) } } impl From<(Unit, Date)> for DateTimeDifference { #[inline] fn from((largest, date): (Unit, Date)) -> DateTimeDifference { DateTimeDifference::from(date).largest(largest) } } impl From<(Unit, Zoned)> for DateTimeDifference { #[inline] fn from((largest, zdt): (Unit, Zoned)) -> DateTimeDifference { DateTimeDifference::from((largest, DateTime::from(zdt))) } } impl<'a> From<(Unit, &'a Zoned)> for DateTimeDifference { #[inline] fn from((largest, zdt): (Unit, &'a Zoned)) -> DateTimeDifference { DateTimeDifference::from((largest, zdt.datetime())) } } /// Options for [`DateTime::round`]. /// /// This type provides a way to configure the rounding of a civil datetime. In /// particular, `DateTime::round` accepts anything that implements the /// `Into` trait. There are some trait implementations that /// therefore make calling `DateTime::round` in some common cases more /// ergonomic: /// /// * `From for DateTimeRound` will construct a rounding /// configuration that rounds to the unit given. Specifically, /// `DateTimeRound::new().smallest(unit)`. /// * `From<(Unit, i64)> for DateTimeRound` is like the one above, but also /// specifies the rounding increment for [`DateTimeRound::increment`]. /// /// Note that in the default configuration, no rounding occurs. /// /// # Example /// /// This example shows how to round a datetime to the nearest second: /// /// ``` /// use jiff::{civil::{DateTime, date}, Unit}; /// /// let dt: DateTime = "2024-06-20 16:24:59.5".parse()?; /// assert_eq!( /// dt.round(Unit::Second)?, /// // The second rounds up and causes minutes to increase. /// date(2024, 6, 20).at(16, 25, 0, 0), /// ); /// /// # Ok::<(), Box>(()) /// ``` /// /// The above makes use of the fact that `Unit` implements /// `Into`. If you want to change the rounding mode to, say, /// truncation, then you'll need to construct a `DateTimeRound` explicitly /// since there are no convenience `Into` trait implementations for /// [`RoundMode`]. /// /// ``` /// use jiff::{civil::{DateTime, DateTimeRound, date}, RoundMode, Unit}; /// /// let dt: DateTime = "2024-06-20 16:24:59.5".parse()?; /// assert_eq!( /// dt.round( /// DateTimeRound::new().smallest(Unit::Second).mode(RoundMode::Trunc), /// )?, /// // The second just gets truncated as if it wasn't there. /// date(2024, 6, 20).at(16, 24, 59, 0), /// ); /// /// # Ok::<(), Box>(()) /// ``` #[derive(Clone, Copy, Debug)] pub struct DateTimeRound { smallest: Unit, mode: RoundMode, increment: i64, } impl DateTimeRound { /// Create a new default configuration for rounding a [`DateTime`]. #[inline] pub fn new() -> DateTimeRound { DateTimeRound { smallest: Unit::Nanosecond, mode: RoundMode::HalfExpand, increment: 1, } } /// Set the smallest units allowed in the datetime returned after rounding. /// /// Any units below the smallest configured unit will be used, along with /// the rounding increment and rounding mode, to determine the value of the /// smallest unit. For example, when rounding `2024-06-20T03:25:30` to the /// nearest minute, the `30` second unit will result in rounding the minute /// unit of `25` up to `26` and zeroing out everything below minutes. /// /// This defaults to [`Unit::Nanosecond`]. /// /// # Errors /// /// The smallest units must be no greater than [`Unit::Day`]. And when the /// smallest unit is `Unit::Day`, the rounding increment must be equal to /// `1`. Otherwise an error will be returned from [`DateTime::round`]. /// /// # Example /// /// ``` /// use jiff::{civil::{DateTimeRound, date}, Unit}; /// /// let dt = date(2024, 6, 20).at(3, 25, 30, 0); /// assert_eq!( /// dt.round(DateTimeRound::new().smallest(Unit::Minute))?, /// date(2024, 6, 20).at(3, 26, 0, 0), /// ); /// // Or, utilize the `From for DateTimeRound` impl: /// assert_eq!( /// dt.round(Unit::Minute)?, /// date(2024, 6, 20).at(3, 26, 0, 0), /// ); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn smallest(self, unit: Unit) -> DateTimeRound { DateTimeRound { smallest: unit, ..self } } /// Set the rounding mode. /// /// This defaults to [`RoundMode::HalfExpand`], which rounds away from /// zero. It matches the kind of rounding you might have been taught in /// school. /// /// # Example /// /// This shows how to always round datetimes up towards positive infinity. /// /// ``` /// use jiff::{civil::{DateTime, DateTimeRound, date}, RoundMode, Unit}; /// /// let dt: DateTime = "2024-06-20 03:25:01".parse()?; /// assert_eq!( /// dt.round( /// DateTimeRound::new() /// .smallest(Unit::Minute) /// .mode(RoundMode::Ceil), /// )?, /// date(2024, 6, 20).at(3, 26, 0, 0), /// ); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn mode(self, mode: RoundMode) -> DateTimeRound { DateTimeRound { mode, ..self } } /// Set the rounding increment for the smallest unit. /// /// The default value is `1`. Other values permit rounding the smallest /// unit to the nearest integer increment specified. For example, if the /// smallest unit is set to [`Unit::Minute`], then a rounding increment of /// `30` would result in rounding in increments of a half hour. That is, /// the only minute value that could result would be `0` or `30`. /// /// # Errors /// /// When the smallest unit is `Unit::Day`, then the rounding increment must /// be `1` or else [`DateTime::round`] will return an error. /// /// For other units, the rounding increment must divide evenly into the /// next highest unit above the smallest unit set. The rounding increment /// must also not be equal to the next highest unit. For example, if the /// smallest unit is [`Unit::Nanosecond`], then *some* of the valid values /// for the rounding increment are `1`, `2`, `4`, `5`, `100` and `500`. /// Namely, any integer that divides evenly into `1,000` nanoseconds since /// there are `1,000` nanoseconds in the next highest unit (microseconds). /// /// # Example /// /// This example shows how to round a datetime to the nearest 10 minute /// increment. /// /// ``` /// use jiff::{civil::{DateTime, DateTimeRound, date}, RoundMode, Unit}; /// /// let dt: DateTime = "2024-06-20 03:24:59".parse()?; /// assert_eq!( /// dt.round((Unit::Minute, 10))?, /// date(2024, 6, 20).at(3, 20, 0, 0), /// ); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn increment(self, increment: i64) -> DateTimeRound { DateTimeRound { increment, ..self } } /// Does the actual rounding. /// /// A non-public configuration here is the length of a day. For civil /// datetimes, this should always be `NANOS_PER_CIVIL_DAY`. But this /// rounding routine is also used for `Zoned` rounding, and in that /// context, the length of a day can vary based on the time zone. pub(crate) fn round( &self, day_length: impl RInto, dt: DateTime, ) -> Result { // ref: https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.round let day_length = t::NoUnits128::rfrom(day_length.rinto()); let increment = increment::for_datetime(self.smallest, self.increment)?; // We permit rounding to any time unit and days, but nothing else. // We should support this, but Temporal doesn't. So for now, we're // sticking to what Temporal does because they're probably not doing // it for good reasons. match self.smallest { Unit::Year | Unit::Month | Unit::Week => { return Err(err!( "rounding datetimes does not support {unit}", unit = self.smallest.plural() )); } // We don't do any rounding in this case, so just bail now. Unit::Nanosecond if increment == 1 => { return Ok(dt); } _ => {} } let time_nanos = dt.time().to_nanosecond(); let sign = t::NoUnits128::rfrom(dt.date().year_ranged().signum()); let time_rounded = self.mode.round_by_unit_in_nanoseconds( time_nanos, self.smallest, increment, ); let days = sign * time_rounded.div_ceil(day_length); let time_nanos = time_rounded.rem_ceil(day_length); let time = Time::from_nanosecond(time_nanos); let date_days = t::SpanDays::rfrom(dt.date().day_ranged()); // OK because days is limited by the fact that the length of a day // can't be any smaller than 1 second, and the number of nanoseconds in // a civil day is capped. let days_len = (date_days - C(1)) + days; // OK because the first day of any month is always valid. let start = dt.date().first_of_month(); // `days` should basically always be <= 1, and so `days_len` should // always be at most 1 greater (or less) than where we started. But // what if there is a time zone transition that makes 9999-12-31 // shorter than 24 hours? And we are rounding 9999-12-31? Well, then // I guess this could overflow and fail. I suppose it could also fail // for really weird time zone data that made the length of a day really // short. But even then, you'd need to be close to the boundary of // supported datetimes. let end = start .checked_add(Span::new().days_ranged(days_len)) .with_context(|| { err!("adding {days_len} days to {start} failed") })?; Ok(DateTime::from_parts(end, time)) } } impl Default for DateTimeRound { #[inline] fn default() -> DateTimeRound { DateTimeRound::new() } } impl From for DateTimeRound { #[inline] fn from(unit: Unit) -> DateTimeRound { DateTimeRound::default().smallest(unit) } } impl From<(Unit, i64)> for DateTimeRound { #[inline] fn from((unit, increment): (Unit, i64)) -> DateTimeRound { DateTimeRound::from(unit).increment(increment) } } /// A builder for setting the fields on a [`DateTime`]. /// /// This builder is constructed via [`DateTime::with`]. /// /// # Example /// /// The builder ensures one can chain together the individual components of a /// datetime without it failing at an intermediate step. For example, if you /// had a date of `2024-10-31T00:00:00` and wanted to change both the day and /// the month, and each setting was validated independent of the other, you /// would need to be careful to set the day first and then the month. In some /// cases, you would need to set the month first and then the day! /// /// But with the builder, you can set values in any order: /// /// ``` /// use jiff::civil::date; /// /// let dt1 = date(2024, 10, 31).at(0, 0, 0, 0); /// let dt2 = dt1.with().month(11).day(30).build()?; /// assert_eq!(dt2, date(2024, 11, 30).at(0, 0, 0, 0)); /// /// let dt1 = date(2024, 4, 30).at(0, 0, 0, 0); /// let dt2 = dt1.with().day(31).month(7).build()?; /// assert_eq!(dt2, date(2024, 7, 31).at(0, 0, 0, 0)); /// /// # Ok::<(), Box>(()) /// ``` #[derive(Clone, Copy, Debug)] pub struct DateTimeWith { date_with: DateWith, time_with: TimeWith, } impl DateTimeWith { #[inline] fn new(original: DateTime) -> DateTimeWith { DateTimeWith { date_with: original.date().with(), time_with: original.time().with(), } } /// Create a new `DateTime` from the fields set on this configuration. /// /// An error occurs when the fields combine to an invalid datetime. /// /// For any fields not set on this configuration, the values are taken from /// the [`DateTime`] that originally created this configuration. When no /// values are set, this routine is guaranteed to succeed and will always /// return the original datetime without modification. /// /// # Example /// /// This creates a datetime corresponding to the last day in the year at /// noon: /// /// ``` /// use jiff::civil::date; /// /// let dt = date(2023, 1, 1).at(12, 0, 0, 0); /// assert_eq!( /// dt.with().day_of_year_no_leap(365).build()?, /// date(2023, 12, 31).at(12, 0, 0, 0), /// ); /// /// // It also works with leap years for the same input: /// let dt = date(2024, 1, 1).at(12, 0, 0, 0); /// assert_eq!( /// dt.with().day_of_year_no_leap(365).build()?, /// date(2024, 12, 31).at(12, 0, 0, 0), /// ); /// /// # Ok::<(), Box>(()) /// ``` /// /// # Example: error for invalid datetime /// /// If the fields combine to form an invalid date, then an error is /// returned: /// /// ``` /// use jiff::civil::date; /// /// let dt = date(2024, 11, 30).at(15, 30, 0, 0); /// assert!(dt.with().day(31).build().is_err()); /// /// let dt = date(2024, 2, 29).at(15, 30, 0, 0); /// assert!(dt.with().year(2023).build().is_err()); /// ``` #[inline] pub fn build(self) -> Result { let date = self.date_with.build()?; let time = self.time_with.build()?; Ok(DateTime::from_parts(date, time)) } /// Set the year, month and day fields via the `Date` given. /// /// This overrides any previous year, month or day settings. /// /// # Example /// /// This shows how to create a new datetime with a different date: /// /// ``` /// use jiff::civil::date; /// /// let dt1 = date(2005, 11, 5).at(15, 30, 0, 0); /// let dt2 = dt1.with().date(date(2017, 10, 31)).build()?; /// // The date changes but the time remains the same. /// assert_eq!(dt2, date(2017, 10, 31).at(15, 30, 0, 0)); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn date(self, date: Date) -> DateTimeWith { DateTimeWith { date_with: date.with(), ..self } } /// Set the hour, minute, second, millisecond, microsecond and nanosecond /// fields via the `Time` given. /// /// This overrides any previous hour, minute, second, millisecond, /// microsecond, nanosecond or subsecond nanosecond settings. /// /// # Example /// /// This shows how to create a new datetime with a different time: /// /// ``` /// use jiff::civil::{date, time}; /// /// let dt1 = date(2005, 11, 5).at(15, 30, 0, 0); /// let dt2 = dt1.with().time(time(23, 59, 59, 123_456_789)).build()?; /// // The time changes but the date remains the same. /// assert_eq!(dt2, date(2005, 11, 5).at(23, 59, 59, 123_456_789)); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn time(self, time: Time) -> DateTimeWith { DateTimeWith { time_with: time.with(), ..self } } /// Set the year field on a [`DateTime`]. /// /// One can access this value via [`DateTime::year`]. /// /// This overrides any previous year settings. /// /// # Errors /// /// This returns an error when [`DateTimeWith::build`] is called if the /// given year is outside the range `-9999..=9999`. This can also return an /// error if the resulting date is otherwise invalid. /// /// # Example /// /// This shows how to create a new datetime with a different year: /// /// ``` /// use jiff::civil::date; /// /// let dt1 = date(2005, 11, 5).at(15, 30, 0, 0); /// assert_eq!(dt1.year(), 2005); /// let dt2 = dt1.with().year(2007).build()?; /// assert_eq!(dt2.year(), 2007); /// /// # Ok::<(), Box>(()) /// ``` /// /// # Example: only changing the year can fail /// /// For example, while `2024-02-29T01:30:00` is valid, /// `2023-02-29T01:30:00` is not: /// /// ``` /// use jiff::civil::date; /// /// let dt = date(2024, 2, 29).at(1, 30, 0, 0); /// assert!(dt.with().year(2023).build().is_err()); /// ``` #[inline] pub fn year(self, year: i16) -> DateTimeWith { DateTimeWith { date_with: self.date_with.year(year), ..self } } /// Set year of a datetime via its era and its non-negative numeric /// component. /// /// One can access this value via [`DateTime::era_year`]. /// /// # Errors /// /// This returns an error when [`DateTimeWith::build`] is called if the /// year is outside the range for the era specified. For [`Era::BCE`], the /// range is `1..=10000`. For [`Era::CE`], the range is `1..=9999`. /// /// # Example /// /// This shows that `CE` years are equivalent to the years used by this /// crate: /// /// ``` /// use jiff::civil::{Era, date}; /// /// let dt1 = date(2005, 11, 5).at(8, 0, 0, 0); /// assert_eq!(dt1.year(), 2005); /// let dt2 = dt1.with().era_year(2007, Era::CE).build()?; /// assert_eq!(dt2.year(), 2007); /// /// // CE years are always positive and can be at most 9999: /// assert!(dt1.with().era_year(-5, Era::CE).build().is_err()); /// assert!(dt1.with().era_year(10_000, Era::CE).build().is_err()); /// /// # Ok::<(), Box>(()) /// ``` /// /// But `BCE` years always correspond to years less than or equal to `0` /// in this crate: /// /// ``` /// use jiff::civil::{Era, date}; /// /// let dt1 = date(-27, 7, 1).at(8, 22, 30, 0); /// assert_eq!(dt1.year(), -27); /// assert_eq!(dt1.era_year(), (28, Era::BCE)); /// /// let dt2 = dt1.with().era_year(509, Era::BCE).build()?; /// assert_eq!(dt2.year(), -508); /// assert_eq!(dt2.era_year(), (509, Era::BCE)); /// /// let dt2 = dt1.with().era_year(10_000, Era::BCE).build()?; /// assert_eq!(dt2.year(), -9_999); /// assert_eq!(dt2.era_year(), (10_000, Era::BCE)); /// /// // BCE years are always positive and can be at most 10000: /// assert!(dt1.with().era_year(-5, Era::BCE).build().is_err()); /// assert!(dt1.with().era_year(10_001, Era::BCE).build().is_err()); /// /// # Ok::<(), Box>(()) /// ``` /// /// # Example: overrides `DateTimeWith::year` /// /// Setting this option will override any previous `DateTimeWith::year` /// option: /// /// ``` /// use jiff::civil::{Era, date}; /// /// let dt1 = date(2024, 7, 2).at(10, 27, 10, 123); /// let dt2 = dt1.with().year(2000).era_year(1900, Era::CE).build()?; /// assert_eq!(dt2, date(1900, 7, 2).at(10, 27, 10, 123)); /// /// # Ok::<(), Box>(()) /// ``` /// /// Similarly, `DateTimeWith::year` will override any previous call to /// `DateTimeWith::era_year`: /// /// ``` /// use jiff::civil::{Era, date}; /// /// let dt1 = date(2024, 7, 2).at(19, 0, 1, 1); /// let dt2 = dt1.with().era_year(1900, Era::CE).year(2000).build()?; /// assert_eq!(dt2, date(2000, 7, 2).at(19, 0, 1, 1)); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn era_year(self, year: i16, era: Era) -> DateTimeWith { DateTimeWith { date_with: self.date_with.era_year(year, era), ..self } } /// Set the month field on a [`DateTime`]. /// /// One can access this value via [`DateTime::month`]. /// /// This overrides any previous month settings. /// /// # Errors /// /// This returns an error when [`DateTimeWith::build`] is called if the /// given month is outside the range `1..=12`. This can also return an /// error if the resulting date is otherwise invalid. /// /// # Example /// /// This shows how to create a new datetime with a different month: /// /// ``` /// use jiff::civil::date; /// /// let dt1 = date(2005, 11, 5).at(18, 3, 59, 123_456_789); /// assert_eq!(dt1.month(), 11); /// let dt2 = dt1.with().month(6).build()?; /// assert_eq!(dt2.month(), 6); /// /// # Ok::<(), Box>(()) /// ``` /// /// # Example: only changing the month can fail /// /// For example, while `2024-10-31T00:00:00` is valid, /// `2024-11-31T00:00:00` is not: /// /// ``` /// use jiff::civil::date; /// /// let dt = date(2024, 10, 31).at(0, 0, 0, 0); /// assert!(dt.with().month(11).build().is_err()); /// ``` #[inline] pub fn month(self, month: i8) -> DateTimeWith { DateTimeWith { date_with: self.date_with.month(month), ..self } } /// Set the day field on a [`DateTime`]. /// /// One can access this value via [`DateTime::day`]. /// /// This overrides any previous day settings. /// /// # Errors /// /// This returns an error when [`DateTimeWith::build`] is called if the /// given given day is outside of allowable days for the corresponding year /// and month fields. /// /// # Example /// /// This shows some examples of setting the day, including a leap day: /// /// ``` /// use jiff::civil::date; /// /// let dt1 = date(2024, 2, 5).at(21, 59, 1, 999); /// assert_eq!(dt1.day(), 5); /// let dt2 = dt1.with().day(10).build()?; /// assert_eq!(dt2.day(), 10); /// let dt3 = dt1.with().day(29).build()?; /// assert_eq!(dt3.day(), 29); /// /// # Ok::<(), Box>(()) /// ``` /// /// # Example: changing only the day can fail /// /// This shows some examples that will fail: /// /// ``` /// use jiff::civil::date; /// /// let dt1 = date(2023, 2, 5).at(22, 58, 58, 9_999); /// // 2023 is not a leap year /// assert!(dt1.with().day(29).build().is_err()); /// /// // September has 30 days, not 31. /// let dt1 = date(2023, 9, 5).at(22, 58, 58, 9_999); /// assert!(dt1.with().day(31).build().is_err()); /// ``` #[inline] pub fn day(self, day: i8) -> DateTimeWith { DateTimeWith { date_with: self.date_with.day(day), ..self } } /// Set the day field on a [`DateTime`] via the ordinal number of a day /// within a year. /// /// When used, any settings for month are ignored since the month is /// determined by the day of the year. /// /// The valid values for `day` are `1..=366`. Note though that `366` is /// only valid for leap years. /// /// This overrides any previous day settings. /// /// # Errors /// /// This returns an error when [`DateTimeWith::build`] is called if the /// given day is outside the allowed range of `1..=366`, or when a value of /// `366` is given for a non-leap year. /// /// # Example /// /// This demonstrates that if a year is a leap year, then `60` corresponds /// to February 29: /// /// ``` /// use jiff::civil::date; /// /// let dt = date(2024, 1, 1).at(23, 59, 59, 999_999_999); /// assert_eq!( /// dt.with().day_of_year(60).build()?, /// date(2024, 2, 29).at(23, 59, 59, 999_999_999), /// ); /// /// # Ok::<(), Box>(()) /// ``` /// /// But for non-leap years, day 60 is March 1: /// /// ``` /// use jiff::civil::date; /// /// let dt = date(2023, 1, 1).at(23, 59, 59, 999_999_999); /// assert_eq!( /// dt.with().day_of_year(60).build()?, /// date(2023, 3, 1).at(23, 59, 59, 999_999_999), /// ); /// /// # Ok::<(), Box>(()) /// ``` /// /// And using `366` for a non-leap year will result in an error, since /// non-leap years only have 365 days: /// /// ``` /// use jiff::civil::date; /// /// let dt = date(2023, 1, 1).at(0, 0, 0, 0); /// assert!(dt.with().day_of_year(366).build().is_err()); /// // The maximal year is not a leap year, so it returns an error too. /// let dt = date(9999, 1, 1).at(0, 0, 0, 0); /// assert!(dt.with().day_of_year(366).build().is_err()); /// ``` #[inline] pub fn day_of_year(self, day: i16) -> DateTimeWith { DateTimeWith { date_with: self.date_with.day_of_year(day), ..self } } /// Set the day field on a [`DateTime`] via the ordinal number of a day /// within a year, but ignoring leap years. /// /// When used, any settings for month are ignored since the month is /// determined by the day of the year. /// /// The valid values for `day` are `1..=365`. The value `365` always /// corresponds to the last day of the year, even for leap years. It is /// impossible for this routine to return a datetime corresponding to /// February 29. /// /// This overrides any previous day settings. /// /// # Errors /// /// This returns an error when [`DateTimeWith::build`] is called if the /// given day is outside the allowed range of `1..=365`. /// /// # Example /// /// This demonstrates that `60` corresponds to March 1, regardless of /// whether the year is a leap year or not: /// /// ``` /// use jiff::civil::date; /// /// let dt = date(2023, 1, 1).at(23, 59, 59, 999_999_999); /// assert_eq!( /// dt.with().day_of_year_no_leap(60).build()?, /// date(2023, 3, 1).at(23, 59, 59, 999_999_999), /// ); /// /// let dt = date(2024, 1, 1).at(23, 59, 59, 999_999_999); /// assert_eq!( /// dt.with().day_of_year_no_leap(60).build()?, /// date(2024, 3, 1).at(23, 59, 59, 999_999_999), /// ); /// /// # Ok::<(), Box>(()) /// ``` /// /// And using `365` for any year will always yield the last day of the /// year: /// /// ``` /// use jiff::civil::date; /// /// let dt = date(2023, 1, 1).at(23, 59, 59, 999_999_999); /// assert_eq!( /// dt.with().day_of_year_no_leap(365).build()?, /// dt.last_of_year(), /// ); /// /// let dt = date(2024, 1, 1).at(23, 59, 59, 999_999_999); /// assert_eq!( /// dt.with().day_of_year_no_leap(365).build()?, /// dt.last_of_year(), /// ); /// /// let dt = date(9999, 1, 1).at(23, 59, 59, 999_999_999); /// assert_eq!( /// dt.with().day_of_year_no_leap(365).build()?, /// dt.last_of_year(), /// ); /// /// # Ok::<(), Box>(()) /// ``` /// /// A value of `366` is out of bounds, even for leap years: /// /// ``` /// use jiff::civil::date; /// /// let dt = date(2024, 1, 1).at(5, 30, 0, 0); /// assert!(dt.with().day_of_year_no_leap(366).build().is_err()); /// ``` #[inline] pub fn day_of_year_no_leap(self, day: i16) -> DateTimeWith { DateTimeWith { date_with: self.date_with.day_of_year_no_leap(day), ..self } } /// Set the hour field on a [`DateTime`]. /// /// One can access this value via [`DateTime::hour`]. /// /// This overrides any previous hour settings. /// /// # Errors /// /// This returns an error when [`DateTimeWith::build`] is called if the /// given hour is outside the range `0..=23`. /// /// # Example /// /// ``` /// use jiff::civil::time; /// /// let dt1 = time(15, 21, 59, 0).on(2010, 6, 1); /// assert_eq!(dt1.hour(), 15); /// let dt2 = dt1.with().hour(3).build()?; /// assert_eq!(dt2.hour(), 3); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn hour(self, hour: i8) -> DateTimeWith { DateTimeWith { time_with: self.time_with.hour(hour), ..self } } /// Set the minute field on a [`DateTime`]. /// /// One can access this value via [`DateTime::minute`]. /// /// This overrides any previous minute settings. /// /// # Errors /// /// This returns an error when [`DateTimeWith::build`] is called if the /// given minute is outside the range `0..=59`. /// /// # Example /// /// ``` /// use jiff::civil::time; /// /// let dt1 = time(15, 21, 59, 0).on(2010, 6, 1); /// assert_eq!(dt1.minute(), 21); /// let dt2 = dt1.with().minute(3).build()?; /// assert_eq!(dt2.minute(), 3); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn minute(self, minute: i8) -> DateTimeWith { DateTimeWith { time_with: self.time_with.minute(minute), ..self } } /// Set the second field on a [`DateTime`]. /// /// One can access this value via [`DateTime::second`]. /// /// This overrides any previous second settings. /// /// # Errors /// /// This returns an error when [`DateTimeWith::build`] is called if the /// given second is outside the range `0..=59`. /// /// # Example /// /// ``` /// use jiff::civil::time; /// /// let dt1 = time(15, 21, 59, 0).on(2010, 6, 1); /// assert_eq!(dt1.second(), 59); /// let dt2 = dt1.with().second(3).build()?; /// assert_eq!(dt2.second(), 3); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn second(self, second: i8) -> DateTimeWith { DateTimeWith { time_with: self.time_with.second(second), ..self } } /// Set the millisecond field on a [`DateTime`]. /// /// One can access this value via [`DateTime::millisecond`]. /// /// This overrides any previous millisecond settings. /// /// # Errors /// /// This returns an error when [`DateTimeWith::build`] is called if the /// given millisecond is outside the range `0..=999`, or if both this and /// [`DateTimeWith::subsec_nanosecond`] are set. /// /// # Example /// /// This shows the relationship between [`DateTime::millisecond`] and /// [`DateTime::subsec_nanosecond`]: /// /// ``` /// use jiff::civil::time; /// /// let dt1 = time(15, 21, 35, 0).on(2010, 6, 1); /// let dt2 = dt1.with().millisecond(123).build()?; /// assert_eq!(dt2.subsec_nanosecond(), 123_000_000); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn millisecond(self, millisecond: i16) -> DateTimeWith { DateTimeWith { time_with: self.time_with.millisecond(millisecond), ..self } } /// Set the microsecond field on a [`DateTime`]. /// /// One can access this value via [`DateTime::microsecond`]. /// /// This overrides any previous microsecond settings. /// /// # Errors /// /// This returns an error when [`DateTimeWith::build`] is called if the /// given microsecond is outside the range `0..=999`, or if both this and /// [`DateTimeWith::subsec_nanosecond`] are set. /// /// # Example /// /// This shows the relationship between [`DateTime::microsecond`] and /// [`DateTime::subsec_nanosecond`]: /// /// ``` /// use jiff::civil::time; /// /// let dt1 = time(15, 21, 35, 0).on(2010, 6, 1); /// let dt2 = dt1.with().microsecond(123).build()?; /// assert_eq!(dt2.subsec_nanosecond(), 123_000); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn microsecond(self, microsecond: i16) -> DateTimeWith { DateTimeWith { time_with: self.time_with.microsecond(microsecond), ..self } } /// Set the nanosecond field on a [`DateTime`]. /// /// One can access this value via [`DateTime::nanosecond`]. /// /// This overrides any previous nanosecond settings. /// /// # Errors /// /// This returns an error when [`DateTimeWith::build`] is called if the /// given nanosecond is outside the range `0..=999`, or if both this and /// [`DateTimeWith::subsec_nanosecond`] are set. /// /// # Example /// /// This shows the relationship between [`DateTime::nanosecond`] and /// [`DateTime::subsec_nanosecond`]: /// /// ``` /// use jiff::civil::time; /// /// let dt1 = time(15, 21, 35, 0).on(2010, 6, 1); /// let dt2 = dt1.with().nanosecond(123).build()?; /// assert_eq!(dt2.subsec_nanosecond(), 123); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn nanosecond(self, nanosecond: i16) -> DateTimeWith { DateTimeWith { time_with: self.time_with.nanosecond(nanosecond), ..self } } /// Set the subsecond nanosecond field on a [`DateTime`]. /// /// If you want to access this value on `DateTime`, then use /// [`DateTime::subsec_nanosecond`]. /// /// This overrides any previous subsecond nanosecond settings. /// /// # Errors /// /// This returns an error when [`DateTimeWith::build`] is called if the /// given subsecond nanosecond is outside the range `0..=999,999,999`, /// or if both this and one of [`DateTimeWith::millisecond`], /// [`DateTimeWith::microsecond`] or [`DateTimeWith::nanosecond`] are set. /// /// # Example /// /// This shows the relationship between constructing a `DateTime` value /// with subsecond nanoseconds and its individual subsecond fields: /// /// ``` /// use jiff::civil::time; /// /// let dt1 = time(15, 21, 35, 0).on(2010, 6, 1); /// let dt2 = dt1.with().subsec_nanosecond(123_456_789).build()?; /// assert_eq!(dt2.millisecond(), 123); /// assert_eq!(dt2.microsecond(), 456); /// assert_eq!(dt2.nanosecond(), 789); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn subsec_nanosecond(self, subsec_nanosecond: i32) -> DateTimeWith { DateTimeWith { time_with: self.time_with.subsec_nanosecond(subsec_nanosecond), ..self } } } #[cfg(test)] mod tests { use std::io::Cursor; use crate::{ civil::{date, time}, span::span_eq, RoundMode, ToSpan, Unit, }; use super::*; #[test] fn from_temporal_docs() { let dt = DateTime::from_parts( date(1995, 12, 7), time(3, 24, 30, 000_003_500), ); let got = dt.round(Unit::Hour).unwrap(); let expected = DateTime::from_parts(date(1995, 12, 7), time(3, 0, 0, 0)); assert_eq!(got, expected); let got = dt.round((Unit::Minute, 30)).unwrap(); let expected = DateTime::from_parts(date(1995, 12, 7), time(3, 30, 0, 0)); assert_eq!(got, expected); let got = dt .round( DateTimeRound::new() .smallest(Unit::Minute) .increment(30) .mode(RoundMode::Floor), ) .unwrap(); let expected = DateTime::from_parts(date(1995, 12, 7), time(3, 0, 0, 0)); assert_eq!(got, expected); } #[test] fn since() { let later = date(2024, 5, 9).at(2, 0, 0, 0); let earlier = date(2024, 5, 8).at(3, 0, 0, 0); span_eq!(later.since(earlier).unwrap(), 23.hours()); let later = date(2024, 5, 9).at(3, 0, 0, 0); let earlier = date(2024, 5, 8).at(2, 0, 0, 0); span_eq!(later.since(earlier).unwrap(), 1.days().hours(1)); let later = date(2024, 5, 9).at(2, 0, 0, 0); let earlier = date(2024, 5, 10).at(3, 0, 0, 0); span_eq!(later.since(earlier).unwrap(), -1.days().hours(1)); let later = date(2024, 5, 9).at(3, 0, 0, 0); let earlier = date(2024, 5, 10).at(2, 0, 0, 0); span_eq!(later.since(earlier).unwrap(), -23.hours()); } #[test] fn until() { let a = date(9999, 12, 30).at(3, 0, 0, 0); let b = date(9999, 12, 31).at(2, 0, 0, 0); span_eq!(a.until(b).unwrap(), 23.hours()); let a = date(-9999, 1, 2).at(2, 0, 0, 0); let b = date(-9999, 1, 1).at(3, 0, 0, 0); span_eq!(a.until(b).unwrap(), -23.hours()); let a = date(1995, 12, 7).at(3, 24, 30, 3500); let b = date(2019, 1, 31).at(15, 30, 0, 0); span_eq!( a.until(b).unwrap(), 8456.days() .hours(12) .minutes(5) .seconds(29) .milliseconds(999) .microseconds(996) .nanoseconds(500) ); span_eq!( a.until((Unit::Year, b)).unwrap(), 23.years() .months(1) .days(24) .hours(12) .minutes(5) .seconds(29) .milliseconds(999) .microseconds(996) .nanoseconds(500) ); span_eq!( b.until((Unit::Year, a)).unwrap(), -23.years() .months(1) .days(24) .hours(12) .minutes(5) .seconds(29) .milliseconds(999) .microseconds(996) .nanoseconds(500) ); span_eq!( a.until((Unit::Nanosecond, b)).unwrap(), 730641929999996500i64.nanoseconds(), ); let a = date(-9999, 1, 1).at(0, 0, 0, 0); let b = date(9999, 12, 31).at(23, 59, 59, 999_999_999); assert!(a.until((Unit::Nanosecond, b)).is_err()); span_eq!( a.until((Unit::Microsecond, b)).unwrap(), Span::new() .microseconds(631_107_417_600_000_000i64 - 1) .nanoseconds(999), ); } #[test] fn until_month_lengths() { let jan1 = date(2020, 1, 1).at(0, 0, 0, 0); let feb1 = date(2020, 2, 1).at(0, 0, 0, 0); let mar1 = date(2020, 3, 1).at(0, 0, 0, 0); span_eq!(jan1.until(feb1).unwrap(), 31.days()); span_eq!(jan1.until((Unit::Month, feb1)).unwrap(), 1.month()); span_eq!(feb1.until(mar1).unwrap(), 29.days()); span_eq!(feb1.until((Unit::Month, mar1)).unwrap(), 1.month()); span_eq!(jan1.until(mar1).unwrap(), 60.days()); span_eq!(jan1.until((Unit::Month, mar1)).unwrap(), 2.months()); } #[test] fn datetime_size() { #[cfg(debug_assertions)] { assert_eq!(36, core::mem::size_of::()); } #[cfg(not(debug_assertions))] { assert_eq!(12, core::mem::size_of::()); } } /// # `serde` deserializer compatibility test /// /// Serde YAML used to be unable to deserialize `jiff` types, /// as deserializing from bytes is not supported by the deserializer. /// /// - /// - #[test] fn civil_datetime_deserialize_yaml() { let expected = datetime(2024, 10, 31, 16, 33, 53, 123456789); let deserialized: DateTime = serde_yaml::from_str("2024-10-31 16:33:53.123456789").unwrap(); assert_eq!(deserialized, expected); let deserialized: DateTime = serde_yaml::from_slice("2024-10-31 16:33:53.123456789".as_bytes()) .unwrap(); assert_eq!(deserialized, expected); let cursor = Cursor::new(b"2024-10-31 16:33:53.123456789"); let deserialized: DateTime = serde_yaml::from_reader(cursor).unwrap(); assert_eq!(deserialized, expected); } } jiff-0.1.28/src/civil/iso_week_date.rs000064400000000000000000000446141046102023000157100ustar 00000000000000use crate::{ civil::{Date, DateTime, Weekday}, error::{err, Error}, util::{ rangeint::RInto, t::{self, ISOWeek, ISOYear, C}, }, Zoned, }; /// A type representing an [ISO 8601 week date]. /// /// The ISO 8601 week date scheme devises a calendar where days are identified /// by their year, week number and weekday. All years have either precisely /// 52 or 53 weeks. /// /// The first week of an ISO 8601 year corresponds to the week containing the /// first Thursday of the year. For this reason, an ISO 8601 week year can be /// mismatched with the day's corresponding Gregorian year. For example, the /// ISO 8601 week date for `1995-01-01` is `1994-W52-7` (with `7` corresponding /// to Sunday). /// /// ISO 8601 also considers Monday to be the start of the week, and uses /// a 1-based numbering system. That is, Monday corresponds to `1` while /// Sunday corresponds to `7` and is the last day of the week. Weekdays are /// encapsulated by the [`Weekday`] type, which provides routines for easily /// converting between different schemes (such as weeks where Sunday is the /// beginning). /// /// [ISO 8601 week date]: https://en.wikipedia.org/wiki/ISO_week_date /// /// # Use case /// /// Some domains use this method of timekeeping. Otherwise, unless you /// specifically want a week oriented calendar, it's likely that you'll never /// need to care about this type. /// /// # Default value /// /// For convenience, this type implements the `Default` trait. Its default /// value is the first day of the zeroth year. i.e., `0000-W1-1`. /// /// # Example: sample dates /// /// This example shows a couple ISO 8601 week dates and their corresponding /// Gregorian equivalents: /// /// ``` /// use jiff::civil::{ISOWeekDate, Weekday, date}; /// /// let d = date(2019, 12, 30); /// let weekdate = ISOWeekDate::new(2020, 1, Weekday::Monday).unwrap(); /// assert_eq!(d.to_iso_week_date(), weekdate); /// /// let d = date(2024, 3, 9); /// let weekdate = ISOWeekDate::new(2024, 10, Weekday::Saturday).unwrap(); /// assert_eq!(d.to_iso_week_date(), weekdate); /// ``` /// /// # Example: overlapping leap and long years /// /// A "long" ISO 8601 week year is a year with 53 weeks. That is, it is a year /// that includes a leap week. This example shows all years in the 20th /// century that are both Gregorian leap years and long years. /// /// ``` /// use jiff::civil::date; /// /// let mut overlapping = vec![]; /// for year in 1900..=1999 { /// let date = date(year, 1, 1); /// if date.in_leap_year() && date.to_iso_week_date().in_long_year() { /// overlapping.push(year); /// } /// } /// assert_eq!(overlapping, vec![ /// 1904, 1908, 1920, 1932, 1936, 1948, 1960, 1964, 1976, 1988, 1992, /// ]); /// ``` #[derive(Clone, Copy, Hash)] pub struct ISOWeekDate { year: ISOYear, week: ISOWeek, weekday: Weekday, } impl ISOWeekDate { /// The maximum representable ISO week date. /// /// The maximum corresponds to the ISO week date of the maximum [`Date`] /// value. That is, `-9999-01-01`. pub const MIN: ISOWeekDate = ISOWeekDate { year: ISOYear::new_unchecked(-9999), week: ISOWeek::new_unchecked(1), weekday: Weekday::Monday, }; /// The minimum representable ISO week date. /// /// The minimum corresponds to the ISO week date of the minimum [`Date`] /// value. That is, `9999-12-31`. pub const MAX: ISOWeekDate = ISOWeekDate { year: ISOYear::new_unchecked(9999), week: ISOWeek::new_unchecked(52), weekday: Weekday::Friday, }; /// The first day of the zeroth year. /// /// This is guaranteed to be equivalent to `ISOWeekDate::default()`. Note /// that this is not equivalent to `Date::default()`. /// /// # Example /// /// ``` /// use jiff::civil::{ISOWeekDate, date}; /// /// assert_eq!(ISOWeekDate::ZERO, ISOWeekDate::default()); /// // The first day of the 0th year in the ISO week calendar is actually /// // the third day of the 0th year in the proleptic Gregorian calendar! /// assert_eq!(ISOWeekDate::default().date(), date(0, 1, 3)); /// ``` pub const ZERO: ISOWeekDate = ISOWeekDate { year: ISOYear::new_unchecked(0), week: ISOWeek::new_unchecked(1), weekday: Weekday::Monday, }; /// Create a new ISO week date from it constituent parts. /// /// If the given values are out of range (based on what is representable /// as a [`Date`]), then this returns an error. This will also return an /// error if a leap week is given (week number `53`) for a year that does /// not contain a leap week. /// /// # Example /// /// This example shows some the boundary conditions involving minimum /// and maximum dates: /// /// ``` /// use jiff::civil::{ISOWeekDate, Weekday, date}; /// /// // The year 1949 does not contain a leap week. /// assert!(ISOWeekDate::new(1949, 53, Weekday::Monday).is_err()); /// /// // Examples of dates at or exceeding the maximum. /// let max = ISOWeekDate::new(9999, 52, Weekday::Friday).unwrap(); /// assert_eq!(max, ISOWeekDate::MAX); /// assert_eq!(max.date(), date(9999, 12, 31)); /// assert!(ISOWeekDate::new(9999, 52, Weekday::Saturday).is_err()); /// assert!(ISOWeekDate::new(9999, 53, Weekday::Monday).is_err()); /// /// // Examples of dates at or exceeding the minimum. /// let min = ISOWeekDate::new(-9999, 1, Weekday::Monday).unwrap(); /// assert_eq!(min, ISOWeekDate::MIN); /// assert_eq!(min.date(), date(-9999, 1, 1)); /// assert!(ISOWeekDate::new(-10000, 52, Weekday::Sunday).is_err()); /// ``` #[inline] pub fn new( year: i16, week: i8, weekday: Weekday, ) -> Result { let year = ISOYear::try_new("year", year)?; let week = ISOWeek::try_new("week", week)?; ISOWeekDate::new_ranged(year, week, weekday) } /// Converts a Gregorian date to an ISO week date. /// /// The minimum and maximum allowed values of an ISO week date are /// set based on the minimum and maximum values of a `Date`. Therefore, /// converting to and from `Date` values is non-lossy and infallible. /// /// This routine is equivalent to [`Date::to_iso_week_date`]. This /// routine is also available via a `From` trait implementation for /// `ISOWeekDate`. /// /// # Example /// /// ``` /// use jiff::civil::{ISOWeekDate, Weekday, date}; /// /// let weekdate = ISOWeekDate::from_date(date(1948, 2, 10)); /// assert_eq!( /// weekdate, /// ISOWeekDate::new(1948, 7, Weekday::Tuesday).unwrap(), /// ); /// ``` #[inline] pub fn from_date(date: Date) -> ISOWeekDate { date.iso_week_date() } // N.B. I tried defining a `ISOWeekDate::constant` for defining ISO week // dates as constants, but it was too annoying to do. We could do it if // there was a compelling reason for it though. /// Returns the year component of this ISO 8601 week date. /// /// The value returned is guaranteed to be in the range `-9999..=9999`. /// /// # Example /// /// ``` /// use jiff::civil::date; /// /// let weekdate = date(2019, 12, 30).to_iso_week_date(); /// assert_eq!(weekdate.year(), 2020); /// ``` #[inline] pub fn year(self) -> i16 { self.year_ranged().get() } /// Returns the week component of this ISO 8601 week date. /// /// The value returned is guaranteed to be in the range `1..=53`. A /// value of `53` can only occur for "long" years. That is, years /// with a leap week. This occurs precisely in cases for which /// [`ISOWeekDate::in_long_year`] returns `true`. /// /// # Example /// /// ``` /// use jiff::civil::date; /// /// let weekdate = date(2019, 12, 30).to_iso_week_date(); /// assert_eq!(weekdate.year(), 2020); /// assert_eq!(weekdate.week(), 1); /// /// let weekdate = date(1948, 12, 31).to_iso_week_date(); /// assert_eq!(weekdate.year(), 1948); /// assert_eq!(weekdate.week(), 53); /// ``` #[inline] pub fn week(self) -> i8 { self.week_ranged().get() } /// Returns the day component of this ISO 8601 week date. /// /// One can use methods on `Weekday` such as /// [`Weekday::to_monday_one_offset`] /// and /// [`Weekday::to_sunday_zero_offset`] /// to convert the weekday to a number. /// /// # Example /// /// ``` /// use jiff::civil::{date, Weekday}; /// /// let weekdate = date(1948, 12, 31).to_iso_week_date(); /// assert_eq!(weekdate.year(), 1948); /// assert_eq!(weekdate.week(), 53); /// assert_eq!(weekdate.weekday(), Weekday::Friday); /// assert_eq!(weekdate.weekday().to_monday_zero_offset(), 4); /// assert_eq!(weekdate.weekday().to_monday_one_offset(), 5); /// assert_eq!(weekdate.weekday().to_sunday_zero_offset(), 5); /// assert_eq!(weekdate.weekday().to_sunday_one_offset(), 6); /// ``` #[inline] pub fn weekday(self) -> Weekday { self.weekday } /// Returns true if and only if the year of this week date is a "long" /// year. /// /// A long year is one that contains precisely 53 weeks. All other years /// contain precisely 52 weeks. /// /// # Example /// /// ``` /// use jiff::civil::{ISOWeekDate, Weekday}; /// /// let weekdate = ISOWeekDate::new(1948, 7, Weekday::Monday).unwrap(); /// assert!(weekdate.in_long_year()); /// let weekdate = ISOWeekDate::new(1949, 7, Weekday::Monday).unwrap(); /// assert!(!weekdate.in_long_year()); /// ``` #[inline] pub fn in_long_year(self) -> bool { is_long_year(self.year_ranged()) } /// Converts this ISO week date to a Gregorian [`Date`]. /// /// The minimum and maximum allowed values of an ISO week date are /// set based on the minimum and maximum values of a `Date`. Therefore, /// converting to and from `Date` values is non-lossy and infallible. /// /// This routine is equivalent to [`Date::from_iso_week_date`]. /// /// # Example /// /// ``` /// use jiff::civil::{ISOWeekDate, Weekday, date}; /// /// let weekdate = ISOWeekDate::new(1948, 7, Weekday::Tuesday).unwrap(); /// assert_eq!(weekdate.date(), date(1948, 2, 10)); /// ``` #[inline] pub fn date(self) -> Date { Date::from_iso_week_date(self) } } /// Deprecated APIs. impl ISOWeekDate { /// A deprecated equivalent to [`ISOWeekDate::date`]. /// /// This method will be removed in `jiff 0.2`. This was done to make naming /// more consistent throughout the crate. #[deprecated(since = "0.1.26", note = "use ISOWeekDate::date instead")] #[inline] pub fn to_date(self) -> Date { Date::from_iso_week_date(self) } } impl ISOWeekDate { /// Creates a new ISO week date from ranged values. /// /// While the ranged values given eliminate some error cases, not all /// combinations of year/week/weekday values are valid ISO week dates /// supported by this crate. For example, a week of `53` for short years, /// or more niche, a week date that would be bigger than what is supported /// by our `Date` type. #[inline] pub(crate) fn new_ranged( year: impl RInto, week: impl RInto, weekday: Weekday, ) -> Result { let year = year.rinto(); let week = week.rinto(); // All combinations of years, weeks and weekdays allowed by our // range types are valid ISO week dates with one exception: a week // number of 53 is only valid for "long" years. Or years with an ISO // leap week. It turns out this only happens when the last day of the // year is a Thursday. // // Note that if the ranges in this crate are changed, this could be // a little trickier if the range of ISOYear is different from Year. debug_assert_eq!(t::Year::MIN, ISOYear::MIN); debug_assert_eq!(t::Year::MAX, ISOYear::MAX); if week == 53 && !is_long_year(year) { return Err(err!( "ISO week number `{week}` is invalid for year `{year}`" )); } // And also, the maximum Date constrains what we can utter with // ISOWeekDate so that we can preserve infallible conversions between // them. So since 9999-12-31 maps to 9999 W52 Friday, it follows that // Saturday and Sunday are not allowed. So reject them. // // We don't need to worry about the minimum because the minimum date // (-9999-01-01) corresponds also to the minimum possible combination // of an ISO week date's fields: -9999 W01 Monday. Nice. if year == ISOYear::MAX_SELF && week == 52 && weekday.to_monday_zero_offset() > Weekday::Friday.to_monday_zero_offset() { return Err(Error::range( "weekday", weekday.to_monday_zero_offset(), Weekday::Monday.to_monday_one_offset(), Weekday::Friday.to_monday_one_offset(), )); } Ok(ISOWeekDate { year, week, weekday }) } /// Like `ISOWeekDate::new_ranged`, but constrains out-of-bounds values /// to their closest valid equivalent. /// /// For example, given 9999 W52 Saturday, this will return 9999 W52 Friday. #[cfg(test)] #[inline] pub(crate) fn new_ranged_constrain( year: impl RInto, week: impl RInto, mut weekday: Weekday, ) -> ISOWeekDate { let year = year.rinto(); let mut week = week.rinto(); debug_assert_eq!(t::Year::MIN, ISOYear::MIN); debug_assert_eq!(t::Year::MAX, ISOYear::MAX); if week == 53 && !is_long_year(year) { week = ISOWeek::new(52).unwrap(); } if year == ISOYear::MAX_SELF && week == 52 && weekday.to_monday_zero_offset() > Weekday::Friday.to_monday_zero_offset() { weekday = Weekday::Friday; } ISOWeekDate { year, week, weekday } } #[inline] pub(crate) fn year_ranged(self) -> ISOYear { self.year } #[inline] pub(crate) fn week_ranged(self) -> ISOWeek { self.week } } impl Default for ISOWeekDate { fn default() -> ISOWeekDate { ISOWeekDate::ZERO } } impl core::fmt::Debug for ISOWeekDate { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { f.debug_struct("ISOWeekDate") .field("year", &self.year_ranged().debug()) .field("week", &self.week_ranged().debug()) .field("weekday", &self.weekday) .finish() } } impl Eq for ISOWeekDate {} impl PartialEq for ISOWeekDate { #[inline] fn eq(&self, other: &ISOWeekDate) -> bool { // We roll our own so that we can call 'get' on our ranged integers // in order to provoke panics for bugs in dealing with boundary // conditions. self.weekday == other.weekday && self.week.get() == other.week.get() && self.year.get() == other.year.get() } } impl Ord for ISOWeekDate { #[inline] fn cmp(&self, other: &ISOWeekDate) -> core::cmp::Ordering { (self.year.get(), self.week.get(), self.weekday.to_monday_one_offset()) .cmp(&( other.year.get(), other.week.get(), other.weekday.to_monday_one_offset(), )) } } impl PartialOrd for ISOWeekDate { #[inline] fn partial_cmp(&self, other: &ISOWeekDate) -> Option { Some(self.cmp(other)) } } impl From for ISOWeekDate { #[inline] fn from(date: Date) -> ISOWeekDate { ISOWeekDate::from_date(date) } } impl From for ISOWeekDate { #[inline] fn from(dt: DateTime) -> ISOWeekDate { ISOWeekDate::from(dt.date()) } } impl From for ISOWeekDate { #[inline] fn from(zdt: Zoned) -> ISOWeekDate { ISOWeekDate::from(zdt.date()) } } impl<'a> From<&'a Zoned> for ISOWeekDate { #[inline] fn from(zdt: &'a Zoned) -> ISOWeekDate { ISOWeekDate::from(zdt.date()) } } #[cfg(test)] impl quickcheck::Arbitrary for ISOWeekDate { fn arbitrary(g: &mut quickcheck::Gen) -> ISOWeekDate { let year = ISOYear::arbitrary(g); let week = ISOWeek::arbitrary(g); let weekday = Weekday::arbitrary(g); ISOWeekDate::new_ranged_constrain(year, week, weekday) } fn shrink(&self) -> alloc::boxed::Box> { alloc::boxed::Box::new( (self.year_ranged(), self.week_ranged(), self.weekday()) .shrink() .map(|(year, week, weekday)| { ISOWeekDate::new_ranged_constrain(year, week, weekday) }), ) } } /// Returns true if the given ISO year is a "long" year or not. /// /// A "long" year is a year with 53 weeks. Otherwise, it's a "short" year /// with 52 weeks. fn is_long_year(year: ISOYear) -> bool { // Inspired by: https://en.wikipedia.org/wiki/ISO_week_date#Weeks_per_year let last = Date::new_ranged(year, C(12), C(31)) .expect("last day of year is always valid"); let weekday = last.weekday(); weekday == Weekday::Thursday || (last.in_leap_year() && weekday == Weekday::Friday) } #[cfg(test)] mod tests { use super::*; quickcheck::quickcheck! { fn prop_all_long_years_have_53rd_week(year: ISOYear) -> bool { !is_long_year(year) || ISOWeekDate::new(year.get(), 53, Weekday::Sunday).is_ok() } fn prop_prev_day_is_less(wd: ISOWeekDate) -> quickcheck::TestResult { use crate::ToSpan; if wd == ISOWeekDate::MIN { return quickcheck::TestResult::discard(); } let prev_date = wd.date().checked_add(-1.days()).unwrap(); quickcheck::TestResult::from_bool(prev_date.iso_week_date() < wd) } fn prop_next_day_is_greater(wd: ISOWeekDate) -> quickcheck::TestResult { use crate::ToSpan; if wd == ISOWeekDate::MAX { return quickcheck::TestResult::discard(); } let next_date = wd.date().checked_add(1.days()).unwrap(); quickcheck::TestResult::from_bool(wd < next_date.iso_week_date()) } } } jiff-0.1.28/src/civil/mod.rs000064400000000000000000000230341046102023000136560ustar 00000000000000/*! Facilities for dealing with inexact dates and times. # Overview The essential types in this module are: * [`Date`] is a specific day in the Gregorian calendar. * [`Time`] is a specific wall clock time. * [`DateTime`] is a combination of a day and a time. Moreover, the [`date`](date()) and [`time`](time()) free functions can be used to conveniently create values of any of three types above: ``` use jiff::civil::{date, time}; assert_eq!(date(2024, 7, 31).to_string(), "2024-07-31"); assert_eq!(time(15, 20, 0, 123).to_string(), "15:20:00.000000123"); assert_eq!( date(2024, 7, 31).at(15, 20, 0, 123).to_string(), "2024-07-31T15:20:00.000000123", ); assert_eq!( time(15, 20, 0, 123).on(2024, 7, 31).to_string(), "2024-07-31T15:20:00.000000123", ); ``` # What is "civil" time? A civil datetime is a calendar date and a clock time. It also goes by the names "naive," "local" or "plain." The most important thing to understand about civil time is that it does not correspond to a precise instant in time. This is in contrast to types like [`Timestamp`](crate::Timestamp) and [`Zoned`](crate::Zoned), which _do_ correspond to a precise instant in time (to nanosecond precision). Because a civil datetime _never_ has a time zone associated with it, and because some time zones have transitions that skip or repeat clock times, it follows that not all civil datetimes precisely map to a single instant in time. For example, `2024-03-10 02:30` never existed on a clock in `America/New_York` because the 2 o'clock hour was skipped when the clocks were "moved forward" for daylight saving time. Conversely, `2024-11-03 01:30` occurred twice in `America/New_York` because the 1 o'clock hour was repeated when clocks were "moved backward" for daylight saving time. (When time is skipped, it's called a "gap." When time is repeated, it's called a "fold.") In contrast, an instant in time (that is, `Timestamp` or `Zoned`) can _always_ be converted to a civil datetime. And, when a civil datetime is combined with its time zone identifier _and_ its offset, the resulting machine readable string is unambiguous 100% of the time: ``` use jiff::{civil::date, tz::TimeZone}; let tz = TimeZone::get("America/New_York")?; let dt = date(2024, 11, 3).at(1, 30, 0, 0); // It's ambiguous, so asking for an unambiguous instant presents an error! assert!(tz.to_ambiguous_zoned(dt).unambiguous().is_err()); // Gives you the earlier time in a fold, i.e., before DST ends: assert_eq!( tz.to_ambiguous_zoned(dt).earlier()?.to_string(), "2024-11-03T01:30:00-04:00[America/New_York]", ); // Gives you the later time in a fold, i.e., after DST ends. // Notice the offset change from the previous example! assert_eq!( tz.to_ambiguous_zoned(dt).later()?.to_string(), "2024-11-03T01:30:00-05:00[America/New_York]", ); // "Just give me something reasonable" assert_eq!( tz.to_ambiguous_zoned(dt).compatible()?.to_string(), "2024-11-03T01:30:00-04:00[America/New_York]", ); # Ok::<(), Box>(()) ``` # When should I use civil time? Here is a likely non-exhaustive list of reasons why you might want to use civil time: * When you want or need to deal with calendar and clock units as an intermediate step before and/or after associating it with a time zone. For example, perhaps you need to parse strings like `2000-01-01T00:00:00` from a CSV file that have no time zone or offset information, but the time zone is implied through some out-of-band mechanism. * When time zone is actually irrelevant. For example, a fitness tracking app that reminds you to work-out at 6am local time, regardless of which time zone you're in. * When you need to perform arithmetic that deliberately ignores daylight saving time. * When interacting with legacy systems or systems that specifically do not support time zones. */ pub use self::{ date::{Date, DateArithmetic, DateDifference, DateSeries, DateWith}, datetime::{ DateTime, DateTimeArithmetic, DateTimeDifference, DateTimeRound, DateTimeSeries, DateTimeWith, }, iso_week_date::ISOWeekDate, time::{ Time, TimeArithmetic, TimeDifference, TimeRound, TimeSeries, TimeWith, }, weekday::{Weekday, WeekdaysForward, WeekdaysReverse}, }; mod date; mod datetime; mod iso_week_date; mod time; mod weekday; /// The era corresponding to a particular year. /// /// The BCE era corresponds to years less than or equal to `0`, while the CE /// era corresponds to years greater than `0`. /// /// In particular, this crate allows years to be negative and also to be `0`, /// which is contrary to the common practice of excluding the year `0` when /// writing dates for the Gregorian calendar. Moreover, common practice eschews /// negative years in favor of labeling a year with an era notation. That is, /// the year `1 BCE` is year `0` in this crate. The year `2 BCE` is the year /// `-1` in this crate. /// /// To get the year in its era format, use [`Date::era_year`]. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum Era { /// The "before common era" era. /// /// This corresponds to all years less than or equal to `0`. /// /// This is precisely equivalent to the "BC" or "before Christ" era. BCE, /// The "common era" era. /// /// This corresponds to all years greater than `0`. /// /// This is precisely equivalent to the "AD" or "anno Domini" or "in the /// year of the Lord" era. CE, } /// Creates a new `DateTime` value in a `const` context. /// /// This is a convenience free function for [`DateTime::constant`]. It is /// intended to provide a terse syntax for constructing `DateTime` values from /// parameters that are known to be valid. /// /// # Panics /// /// This routine panics when [`DateTime::new`] would return an error. That /// is, when the given components do not correspond to a valid datetime. /// Namely, all of the following must be true: /// /// * The year must be in the range `-9999..=9999`. /// * The month must be in the range `1..=12`. /// * The day must be at least `1` and must be at most the number of days /// in the corresponding month. So for example, `2024-02-29` is valid but /// `2023-02-29` is not. /// * `0 <= hour <= 23` /// * `0 <= minute <= 59` /// * `0 <= second <= 59` /// * `0 <= subsec_nanosecond <= 999,999,999` /// /// Similarly, when used in a const context, invalid parameters will prevent /// your Rust program from compiling. /// /// # Example /// /// ``` /// use jiff::civil::DateTime; /// /// let d = DateTime::constant(2024, 2, 29, 21, 30, 5, 123_456_789); /// assert_eq!(d.date().year(), 2024); /// assert_eq!(d.date().month(), 2); /// assert_eq!(d.date().day(), 29); /// assert_eq!(d.time().hour(), 21); /// assert_eq!(d.time().minute(), 30); /// assert_eq!(d.time().second(), 5); /// assert_eq!(d.time().millisecond(), 123); /// assert_eq!(d.time().microsecond(), 456); /// assert_eq!(d.time().nanosecond(), 789); /// ``` #[inline] pub const fn datetime( year: i16, month: i8, day: i8, hour: i8, minute: i8, second: i8, subsec_nanosecond: i32, ) -> DateTime { DateTime::constant( year, month, day, hour, minute, second, subsec_nanosecond, ) } /// Creates a new `Date` value in a `const` context. /// /// This is a convenience free function for [`Date::constant`]. It is intended /// to provide a terse syntax for constructing `Date` values from parameters /// that are known to be valid. /// /// # Panics /// /// This routine panics when [`Date::new`] would return an error. That is, /// when the given year-month-day does not correspond to a valid date. /// Namely, all of the following must be true: /// /// * The year must be in the range `-9999..=9999`. /// * The month must be in the range `1..=12`. /// * The day must be at least `1` and must be at most the number of days /// in the corresponding month. So for example, `2024-02-29` is valid but /// `2023-02-29` is not. /// /// Similarly, when used in a const context, invalid parameters will prevent /// your Rust program from compiling. /// /// # Example /// /// ``` /// use jiff::civil::date; /// /// let d = date(2024, 2, 29); /// assert_eq!(d.year(), 2024); /// assert_eq!(d.month(), 2); /// assert_eq!(d.day(), 29); /// ``` #[inline] pub const fn date(year: i16, month: i8, day: i8) -> Date { Date::constant(year, month, day) } /// Creates a new `Time` value in a `const` context. /// /// This is a convenience free function for [`Time::constant`]. It is intended /// to provide a terse syntax for constructing `Time` values from parameters /// that are known to be valid. /// /// # Panics /// /// This panics if the given values do not correspond to a valid `Time`. /// All of the following conditions must be true: /// /// * `0 <= hour <= 23` /// * `0 <= minute <= 59` /// * `0 <= second <= 59` /// * `0 <= subsec_nanosecond <= 999,999,999` /// /// Similarly, when used in a const context, invalid parameters will /// prevent your Rust program from compiling. /// /// # Example /// /// This shows an example of a valid time in a `const` context: /// /// ``` /// use jiff::civil::Time; /// /// const BEDTIME: Time = Time::constant(21, 30, 5, 123_456_789); /// assert_eq!(BEDTIME.hour(), 21); /// assert_eq!(BEDTIME.minute(), 30); /// assert_eq!(BEDTIME.second(), 5); /// assert_eq!(BEDTIME.millisecond(), 123); /// assert_eq!(BEDTIME.microsecond(), 456); /// assert_eq!(BEDTIME.nanosecond(), 789); /// assert_eq!(BEDTIME.subsec_nanosecond(), 123_456_789); /// ``` #[inline] pub const fn time( hour: i8, minute: i8, second: i8, subsec_nanosecond: i32, ) -> Time { Time::constant(hour, minute, second, subsec_nanosecond) } jiff-0.1.28/src/civil/time.rs000064400000000000000000003333701046102023000140440ustar 00000000000000use core::time::Duration as UnsignedDuration; use crate::{ civil::{Date, DateTime}, duration::{Duration, SDuration}, error::{err, Error, ErrorContext}, fmt::{ self, temporal::{self, DEFAULT_DATETIME_PARSER}, }, util::{ rangeint::{RFrom, RInto, TryRFrom}, round::increment, t::{ self, CivilDayNanosecond, Hour, Microsecond, Millisecond, Minute, Nanosecond, Second, SubsecNanosecond, C, }, }, RoundMode, SignedDuration, Span, SpanRound, Unit, Zoned, }; /// A representation of civil "wall clock" time. /// /// Conceptually, a `Time` value corresponds to the typical hours and minutes /// that you might see on a clock. This type also contains the second and /// fractional subsecond (to nanosecond precision) associated with a time. /// /// # Civil time /// /// A `Time` value behaves as if it corresponds precisely to a single /// nanosecond within a day, where all days have `86,400` seconds. That is, /// any given `Time` value corresponds to a nanosecond in the inclusive range /// `[0, 86399999999999]`, where `0` corresponds to `00:00:00.000000000` /// ([`Time::MIN`]) and `86399999999999` corresponds to `23:59:59.999999999` /// ([`Time::MAX`]). Moreover, in civil time, all hours have the same number of /// minutes, all minutes have the same number of seconds and all seconds have /// the same number of nanoseconds. /// /// # Parsing and printing /// /// The `Time` type provides convenient trait implementations of /// [`std::str::FromStr`] and [`std::fmt::Display`]: /// /// ``` /// use jiff::civil::Time; /// /// let t: Time = "15:22:45".parse()?; /// assert_eq!(t.to_string(), "15:22:45"); /// /// # Ok::<(), Box>(()) /// ``` /// /// A civil `Time` can also be parsed from something that _contains_ a /// time, but with perhaps other data (such as an offset or time zone): /// /// ``` /// use jiff::civil::Time; /// /// let t: Time = "2024-06-19T15:22:45-04[America/New_York]".parse()?; /// assert_eq!(t.to_string(), "15:22:45"); /// /// # Ok::<(), Box>(()) /// ``` /// /// For more information on the specific format supported, see the /// [`fmt::temporal`](crate::fmt::temporal) module documentation. /// /// # Default value /// /// For convenience, this type implements the `Default` trait. Its default /// value is midnight. i.e., `00:00:00.000000000`. /// /// # Leap seconds /// /// Jiff does not support leap seconds. Jiff behaves as if they don't exist. /// The only exception is that if one parses a time with a second component /// of `60`, then it is automatically constrained to `59`: /// /// ``` /// use jiff::civil::{Time, time}; /// /// let t: Time = "23:59:60".parse()?; /// assert_eq!(t, time(23, 59, 59, 0)); /// /// # Ok::<(), Box>(()) /// ``` /// /// # Comparisons /// /// The `Time` type provides both `Eq` and `Ord` trait implementations to /// facilitate easy comparisons. When a time `t1` occurs before a time `t2`, /// then `t1 < t2`. For example: /// /// ``` /// use jiff::civil::time; /// /// let t1 = time(7, 30, 1, 0); /// let t2 = time(8, 10, 0, 0); /// assert!(t1 < t2); /// ``` /// /// As mentioned above, `Time` values are not associated with timezones, and /// thus transitions such as DST are not taken into account when comparing /// `Time` values. /// /// # Arithmetic /// /// This type provides routines for adding and subtracting spans of time, as /// well as computing the span of time between two `Time` values. /// /// For adding or subtracting spans of time, one can use any of the following /// routines: /// /// * [`Time::wrapping_add`] or [`Time::wrapping_sub`] for wrapping arithmetic. /// * [`Time::checked_add`] or [`Time::checked_sub`] for checked arithmetic. /// * [`Time::saturating_add`] or [`Time::saturating_sub`] for saturating /// arithmetic. /// /// Additionally, wrapping arithmetic is available via the `Add` and `Sub` /// trait implementations: /// /// ``` /// use jiff::{civil::time, ToSpan}; /// /// let t = time(20, 10, 1, 0); /// let span = 1.hours().minutes(49).seconds(59); /// assert_eq!(t + span, time(22, 0, 0, 0)); /// /// // Overflow will result in wrap-around unless using checked /// // arithmetic explicitly. /// let t = time(23, 59, 59, 999_999_999); /// assert_eq!(time(0, 0, 0, 0), t + 1.nanoseconds()); /// ``` /// /// Wrapping arithmetic is used by default because it corresponds to how clocks /// showing the time of day behave in practice. /// /// One can compute the span of time between two times using either /// [`Time::until`] or [`Time::since`]. It's also possible to subtract two /// `Time` values directly via a `Sub` trait implementation: /// /// ``` /// use jiff::{civil::time, ToSpan}; /// /// let time1 = time(22, 0, 0, 0); /// let time2 = time(20, 10, 1, 0); /// assert_eq!( /// time1 - time2, /// 1.hours().minutes(49).seconds(59).fieldwise(), /// ); /// ``` /// /// The `until` and `since` APIs are polymorphic and allow re-balancing and /// rounding the span returned. For example, the default largest unit is hours /// (as exemplified above), but we can ask for smaller units: /// /// ``` /// use jiff::{civil::time, ToSpan, Unit}; /// /// let time1 = time(23, 30, 0, 0); /// let time2 = time(7, 0, 0, 0); /// assert_eq!( /// time1.since((Unit::Minute, time2))?, /// 990.minutes().fieldwise(), /// ); /// /// # Ok::<(), Box>(()) /// ``` /// /// Or even round the span returned: /// /// ``` /// use jiff::{civil::{TimeDifference, time}, RoundMode, ToSpan, Unit}; /// /// let time1 = time(23, 30, 0, 0); /// let time2 = time(23, 35, 59, 0); /// assert_eq!( /// time1.until( /// TimeDifference::new(time2).smallest(Unit::Minute), /// )?, /// 5.minutes().fieldwise(), /// ); /// // `TimeDifference` uses truncation as a rounding mode by default, /// // but you can set the rounding mode to break ties away from zero: /// assert_eq!( /// time1.until( /// TimeDifference::new(time2) /// .smallest(Unit::Minute) /// .mode(RoundMode::HalfExpand), /// )?, /// // Rounds up to 6 minutes. /// 6.minutes().fieldwise(), /// ); /// /// # Ok::<(), Box>(()) /// ``` /// /// # Rounding /// /// A `Time` can be rounded based on a [`TimeRound`] configuration of smallest /// units, rounding increment and rounding mode. Here's an example showing how /// to round to the nearest third hour: /// /// ``` /// use jiff::{civil::{TimeRound, time}, Unit}; /// /// let t = time(16, 27, 29, 999_999_999); /// assert_eq!( /// t.round(TimeRound::new().smallest(Unit::Hour).increment(3))?, /// time(15, 0, 0, 0), /// ); /// // Or alternatively, make use of the `From<(Unit, i64)> for TimeRound` /// // trait implementation: /// assert_eq!(t.round((Unit::Hour, 3))?, time(15, 0, 0, 0)); /// /// # Ok::<(), Box>(()) /// ``` /// /// See [`Time::round`] for more details. #[derive(Clone, Copy, Eq, Hash, PartialEq, PartialOrd, Ord)] pub struct Time { hour: Hour, minute: Minute, second: Second, subsec_nanosecond: SubsecNanosecond, } impl Time { /// The minimum representable time value. /// /// This corresponds to `00:00:00.000000000`. pub const MIN: Time = Time::midnight(); /// The maximum representable time value. /// /// This corresponds to `23:59:59.999999999`. pub const MAX: Time = Time::constant(23, 59, 59, 999_999_999); /// Creates a new `Time` value from its component hour, minute, second and /// fractional subsecond (up to nanosecond precision) values. /// /// To set the component values of a time after creating it, use /// [`TimeWith`] via [`Time::with`] to build a new [`Time`] from the fields /// of an existing time. /// /// # Errors /// /// This returns an error unless *all* of the following conditions are /// true: /// /// * `0 <= hour <= 23` /// * `0 <= minute <= 59` /// * `0 <= second <= 59` /// * `0 <= subsec_nanosecond <= 999,999,999` /// /// # Example /// /// This shows an example of a valid time: /// /// ``` /// use jiff::civil::Time; /// /// let t = Time::new(21, 30, 5, 123_456_789).unwrap(); /// assert_eq!(t.hour(), 21); /// assert_eq!(t.minute(), 30); /// assert_eq!(t.second(), 5); /// assert_eq!(t.millisecond(), 123); /// assert_eq!(t.microsecond(), 456); /// assert_eq!(t.nanosecond(), 789); /// ``` /// /// This shows an example of an invalid time: /// /// ``` /// use jiff::civil::Time; /// /// assert!(Time::new(21, 30, 60, 0).is_err()); /// ``` #[inline] pub fn new( hour: i8, minute: i8, second: i8, subsec_nanosecond: i32, ) -> Result { let hour = Hour::try_new("hour", hour)?; let minute = Minute::try_new("minute", minute)?; let second = Second::try_new("second", second)?; let subsec_nanosecond = SubsecNanosecond::try_new("subsec_nanosecond", subsec_nanosecond)?; Ok(Time::new_ranged(hour, minute, second, subsec_nanosecond)) } /// Creates a new `Time` value in a `const` context. /// /// # Panics /// /// This panics if the given values do not correspond to a valid `Time`. /// All of the following conditions must be true: /// /// * `0 <= hour <= 23` /// * `0 <= minute <= 59` /// * `0 <= second <= 59` /// * `0 <= subsec_nanosecond <= 999,999,999` /// /// Similarly, when used in a const context, invalid parameters will /// prevent your Rust program from compiling. /// /// # Example /// /// This shows an example of a valid time in a `const` context: /// /// ``` /// use jiff::civil::Time; /// /// const BEDTIME: Time = Time::constant(21, 30, 5, 123_456_789); /// assert_eq!(BEDTIME.hour(), 21); /// assert_eq!(BEDTIME.minute(), 30); /// assert_eq!(BEDTIME.second(), 5); /// assert_eq!(BEDTIME.millisecond(), 123); /// assert_eq!(BEDTIME.microsecond(), 456); /// assert_eq!(BEDTIME.nanosecond(), 789); /// assert_eq!(BEDTIME.subsec_nanosecond(), 123_456_789); /// ``` #[inline] pub const fn constant( hour: i8, minute: i8, second: i8, subsec_nanosecond: i32, ) -> Time { if !Hour::contains(hour) { panic!("invalid hour"); } if !Minute::contains(minute) { panic!("invalid minute"); } if !Second::contains(second) { panic!("invalid second"); } if !SubsecNanosecond::contains(subsec_nanosecond) { panic!("invalid nanosecond"); } let hour = Hour::new_unchecked(hour); let minute = Minute::new_unchecked(minute); let second = Second::new_unchecked(second); let subsec_nanosecond = SubsecNanosecond::new_unchecked(subsec_nanosecond); Time { hour, minute, second, subsec_nanosecond } } /// Returns the first moment of time in a day. /// /// Specifically, this has the `hour`, `minute`, `second`, `millisecond`, /// `microsecond` and `nanosecond` fields all set to `0`. /// /// # Example /// /// ``` /// use jiff::civil::Time; /// /// let t = Time::midnight(); /// assert_eq!(t.hour(), 0); /// assert_eq!(t.minute(), 0); /// assert_eq!(t.second(), 0); /// assert_eq!(t.millisecond(), 0); /// assert_eq!(t.microsecond(), 0); /// assert_eq!(t.nanosecond(), 0); /// ``` #[inline] pub const fn midnight() -> Time { Time::constant(0, 0, 0, 0) } /// Create a builder for constructing a `Time` from the fields of this /// time. /// /// See the methods on [`TimeWith`] for the different ways one can set the /// fields of a new `Time`. /// /// # Example /// /// Unlike [`Date`], a [`Time`] is valid for all possible valid values /// of its fields. That is, there is no way for two valid field values /// to combine into an invalid `Time`. So, for `Time`, this builder does /// have as much of a benefit versus an API design with methods like /// `Time::with_hour` and `Time::with_minute`. Nevertheless, this builder /// permits settings multiple fields at the same time and performing only /// one validity check. Moreover, this provides a consistent API with other /// date and time types in this crate. /// /// ``` /// use jiff::civil::time; /// /// let t1 = time(0, 0, 24, 0); /// let t2 = t1.with().hour(15).minute(30).millisecond(10).build()?; /// assert_eq!(t2, time(15, 30, 24, 10_000_000)); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn with(self) -> TimeWith { TimeWith::new(self) } /// Returns the "hour" component of this time. /// /// The value returned is guaranteed to be in the range `0..=23`. /// /// # Example /// /// ``` /// use jiff::civil::time; /// /// let t = time(13, 35, 56, 123_456_789); /// assert_eq!(t.hour(), 13); /// ``` #[inline] pub fn hour(self) -> i8 { self.hour_ranged().get() } /// Returns the "minute" component of this time. /// /// The value returned is guaranteed to be in the range `0..=59`. /// /// # Example /// /// ``` /// use jiff::civil::time; /// /// let t = time(13, 35, 56, 123_456_789); /// assert_eq!(t.minute(), 35); /// ``` #[inline] pub fn minute(self) -> i8 { self.minute_ranged().get() } /// Returns the "second" component of this time. /// /// The value returned is guaranteed to be in the range `0..=59`. /// /// # Example /// /// ``` /// use jiff::civil::time; /// /// let t = time(13, 35, 56, 123_456_789); /// assert_eq!(t.second(), 56); /// ``` #[inline] pub fn second(self) -> i8 { self.second_ranged().get() } /// Returns the "millisecond" component of this time. /// /// The value returned is guaranteed to be in the range `0..=999`. /// /// # Example /// /// ``` /// use jiff::civil::time; /// /// let t = time(13, 35, 56, 123_456_789); /// assert_eq!(t.millisecond(), 123); /// ``` #[inline] pub fn millisecond(self) -> i16 { self.millisecond_ranged().get() } /// Returns the "microsecond" component of this time. /// /// The value returned is guaranteed to be in the range `0..=999`. /// /// # Example /// /// ``` /// use jiff::civil::time; /// /// let t = time(13, 35, 56, 123_456_789); /// assert_eq!(t.microsecond(), 456); /// ``` #[inline] pub fn microsecond(self) -> i16 { self.microsecond_ranged().get() } /// Returns the "nanosecond" component of this time. /// /// The value returned is guaranteed to be in the range `0..=999`. /// /// # Example /// /// ``` /// use jiff::civil::time; /// /// let t = time(13, 35, 56, 123_456_789); /// assert_eq!(t.nanosecond(), 789); /// ``` #[inline] pub fn nanosecond(self) -> i16 { self.nanosecond_ranged().get() } /// Returns the fractional nanosecond for this `Time` value. /// /// If you want to set this value on `Time`, then use /// [`TimeWith::subsec_nanosecond`] via [`Time::with`]. /// /// The value returned is guaranteed to be in the range `0..=999_999_999`. /// /// # Example /// /// This shows the relationship between constructing a `Time` value /// with routines like `with().millisecond()` and accessing the entire /// fractional part as a nanosecond: /// /// ``` /// use jiff::civil::time; /// /// let t = time(15, 21, 35, 0).with().millisecond(987).build()?; /// assert_eq!(t.subsec_nanosecond(), 987_000_000); /// /// # Ok::<(), Box>(()) /// ``` /// /// # Example: nanoseconds from a timestamp /// /// This shows how the fractional nanosecond part of a `Time` value /// manifests from a specific timestamp. /// /// ``` /// use jiff::{civil, Timestamp}; /// /// // 1,234 nanoseconds after the Unix epoch. /// let zdt = Timestamp::new(0, 1_234)?.in_tz("UTC")?; /// let time = zdt.datetime().time(); /// assert_eq!(time.subsec_nanosecond(), 1_234); /// /// // 1,234 nanoseconds before the Unix epoch. /// let zdt = Timestamp::new(0, -1_234)?.in_tz("UTC")?; /// let time = zdt.datetime().time(); /// // The nanosecond is equal to `1_000_000_000 - 1_234`. /// assert_eq!(time.subsec_nanosecond(), 999998766); /// // Looking at the other components of the time value might help. /// assert_eq!(time.hour(), 23); /// assert_eq!(time.minute(), 59); /// assert_eq!(time.second(), 59); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn subsec_nanosecond(self) -> i32 { self.subsec_nanosecond_ranged().get() } /// Given a [`Date`], this constructs a [`DateTime`] value with its time /// component equal to this time. /// /// This is a convenience function for [`DateTime::from_parts`]. /// /// # Example /// /// ``` /// use jiff::civil::{DateTime, date, time}; /// /// let d = date(2010, 3, 14); /// let t = time(2, 30, 0, 0); /// assert_eq!(DateTime::from_parts(d, t), t.to_datetime(d)); /// ``` #[inline] pub const fn to_datetime(self, date: Date) -> DateTime { DateTime::from_parts(date, self) } /// A convenience function for constructing a [`DateTime`] from this time /// on the date given by its components. /// /// # Example /// /// ``` /// use jiff::civil::time; /// /// assert_eq!( /// time(2, 30, 0, 0).on(2010, 3, 14).to_string(), /// "2010-03-14T02:30:00", /// ); /// ``` /// /// One can also flip the order by making use of [`Date::at`]: /// /// ``` /// use jiff::civil::date; /// /// assert_eq!( /// date(2010, 3, 14).at(2, 30, 0, 0).to_string(), /// "2010-03-14T02:30:00", /// ); /// ``` #[inline] pub const fn on(self, year: i16, month: i8, day: i8) -> DateTime { DateTime::from_parts(Date::constant(year, month, day), self) } /// Add the given span to this time and wrap around on overflow. /// /// This operation accepts three different duration types: [`Span`], /// [`SignedDuration`] or [`std::time::Duration`]. This is achieved via /// `From` trait implementations for the [`TimeArithmetic`] type. /// /// # Properties /// /// Given times `t1` and `t2`, and a span `s`, with `t2 = t1 + s`, it /// follows then that `t1 = t2 - s` for all values of `t1` and `s` that sum /// to `t2`. /// /// In short, subtracting the given span from the sum returned by this /// function is guaranteed to result in precisely the original time. /// /// # Example: available via addition operator /// /// This routine can be used via the `+` operator. /// /// ``` /// use jiff::{civil::time, ToSpan}; /// /// let t = time(20, 10, 1, 0); /// assert_eq!( /// t + 1.hours().minutes(49).seconds(59), /// time(22, 0, 0, 0), /// ); /// ``` /// /// # Example: add nanoseconds to a `Time` /// /// ``` /// use jiff::{civil::time, ToSpan}; /// /// let t = time(22, 35, 1, 0); /// assert_eq!( /// time(22, 35, 3, 500_000_000), /// t.wrapping_add(2_500_000_000i64.nanoseconds()), /// ); /// ``` /// /// # Example: add span with multiple units /// /// ``` /// use jiff::{civil::time, ToSpan}; /// /// let t = time(20, 10, 1, 0); /// assert_eq!( /// time(22, 0, 0, 0), /// t.wrapping_add(1.hours().minutes(49).seconds(59)), /// ); /// ``` /// /// # Example: adding an empty span is a no-op /// /// ``` /// use jiff::{civil::time, Span}; /// /// let t = time(20, 10, 1, 0); /// assert_eq!(t, t.wrapping_add(Span::new())); /// ``` /// /// # Example: addition wraps on overflow /// /// ``` /// use jiff::{civil::time, SignedDuration, ToSpan}; /// /// let t = time(23, 59, 59, 999_999_999); /// assert_eq!( /// t.wrapping_add(1.nanoseconds()), /// time(0, 0, 0, 0), /// ); /// assert_eq!( /// t.wrapping_add(SignedDuration::from_nanos(1)), /// time(0, 0, 0, 0), /// ); /// assert_eq!( /// t.wrapping_add(std::time::Duration::from_nanos(1)), /// time(0, 0, 0, 0), /// ); /// ``` /// /// Similarly, if there are any non-zero units greater than hours in the /// given span, then they also result in wrapping behavior (i.e., they are /// ignored): /// /// ``` /// use jiff::{civil::time, ToSpan}; /// /// // doesn't matter what our time value is in this example /// let t = time(0, 0, 0, 0); /// assert_eq!(t, t.wrapping_add(1.days())); /// ``` #[inline] pub fn wrapping_add>(self, duration: A) -> Time { let duration: TimeArithmetic = duration.into(); duration.wrapping_add(self) } #[inline] fn wrapping_add_span(self, span: Span) -> Time { let mut sum = self.to_nanosecond().without_bounds(); sum = sum.wrapping_add( span.get_hours_ranged() .without_bounds() .wrapping_mul(t::NANOS_PER_HOUR), ); sum = sum.wrapping_add( span.get_minutes_ranged() .without_bounds() .wrapping_mul(t::NANOS_PER_MINUTE), ); sum = sum.wrapping_add( span.get_seconds_ranged() .without_bounds() .wrapping_mul(t::NANOS_PER_SECOND), ); sum = sum.wrapping_add( span.get_milliseconds_ranged() .without_bounds() .wrapping_mul(t::NANOS_PER_MILLI), ); sum = sum.wrapping_add( span.get_microseconds_ranged() .without_bounds() .wrapping_mul(t::NANOS_PER_MICRO), ); sum = sum.wrapping_add(span.get_nanoseconds_ranged().without_bounds()); let civil_day_nanosecond = sum % t::NANOS_PER_CIVIL_DAY; Time::from_nanosecond(civil_day_nanosecond) } #[inline] fn wrapping_add_signed_duration(self, duration: SignedDuration) -> Time { let start = t::NoUnits128::rfrom(self.to_nanosecond()); let duration = t::NoUnits128::new_unchecked(duration.as_nanos()); let end = start.wrapping_add(duration) % t::NANOS_PER_CIVIL_DAY; Time::from_nanosecond(end) } #[inline] fn wrapping_add_unsigned_duration( self, duration: UnsignedDuration, ) -> Time { let start = t::NoUnits128::rfrom(self.to_nanosecond()); // OK because 96-bit unsigned integer can't overflow i128. let duration = i128::try_from(duration.as_nanos()).unwrap(); let duration = t::NoUnits128::new_unchecked(duration); let duration = duration % t::NANOS_PER_CIVIL_DAY; let end = start.wrapping_add(duration) % t::NANOS_PER_CIVIL_DAY; Time::from_nanosecond(end) } /// This routine is identical to [`Time::wrapping_add`] with the duration /// negated. /// /// # Example /// /// ``` /// use jiff::{civil::time, SignedDuration, ToSpan}; /// /// let t = time(0, 0, 0, 0); /// assert_eq!( /// t.wrapping_sub(1.nanoseconds()), /// time(23, 59, 59, 999_999_999), /// ); /// assert_eq!( /// t.wrapping_sub(SignedDuration::from_nanos(1)), /// time(23, 59, 59, 999_999_999), /// ); /// assert_eq!( /// t.wrapping_sub(std::time::Duration::from_nanos(1)), /// time(23, 59, 59, 999_999_999), /// ); /// /// assert_eq!( /// t.wrapping_sub(SignedDuration::MIN), /// time(15, 30, 8, 999_999_999), /// ); /// assert_eq!( /// t.wrapping_sub(SignedDuration::MAX), /// time(8, 29, 52, 1), /// ); /// assert_eq!( /// t.wrapping_sub(std::time::Duration::MAX), /// time(16, 59, 44, 1), /// ); /// ``` #[inline] pub fn wrapping_sub>(self, duration: A) -> Time { let duration: TimeArithmetic = duration.into(); duration.wrapping_sub(self) } #[inline] fn wrapping_sub_unsigned_duration( self, duration: UnsignedDuration, ) -> Time { let start = t::NoUnits128::rfrom(self.to_nanosecond()); // OK because 96-bit unsigned integer can't overflow i128. let duration = i128::try_from(duration.as_nanos()).unwrap(); let duration = t::NoUnits128::new_unchecked(duration); let end = start.wrapping_sub(duration) % t::NANOS_PER_CIVIL_DAY; Time::from_nanosecond(end) } /// Add the given span to this time and return an error if the result would /// otherwise overflow. /// /// This operation accepts three different duration types: [`Span`], /// [`SignedDuration`] or [`std::time::Duration`]. This is achieved via /// `From` trait implementations for the [`TimeArithmetic`] type. /// /// # Properties /// /// Given a time `t1` and a span `s`, and assuming `t2 = t1 + s` exists, it /// follows then that `t1 = t2 - s` for all values of `t1` and `s` that sum /// to a valid `t2`. /// /// In short, subtracting the given span from the sum returned by this /// function is guaranteed to result in precisely the original time. /// /// # Errors /// /// If the sum would overflow the minimum or maximum timestamp values, then /// an error is returned. /// /// If the given span has any non-zero units greater than hours, then an /// error is returned. /// /// # Example: add nanoseconds to a `Time` /// /// ``` /// use jiff::{civil::time, ToSpan}; /// /// let t = time(22, 35, 1, 0); /// assert_eq!( /// time(22, 35, 3, 500_000_000), /// t.checked_add(2_500_000_000i64.nanoseconds())?, /// ); /// # Ok::<(), Box>(()) /// ``` /// /// # Example: add span with multiple units /// /// ``` /// use jiff::{civil::time, ToSpan}; /// /// let t = time(20, 10, 1, 0); /// assert_eq!( /// time(22, 0, 0, 0), /// t.checked_add(1.hours().minutes(49).seconds(59))?, /// ); /// # Ok::<(), Box>(()) /// ``` /// /// # Example: adding an empty span is a no-op /// /// ``` /// use jiff::{civil::time, Span}; /// /// let t = time(20, 10, 1, 0); /// assert_eq!(t, t.checked_add(Span::new())?); /// /// # Ok::<(), Box>(()) /// ``` /// /// # Example: error on overflow /// /// ``` /// use jiff::{civil::time, ToSpan}; /// /// // okay /// let t = time(23, 59, 59, 999_999_998); /// assert_eq!( /// t.with().nanosecond(999).build()?, /// t.checked_add(1.nanoseconds())?, /// ); /// /// // not okay /// let t = time(23, 59, 59, 999_999_999); /// assert!(t.checked_add(1.nanoseconds()).is_err()); /// /// # Ok::<(), Box>(()) /// ``` /// /// Similarly, if there are any non-zero units greater than hours in the /// given span, then they also result in overflow (and thus an error): /// /// ``` /// use jiff::{civil::time, ToSpan}; /// /// // doesn't matter what our time value is in this example /// let t = time(0, 0, 0, 0); /// assert!(t.checked_add(1.days()).is_err()); /// ``` /// /// # Example: adding absolute durations /// /// This shows how to add signed and unsigned absolute durations to a /// `Time`. As with adding a `Span`, any overflow that occurs results in /// an error. /// /// ``` /// use std::time::Duration; /// /// use jiff::{civil::time, SignedDuration}; /// /// let t = time(23, 0, 0, 0); /// /// let dur = SignedDuration::from_mins(30); /// assert_eq!(t.checked_add(dur)?, time(23, 30, 0, 0)); /// assert_eq!(t.checked_add(-dur)?, time(22, 30, 0, 0)); /// /// let dur = Duration::new(0, 1); /// assert_eq!(t.checked_add(dur)?, time(23, 0, 0, 1)); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn checked_add>( self, duration: A, ) -> Result { let duration: TimeArithmetic = duration.into(); duration.checked_add(self) } #[inline] fn checked_add_span(self, span: Span) -> Result { let (time, span) = self.overflowing_add(span)?; if let Some(err) = span.smallest_non_time_non_zero_unit_error() { return Err(err); } Ok(time) } #[inline] fn checked_add_duration( self, duration: SignedDuration, ) -> Result { let original = duration; let start = t::NoUnits128::rfrom(self.to_nanosecond()); let duration = t::NoUnits128::new_unchecked(duration.as_nanos()); // This can never fail because the maximum duration fits into a // 96-bit integer, and adding any 96-bit integer to any 64-bit // integer can never overflow a 128-bit integer. let end = start.try_checked_add("nanoseconds", duration).unwrap(); let end = CivilDayNanosecond::try_rfrom("nanoseconds", end) .with_context(|| { err!( "adding signed duration {duration:?}, equal to {nanos} nanoseconds, to {time} overflowed", duration = original, nanos = original.as_nanos(), time = self, ) })?; Ok(Time::from_nanosecond(end)) } /// This routine is identical to [`Time::checked_add`] with the duration /// negated. /// /// # Errors /// /// This has the same error conditions as [`Time::checked_add`]. /// /// # Example /// /// ``` /// use std::time::Duration; /// /// use jiff::{civil::time, SignedDuration, ToSpan}; /// /// let t = time(22, 0, 0, 0); /// assert_eq!( /// t.checked_sub(1.hours().minutes(49).seconds(59))?, /// time(20, 10, 1, 0), /// ); /// assert_eq!( /// t.checked_sub(SignedDuration::from_hours(1))?, /// time(21, 0, 0, 0), /// ); /// assert_eq!( /// t.checked_sub(Duration::from_secs(60 * 60))?, /// time(21, 0, 0, 0), /// ); /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn checked_sub>( self, duration: A, ) -> Result { let duration: TimeArithmetic = duration.into(); duration.checked_neg().and_then(|ta| ta.checked_add(self)) } /// This routine is identical to [`Time::checked_add`], except the /// result saturates on overflow. That is, instead of overflow, either /// [`Time::MIN`] or [`Time::MAX`] is returned. /// /// # Example /// /// ``` /// use jiff::{civil::{Time, time}, SignedDuration, ToSpan}; /// /// // no saturation /// let t = time(23, 59, 59, 999_999_998); /// assert_eq!( /// t.with().nanosecond(999).build()?, /// t.saturating_add(1.nanoseconds()), /// ); /// /// // saturates /// let t = time(23, 59, 59, 999_999_999); /// assert_eq!(Time::MAX, t.saturating_add(1.nanoseconds())); /// assert_eq!(Time::MAX, t.saturating_add(SignedDuration::MAX)); /// assert_eq!(Time::MIN, t.saturating_add(SignedDuration::MIN)); /// assert_eq!(Time::MAX, t.saturating_add(std::time::Duration::MAX)); /// /// # Ok::<(), Box>(()) /// ``` /// /// Similarly, if there are any non-zero units greater than hours in the /// given span, then they also result in overflow (and thus saturation): /// /// ``` /// use jiff::{civil::{Time, time}, ToSpan}; /// /// // doesn't matter what our time value is in this example /// let t = time(0, 0, 0, 0); /// assert_eq!(Time::MAX, t.saturating_add(1.days())); /// ``` #[inline] pub fn saturating_add>(self, duration: A) -> Time { let duration: TimeArithmetic = duration.into(); self.checked_add(duration).unwrap_or_else(|_| { if duration.is_negative() { Time::MIN } else { Time::MAX } }) } /// This routine is identical to [`Time::saturating_add`] with the duration /// negated. /// /// # Example /// /// ``` /// use jiff::{civil::{Time, time}, SignedDuration, ToSpan}; /// /// // no saturation /// let t = time(0, 0, 0, 1); /// assert_eq!( /// t.with().nanosecond(0).build()?, /// t.saturating_sub(1.nanoseconds()), /// ); /// /// // saturates /// let t = time(0, 0, 0, 0); /// assert_eq!(Time::MIN, t.saturating_sub(1.nanoseconds())); /// assert_eq!(Time::MIN, t.saturating_sub(SignedDuration::MAX)); /// assert_eq!(Time::MAX, t.saturating_sub(SignedDuration::MIN)); /// assert_eq!(Time::MIN, t.saturating_sub(std::time::Duration::MAX)); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn saturating_sub>(self, duration: A) -> Time { let duration: TimeArithmetic = duration.into(); let Ok(duration) = duration.checked_neg() else { return Time::MIN }; self.saturating_add(duration) } /// Adds the given span to the this time value, and returns the resulting /// time with any overflowing amount in the span returned. /// /// This isn't part of the public API because it seems a little odd, and /// I'm unsure of its use case. Overall this routine is a bit specialized /// and I'm not sure how generally useful it is. But it is used in crucial /// points in other parts of this crate. /// /// If you want this public, please file an issue and discuss your use /// case: https://github.com/BurntSushi/jiff/issues/new #[inline] pub(crate) fn overflowing_add( self, span: Span, ) -> Result<(Time, Span), Error> { if let Some(err) = span.smallest_non_time_non_zero_unit_error() { return Err(err); } let span_nanos = span.to_invariant_nanoseconds(); let time_nanos = self.to_nanosecond(); let sum = span_nanos + time_nanos; let days = t::SpanDays::try_new( "overflowing-days", sum.div_floor(t::NANOS_PER_CIVIL_DAY), )?; let time_nanos = sum.rem_floor(t::NANOS_PER_CIVIL_DAY); let time = Time::from_nanosecond(time_nanos); Ok((time, Span::new().days_ranged(days))) } /// Like `overflowing_add`, but with `SignedDuration`. /// /// This is used for datetime arithmetic, when adding to the time /// component overflows into days (always 24 hours). #[inline] pub(crate) fn overflowing_add_duration( self, duration: SignedDuration, ) -> Result<(Time, SignedDuration), Error> { let start = t::NoUnits128::rfrom(self.to_nanosecond()); let duration = t::NoUnits96::new_unchecked(duration.as_nanos()); // This can never fail because the maximum duration fits into a // 96-bit integer, and adding any 96-bit integer to any 64-bit // integer can never overflow a 128-bit integer. let sum = start.try_checked_add("nanoseconds", duration).unwrap(); let days = t::SpanDays::try_new( "overflowing-days", sum.div_floor(t::NANOS_PER_CIVIL_DAY), )?; let time_nanos = sum.rem_floor(t::NANOS_PER_CIVIL_DAY); let time = Time::from_nanosecond(time_nanos); // OK because of the constraint imposed by t::SpanDays. let hours = i64::from(days).checked_mul(24).unwrap(); Ok((time, SignedDuration::from_hours(hours))) } /// Returns a span representing the elapsed time from this time until /// the given `other` time. /// /// When `other` is earlier than this time, the span returned will be /// negative. /// /// Depending on the input provided, the span returned is rounded. It may /// also be balanced down to smaller units than the default. By default, /// the span returned is balanced such that the biggest possible unit is /// hours. /// /// This operation is configured by providing a [`TimeDifference`] /// value. Since this routine accepts anything that implements /// `Into`, once can pass a `Time` directly. One /// can also pass a `(Unit, Time)`, where `Unit` is treated as /// [`TimeDifference::largest`]. /// /// # Properties /// /// As long as no rounding is requested, it is guaranteed that adding the /// span returned to the `other` time will always equal this time. /// /// # Errors /// /// An error can occur if `TimeDifference` is misconfigured. For example, /// if the smallest unit provided is bigger than the largest unit, or if /// the largest unit is bigger than [`Unit::Hour`]. /// /// It is guaranteed that if one provides a time with the default /// [`TimeDifference`] configuration, then this routine will never fail. /// /// # Examples /// /// ``` /// use jiff::{civil::time, ToSpan}; /// /// let t1 = time(22, 35, 1, 0); /// let t2 = time(22, 35, 3, 500_000_000); /// assert_eq!(t1.until(t2)?, 2.seconds().milliseconds(500).fieldwise()); /// // Flipping the dates is fine, but you'll get a negative span. /// assert_eq!(t2.until(t1)?, -2.seconds().milliseconds(500).fieldwise()); /// /// # Ok::<(), Box>(()) /// ``` /// /// # Example: using smaller units /// /// This example shows how to contract the span returned to smaller units. /// This makes use of a `From<(Unit, Time)> for TimeDifference` /// trait implementation. /// /// ``` /// use jiff::{civil::time, Unit, ToSpan}; /// /// let t1 = time(3, 24, 30, 3500); /// let t2 = time(15, 30, 0, 0); /// /// // The default limits spans to using "hours" as the biggest unit. /// let span = t1.until(t2)?; /// assert_eq!(span.to_string(), "PT12H5M29.9999965S"); /// /// // But we can ask for smaller units, like capping the biggest unit /// // to minutes instead of hours. /// let span = t1.until((Unit::Minute, t2))?; /// assert_eq!(span.to_string(), "PT725M29.9999965S"); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn until>( self, other: A, ) -> Result { let args: TimeDifference = other.into(); let span = args.until_with_largest_unit(self)?; if args.rounding_may_change_span() { span.round(args.round) } else { Ok(span) } } /// This routine is identical to [`Time::until`], but the order of the /// parameters is flipped. /// /// # Errors /// /// This has the same error conditions as [`Time::until`]. /// /// # Example /// /// This routine can be used via the `-` operator. Since the default /// configuration is used and because a `Span` can represent the difference /// between any two possible times, it will never panic. /// /// ``` /// use jiff::{civil::time, ToSpan}; /// /// let earlier = time(1, 0, 0, 0); /// let later = time(22, 30, 0, 0); /// assert_eq!(later - earlier, 21.hours().minutes(30).fieldwise()); /// ``` #[inline] pub fn since>( self, other: A, ) -> Result { let args: TimeDifference = other.into(); let span = -args.until_with_largest_unit(self)?; if args.rounding_may_change_span() { span.round(args.round) } else { Ok(span) } } /// Returns an absolute duration representing the elapsed time from this /// time until the given `other` time. /// /// When `other` occurs before this time, then the duration returned will /// be negative. /// /// Unlike [`Time::until`], this returns a duration corresponding to a /// 96-bit integer of nanoseconds between two times. In this case of /// computing durations between civil times where all days are assumed to /// be 24 hours long, the duration returned will always be less than 24 /// hours. /// /// # Fallibility /// /// This routine never panics or returns an error. Since there are no /// configuration options that can be incorrectly provided, no error is /// possible when calling this routine. In contrast, [`Time::until`] can /// return an error in some cases due to misconfiguration. But like this /// routine, [`Time::until`] never panics or returns an error in its /// default configuration. /// /// # When should I use this versus [`Time::until`]? /// /// See the type documentation for [`SignedDuration`] for the section on /// when one should use [`Span`] and when one should use `SignedDuration`. /// In short, use `Span` (and therefore `Time::until`) unless you have a /// specific reason to do otherwise. /// /// # Example /// /// ``` /// use jiff::{civil::time, SignedDuration}; /// /// let t1 = time(22, 35, 1, 0); /// let t2 = time(22, 35, 3, 500_000_000); /// assert_eq!(t1.duration_until(t2), SignedDuration::new(2, 500_000_000)); /// // Flipping the time is fine, but you'll get a negative duration. /// assert_eq!(t2.duration_until(t1), -SignedDuration::new(2, 500_000_000)); /// ``` /// /// # Example: difference with [`Time::until`] /// /// Since the difference between two civil times is always expressed in /// units of hours or smaller, and units of hours or smaller are always /// uniform, there is no "expressive" difference between this routine and /// `Time::until`. The only difference is that this routine returns a /// `SignedDuration` and `Time::until` returns a [`Span`]. Moreover, since /// the difference is always less than 24 hours, the return values can /// always be infallibly and losslessly converted between each other: /// /// ``` /// use jiff::{civil::time, SignedDuration, Span}; /// /// let t1 = time(22, 35, 1, 0); /// let t2 = time(22, 35, 3, 500_000_000); /// let dur = t1.duration_until(t2); /// // Guaranteed to never fail because the duration /// // between two civil times never exceeds the limits /// // of a `Span`. /// let span = Span::try_from(dur).unwrap(); /// assert_eq!(span, Span::new().seconds(2).milliseconds(500).fieldwise()); /// // Guaranteed to succeed and always return the original /// // duration because the units are always hours or smaller, /// // and thus uniform. This means a relative datetime is /// // never required to do this conversion. /// let dur = SignedDuration::try_from(span).unwrap(); /// assert_eq!(dur, SignedDuration::new(2, 500_000_000)); /// ``` /// /// This conversion guarantee also applies to [`Time::until`] since it /// always returns a balanced span. That is, it never returns spans like /// `1 second 1000 milliseconds`. (Those cannot be losslessly converted to /// a `SignedDuration` since a `SignedDuration` is only represented as a /// single 96-bit integer of nanoseconds.) /// /// # Example: getting an unsigned duration /// /// If you're looking to find the duration between two times as a /// [`std::time::Duration`], you'll need to use this method to get a /// [`SignedDuration`] and then convert it to a `std::time::Duration`: /// /// ``` /// use std::time::Duration; /// /// use jiff::{civil::time, SignedDuration, Span}; /// /// let t1 = time(22, 35, 1, 0); /// let t2 = time(22, 35, 3, 500_000_000); /// let dur = Duration::try_from(t1.duration_until(t2))?;; /// assert_eq!(dur, Duration::new(2, 500_000_000)); /// /// // Note that unsigned durations cannot represent all /// // possible differences! If the duration would be negative, /// // then the conversion fails: /// assert!(Duration::try_from(t2.duration_until(t1)).is_err()); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn duration_until(self, other: Time) -> SignedDuration { SignedDuration::time_until(self, other) } /// This routine is identical to [`Time::duration_until`], but the order of /// the parameters is flipped. /// /// # Example /// /// ``` /// use jiff::{civil::time, SignedDuration}; /// /// let earlier = time(1, 0, 0, 0); /// let later = time(22, 30, 0, 0); /// assert_eq!( /// later.duration_since(earlier), /// SignedDuration::from_secs((21 * 60 * 60) + (30 * 60)), /// ); /// ``` #[inline] pub fn duration_since(self, other: Time) -> SignedDuration { SignedDuration::time_until(other, self) } /// Rounds this time according to the [`TimeRound`] configuration given. /// /// The principal option is [`TimeRound::smallest`], which allows one /// to configure the smallest units in the returned time. Rounding /// is what determines whether that unit should keep its current value /// or whether it should be incremented. Moreover, the amount it should /// be incremented can be configured via [`TimeRound::increment`]. /// Finally, the rounding strategy itself can be configured via /// [`TimeRound::mode`]. /// /// Note that this routine is generic and accepts anything that /// implements `Into`. Some notable implementations are: /// /// * `From for Round`, which will automatically create a /// `TimeRound::new().smallest(unit)` from the unit provided. /// * `From<(Unit, i64)> for Round`, which will automatically create a /// `TimeRound::new().smallest(unit).increment(number)` from the unit /// and increment provided. /// /// # Errors /// /// This returns an error if the smallest unit configured on the given /// [`TimeRound`] is bigger than hours. /// /// The rounding increment must divide evenly into the next highest unit /// after the smallest unit configured (and must not be equivalent to it). /// For example, if the smallest unit is [`Unit::Nanosecond`], then *some* /// of the valid values for the rounding increment are `1`, `2`, `4`, `5`, /// `100` and `500`. Namely, any integer that divides evenly into `1,000` /// nanoseconds since there are `1,000` nanoseconds in the next highest /// unit (microseconds). /// /// This can never fail because of overflow for any input. The only /// possible errors are "configuration" errors. /// /// # Example /// /// This is a basic example that demonstrates rounding a datetime to the /// nearest second. This also demonstrates calling this method with the /// smallest unit directly, instead of constructing a `TimeRound` manually. /// /// ``` /// use jiff::{civil::time, Unit}; /// /// let t = time(15, 45, 10, 123_456_789); /// assert_eq!( /// t.round(Unit::Second)?, /// time(15, 45, 10, 0), /// ); /// let t = time(15, 45, 10, 500_000_001); /// assert_eq!( /// t.round(Unit::Second)?, /// time(15, 45, 11, 0), /// ); /// /// # Ok::<(), Box>(()) /// ``` /// /// # Example: changing the rounding mode /// /// The default rounding mode is [`RoundMode::HalfExpand`], which /// breaks ties by rounding away from zero. But other modes like /// [`RoundMode::Trunc`] can be used too: /// /// ``` /// use jiff::{civil::{TimeRound, time}, RoundMode, Unit}; /// /// let t = time(15, 45, 10, 999_999_999); /// assert_eq!( /// t.round(Unit::Second)?, /// time(15, 45, 11, 0), /// ); /// // The default will round up to the next second for any fraction /// // greater than or equal to 0.5. But truncation will always round /// // toward zero. /// assert_eq!( /// t.round( /// TimeRound::new().smallest(Unit::Second).mode(RoundMode::Trunc), /// )?, /// time(15, 45, 10, 0), /// ); /// /// # Ok::<(), Box>(()) /// ``` /// /// # Example: rounding to the nearest 5 minute increment /// /// ``` /// use jiff::{civil::time, Unit}; /// /// // rounds down /// let t = time(15, 27, 29, 999_999_999); /// assert_eq!(t.round((Unit::Minute, 5))?, time(15, 25, 0, 0)); /// // rounds up /// let t = time(15, 27, 30, 0); /// assert_eq!(t.round((Unit::Minute, 5))?, time(15, 30, 0, 0)); /// /// # Ok::<(), Box>(()) /// ``` /// /// # Example: rounding wraps around on overflow /// /// This example demonstrates that it's possible for this operation to /// overflow, and as a result, have the time wrap around. /// /// ``` /// use jiff::{civil::Time, Unit}; /// /// let t = Time::MAX; /// assert_eq!(t.round(Unit::Hour)?, Time::MIN); /// /// # Ok::<(), Box>(()) /// ``` #[inline] pub fn round>(self, options: R) -> Result { let options: TimeRound = options.into(); options.round(self) } /// Return an iterator of periodic times determined by the given span. /// /// The given span may be negative, in which case, the iterator will move /// backwards through time. The iterator won't stop until either the span /// itself overflows, or it would otherwise exceed the minimum or maximum /// `Time` value. /// /// # Example: visiting every third hour /// /// This shows how to visit each third hour of a 24 hour time interval: /// /// ``` /// use jiff::{civil::{Time, time}, ToSpan}; /// /// let start = Time::MIN; /// let mut every_third_hour = vec![]; /// for t in start.series(3.hours()) { /// every_third_hour.push(t); /// } /// assert_eq!(every_third_hour, vec![ /// time(0, 0, 0, 0), /// time(3, 0, 0, 0), /// time(6, 0, 0, 0), /// time(9, 0, 0, 0), /// time(12, 0, 0, 0), /// time(15, 0, 0, 0), /// time(18, 0, 0, 0), /// time(21, 0, 0, 0), /// ]); /// ``` /// /// Or go backwards every 6.5 hours: /// /// ``` /// use jiff::{civil::{Time, time}, ToSpan}; /// /// let start = time(23, 0, 0, 0); /// let times: Vec