├── .gitignore ├── time ├── LICENSE-MIT ├── LICENSE-Apache ├── tests │ └── integration │ │ ├── convert.rs │ │ ├── compile-fail │ │ ├── invalid_datetime.rs │ │ ├── invalid_offset.rs │ │ ├── invalid_utc_datetime.rs │ │ ├── invalid_time.rs │ │ ├── invalid_date.rs │ │ ├── invalid_datetime.stderr │ │ ├── invalid_utc_datetime.stderr │ │ ├── invalid_serializer.rs │ │ ├── invalid_offset.stderr │ │ ├── invalid_format_description.rs │ │ ├── invalid_time.stderr │ │ ├── invalid_date.stderr │ │ └── invalid_serializer.stderr │ │ ├── serde │ │ ├── rfc2822.rs │ │ ├── error_conditions.rs │ │ ├── iso8601.rs │ │ └── rfc3339.rs │ │ ├── rand.rs │ │ ├── format_description.rs │ │ ├── util.rs │ │ └── weekday.rs ├── src │ ├── sys │ │ ├── local_offset_at │ │ │ ├── imp.rs │ │ │ ├── wasm_js.rs │ │ │ ├── mod.rs │ │ │ ├── unix.rs │ │ │ └── windows.rs │ │ ├── refresh_tz │ │ │ ├── imp.rs │ │ │ ├── mod.rs │ │ │ └── unix.rs │ │ └── mod.rs │ ├── parsing │ │ ├── combinator │ │ │ └── rfc │ │ │ │ ├── mod.rs │ │ │ │ ├── rfc2234.rs │ │ │ │ └── rfc2822.rs │ │ ├── shim.rs │ │ └── mod.rs │ ├── ext │ │ ├── mod.rs │ │ ├── digit_count.rs │ │ ├── systemtime.rs │ │ └── instant.rs │ ├── hint.rs │ ├── interop │ │ ├── js_sys_date_utcdatetime.rs │ │ ├── js_sys_date_offsetdatetime.rs │ │ ├── mod.rs │ │ ├── offsetdatetime_utcdatetime.rs │ │ ├── utcdatetime_systemtime.rs │ │ └── offsetdatetime_systemtime.rs │ ├── error │ │ ├── invalid_variant.rs │ │ ├── different_variant.rs │ │ ├── indeterminate_offset.rs │ │ ├── conversion_range.rs │ │ ├── parse_from_description.rs │ │ ├── try_from_parsed.rs │ │ ├── parse.rs │ │ ├── format.rs │ │ └── component_range.rs │ ├── format_description │ │ ├── well_known │ │ │ ├── rfc3339.rs │ │ │ └── rfc2822.rs │ │ ├── mod.rs │ │ ├── component.rs │ │ └── borrowed_format_item.rs │ ├── serde │ │ ├── timestamp │ │ │ ├── mod.rs │ │ │ ├── nanoseconds.rs │ │ │ ├── microseconds.rs │ │ │ ├── milliseconds.rs │ │ │ └── milliseconds_i64.rs │ │ ├── rfc2822.rs │ │ ├── rfc3339.rs │ │ └── iso8601.rs │ ├── rand08.rs │ ├── rand09.rs │ ├── tests.rs │ └── util.rs ├── benchmarks │ ├── utc_date_time.rs │ ├── util.rs │ ├── rand08.rs │ ├── rand09.rs │ ├── utc_offset.rs │ ├── month.rs │ ├── instant.rs │ ├── weekday.rs │ └── main.rs └── Cargo.toml ├── time-core ├── LICENSE-MIT ├── LICENSE-Apache ├── src │ ├── lib.rs │ ├── hint.rs │ └── util.rs └── Cargo.toml ├── time-macros ├── LICENSE-MIT ├── LICENSE-Apache ├── Cargo.toml └── src │ ├── utc_datetime.rs │ ├── format_description │ ├── public │ │ ├── component.rs │ │ └── mod.rs │ └── mod.rs │ ├── datetime.rs │ ├── to_tokens.rs │ ├── offset.rs │ └── time.rs ├── .mailmap ├── .deny.toml ├── .editorconfig ├── .github ├── codecov.yaml └── workflows │ ├── dependency-audit.yaml │ ├── github-release.yaml │ └── powerset.yaml ├── rustfmt.toml ├── logo.svg ├── LICENSE-MIT ├── .clippy.toml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /time/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /time-core/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /time-macros/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /time/LICENSE-Apache: -------------------------------------------------------------------------------- 1 | ../LICENSE-Apache -------------------------------------------------------------------------------- /time-core/LICENSE-Apache: -------------------------------------------------------------------------------- 1 | ../LICENSE-Apache -------------------------------------------------------------------------------- /time-macros/LICENSE-Apache: -------------------------------------------------------------------------------- 1 | ../LICENSE-Apache -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | Jacob Pratt 2 | -------------------------------------------------------------------------------- /.deny.toml: -------------------------------------------------------------------------------- 1 | [licenses] 2 | allow = ["MIT", "Apache-2.0", "Unicode-3.0"] 3 | -------------------------------------------------------------------------------- /time/tests/integration/convert.rs: -------------------------------------------------------------------------------- 1 | use rstest::rstest; 2 | use time::convert::*; 3 | 4 | #[rstest] 5 | fn issue_749() { 6 | assert_eq!(Nanosecond::per(Second), 1_000_000_000u32); 7 | } 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.yaml] 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /.github/codecov.yaml: -------------------------------------------------------------------------------- 1 | codecov: 2 | max_report_age: 1 3 | require_ci_to_pass: false 4 | notify: 5 | wait_for_ci: false 6 | 7 | coverage: 8 | precision: 1 9 | round: "nearest" 10 | status: 11 | project: 12 | default: 13 | informational: true 14 | -------------------------------------------------------------------------------- /time/tests/integration/compile-fail/invalid_datetime.rs: -------------------------------------------------------------------------------- 1 | use time::macros::datetime; 2 | 3 | fn main() { 4 | let _ = datetime!(2021-000 0:00); 5 | let _ = datetime!(2021-001 24:00); 6 | let _ = datetime!(2021-001 0:00 0); 7 | let _ = datetime!(2021-001 0:00 UTC x); 8 | } 9 | -------------------------------------------------------------------------------- /time/src/sys/local_offset_at/imp.rs: -------------------------------------------------------------------------------- 1 | //! A fallback for any OS not covered. 2 | 3 | use crate::{OffsetDateTime, UtcOffset}; 4 | 5 | #[expect(clippy::missing_docs_in_private_items)] 6 | #[inline] 7 | pub(super) fn local_offset_at(_datetime: OffsetDateTime) -> Option { 8 | None 9 | } 10 | -------------------------------------------------------------------------------- /time/src/sys/refresh_tz/imp.rs: -------------------------------------------------------------------------------- 1 | //! A fallback for any OS not covered. 2 | 3 | #[expect(clippy::missing_docs_in_private_items)] 4 | #[inline] 5 | pub(super) unsafe fn refresh_tz_unchecked() {} 6 | 7 | #[expect(clippy::missing_docs_in_private_items)] 8 | #[inline] 9 | pub(super) fn refresh_tz() -> Option<()> { 10 | Some(()) 11 | } 12 | -------------------------------------------------------------------------------- /time/tests/integration/compile-fail/invalid_offset.rs: -------------------------------------------------------------------------------- 1 | use time::macros::offset; 2 | 3 | fn main() { 4 | let _ = offset!(+26); 5 | let _ = offset!(+0:60); 6 | let _ = offset!(+0:00:60); 7 | let _ = offset!(0); 8 | let _ = offset!(); 9 | let _ = offset!(+0a); 10 | let _ = offset!(+0:0a); 11 | let _ = offset!(+0:00:0a); 12 | } 13 | -------------------------------------------------------------------------------- /time/benchmarks/utc_date_time.rs: -------------------------------------------------------------------------------- 1 | use std::hint::black_box; 2 | 3 | use criterion::Bencher; 4 | use time::macros::{offset, utc_datetime}; 5 | 6 | setup_benchmark! { 7 | "UtcDateTime", 8 | 9 | fn to_offset(ben: &mut Bencher<'_>) { 10 | ben.iter(|| black_box(utc_datetime!(2000-01-01 0:00)).to_offset(black_box(offset!(-5)))); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /time/tests/integration/compile-fail/invalid_utc_datetime.rs: -------------------------------------------------------------------------------- 1 | use time::macros::utc_datetime; 2 | 3 | fn main() { 4 | let _ = utc_datetime!(2021-000 0:00); 5 | let _ = utc_datetime!(2021-001 24:00); 6 | let _ = utc_datetime!(2021-001 0:00 0); 7 | let _ = utc_datetime!(2021-001 0:00 UTC); 8 | let _ = utc_datetime!(2021-001 0:00 UTC x); 9 | } 10 | -------------------------------------------------------------------------------- /time/src/parsing/combinator/rfc/mod.rs: -------------------------------------------------------------------------------- 1 | //! Combinators for rules as defined in a standard. 2 | //! 3 | //! When applicable, these rules have been converted strictly following the ABNF syntax as specified 4 | //! in [RFC 2234]. 5 | //! 6 | //! [RFC 2234]: https://datatracker.ietf.org/doc/html/rfc2234 7 | 8 | pub(crate) mod iso8601; 9 | pub(crate) mod rfc2234; 10 | pub(crate) mod rfc2822; 11 | -------------------------------------------------------------------------------- /time/src/sys/mod.rs: -------------------------------------------------------------------------------- 1 | //! Functions with a common interface that rely on system calls. 2 | 3 | #[cfg(feature = "local-offset")] 4 | mod local_offset_at; 5 | #[cfg(feature = "local-offset")] 6 | mod refresh_tz; 7 | 8 | #[cfg(feature = "local-offset")] 9 | pub(crate) use self::local_offset_at::local_offset_at; 10 | #[cfg(feature = "local-offset")] 11 | pub(crate) use self::refresh_tz::{refresh_tz, refresh_tz_unchecked}; 12 | -------------------------------------------------------------------------------- /time-core/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Core items for `time`. 2 | //! 3 | //! This crate is an implementation detail of `time` and should not be relied upon directly. 4 | 5 | #![no_std] 6 | #![doc(html_favicon_url = "https://avatars0.githubusercontent.com/u/55999857")] 7 | #![doc(html_logo_url = "https://avatars0.githubusercontent.com/u/55999857")] 8 | #![doc(test(attr(deny(warnings))))] 9 | 10 | pub mod convert; 11 | mod hint; 12 | pub mod util; 13 | -------------------------------------------------------------------------------- /.github/workflows/dependency-audit.yaml: -------------------------------------------------------------------------------- 1 | name: Dependency audit 2 | 3 | on: 4 | schedule: 5 | - cron: "0 0 * * 1" # midnight on Monday 6 | workflow_dispatch: 7 | 8 | jobs: 9 | security-audit: 10 | name: Dependency audit 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout sources 14 | uses: actions/checkout@v5 15 | 16 | - name: Audit dependencies 17 | uses: EmbarkStudios/cargo-deny-action@v2 18 | -------------------------------------------------------------------------------- /time/src/parsing/combinator/rfc/rfc2234.rs: -------------------------------------------------------------------------------- 1 | //! Rules defined in [RFC 2234]. 2 | //! 3 | //! [RFC 2234]: https://datatracker.ietf.org/doc/html/rfc2234 4 | 5 | use crate::parsing::ParsedItem; 6 | 7 | /// Consume exactly one space or tab. 8 | #[inline] 9 | pub(crate) const fn wsp(input: &[u8]) -> Option> { 10 | match input { 11 | [b' ' | b'\t', rest @ ..] => Some(ParsedItem(rest, ())), 12 | _ => None, 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /time/tests/integration/compile-fail/invalid_time.rs: -------------------------------------------------------------------------------- 1 | use time::macros::time; 2 | 3 | fn main() { 4 | let _ = time!(24:00); 5 | let _ = time!(0:60); 6 | let _ = time!(0:00:60); 7 | let _ = time!(x); 8 | let _ = time!(0:00:00 x); 9 | let _ = time!(""); 10 | let _ = time!(0:); 11 | let _ = time!(0,); 12 | let _ = time!(0:00:0a); 13 | let _ = time!(0:00 pm); 14 | let _ = time!(0); 15 | let _ = time!(0 pm); 16 | let _ = time!(1 am :); 17 | } 18 | -------------------------------------------------------------------------------- /time/src/ext/mod.rs: -------------------------------------------------------------------------------- 1 | //! Extension traits. 2 | 3 | mod digit_count; 4 | #[cfg(feature = "std")] 5 | mod instant; 6 | mod numerical_duration; 7 | mod numerical_std_duration; 8 | #[cfg(feature = "std")] 9 | mod systemtime; 10 | 11 | pub(crate) use self::digit_count::DigitCount; 12 | #[cfg(feature = "std")] 13 | pub use self::instant::InstantExt; 14 | pub use self::numerical_duration::NumericalDuration; 15 | pub use self::numerical_std_duration::NumericalStdDuration; 16 | #[cfg(feature = "std")] 17 | pub use self::systemtime::SystemTimeExt; 18 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | newline_style = "unix" 2 | use_field_init_shorthand = true 3 | use_try_shorthand = true 4 | 5 | # Unstable features below 6 | unstable_features = true 7 | comment_width = 100 8 | error_on_line_overflow = true 9 | format_code_in_doc_comments = true 10 | format_macro_bodies = true 11 | format_macro_matchers = true 12 | format_strings = true 13 | group_imports = "StdExternalCrate" 14 | imports_granularity = "Module" 15 | normalize_doc_attributes = true 16 | skip_macro_invocations = ["date", "time", "offset"] 17 | wrap_comments = true 18 | -------------------------------------------------------------------------------- /time-core/Cargo.toml: -------------------------------------------------------------------------------- 1 | lints.workspace = true 2 | 3 | [package] 4 | name = "time-core" 5 | version = "0.1.6" 6 | categories = ["date-and-time"] 7 | description = "This crate is an implementation detail and should not be relied upon directly." 8 | 9 | authors.workspace = true 10 | edition.workspace = true 11 | keywords.workspace = true 12 | license.workspace = true 13 | repository.workspace = true 14 | rust-version.workspace = true 15 | 16 | [package.metadata.docs.rs] 17 | all-features = true 18 | targets = ["x86_64-unknown-linux-gnu"] 19 | rustdoc-args = ["--generate-link-to-definition"] 20 | -------------------------------------------------------------------------------- /time/src/sys/refresh_tz/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg_attr(target_family = "unix", path = "unix.rs")] 2 | mod imp; 3 | 4 | /// Update time zone information from the system. 5 | /// 6 | /// For safety documentation, see [`time::util::refresh_tz`]. 7 | #[inline] 8 | pub(crate) unsafe fn refresh_tz_unchecked() { 9 | // Safety: The caller must uphold the safety requirements. 10 | unsafe { imp::refresh_tz_unchecked() } 11 | } 12 | 13 | /// Attempt to update time zone information from the system. 14 | /// 15 | /// Returns `None` if the call is not known to be sound. 16 | #[inline] 17 | pub(crate) fn refresh_tz() -> Option<()> { 18 | imp::refresh_tz() 19 | } 20 | -------------------------------------------------------------------------------- /.github/workflows/github-release.yaml: -------------------------------------------------------------------------------- 1 | name: GitHub release 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | prerelease: 7 | required: false 8 | type: boolean 9 | 10 | jobs: 11 | release: 12 | name: Create release 13 | runs-on: ubuntu-latest 14 | permissions: 15 | contents: write 16 | steps: 17 | - name: Checkout code 18 | uses: actions/checkout@v5 19 | 20 | - name: Create release 21 | uses: ncipollo/release-action@v1 22 | with: 23 | body: See the [changelog](https://github.com/time-rs/time/blob/main/CHANGELOG.md) for details. 24 | prerelease: ${{ inputs.prerelease }} 25 | -------------------------------------------------------------------------------- /time/src/hint.rs: -------------------------------------------------------------------------------- 1 | //! Hints to the compiler that affects how code should be emitted or optimized. 2 | 3 | /// Indicate that a given branch is **not** likely to be taken, relatively speaking. 4 | #[inline(always)] 5 | #[cold] 6 | pub(crate) const fn cold_path() {} 7 | 8 | /// Indicate that a given condition is likely to be true. 9 | #[inline(always)] 10 | pub(crate) const fn likely(b: bool) -> bool { 11 | if !b { 12 | cold_path(); 13 | } 14 | b 15 | } 16 | 17 | /// Indicate that a given condition is likely to be false. 18 | #[inline(always)] 19 | pub(crate) const fn unlikely(b: bool) -> bool { 20 | if b { 21 | cold_path(); 22 | } 23 | b 24 | } 25 | -------------------------------------------------------------------------------- /time/tests/integration/compile-fail/invalid_date.rs: -------------------------------------------------------------------------------- 1 | use time::macros::date; 2 | 3 | fn main() { 4 | let _ = date!(+1_000_000-01-01); 5 | let _ = date!(10_000-01-01); 6 | let _ = date!(2021-W 60-1); 7 | let _ = date!(2021-W 01-0); 8 | let _ = date!(2021-W 01-8); 9 | let _ = date!(2021-00-01); 10 | let _ = date!(2021-13-01); 11 | let _ = date!(2021-01-00); 12 | let _ = date!(2021-01-32); 13 | let _ = date!(2021-000); 14 | let _ = date!(2021-366); 15 | let _ = date!(0a); 16 | let _ = date!(2021:); 17 | let _ = date!(2021-W 0a); 18 | let _ = date!(2021-W 01:); 19 | let _ = date!(2021-W 01-0a); 20 | let _ = date!(2021-0a); 21 | let _ = date!(2021-01-0a); 22 | } 23 | -------------------------------------------------------------------------------- /time/src/sys/local_offset_at/wasm_js.rs: -------------------------------------------------------------------------------- 1 | use num_conv::prelude::*; 2 | 3 | use crate::convert::*; 4 | use crate::{OffsetDateTime, UtcOffset}; 5 | 6 | /// Obtain the system's UTC offset. 7 | #[inline] 8 | pub(super) fn local_offset_at(datetime: OffsetDateTime) -> Option { 9 | let js_date: js_sys::Date = datetime.into(); 10 | // The number of minutes returned by getTimezoneOffset() is positive if the local time zone 11 | // is behind UTC, and negative if the local time zone is ahead of UTC. For example, 12 | // for UTC+10, -600 will be returned. 13 | let timezone_offset = (js_date.get_timezone_offset() as i32) * -Minute::per_t::(Hour); 14 | 15 | UtcOffset::from_whole_seconds(timezone_offset).ok() 16 | } 17 | -------------------------------------------------------------------------------- /time/benchmarks/util.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, Bencher}; 2 | use time::util; 3 | 4 | setup_benchmark! { 5 | "Utils", 6 | 7 | fn is_leap_year(ben: &mut Bencher<'_>) { 8 | ben.iter(|| { 9 | for year in 0..400 { 10 | black_box(util::is_leap_year(year)); 11 | } 12 | }); 13 | } 14 | 15 | fn days_in_year(ben: &mut Bencher<'_>) { 16 | ben.iter(|| { 17 | for year in 0..400 { 18 | black_box(util::days_in_year(year)); 19 | } 20 | }); 21 | } 22 | 23 | fn weeks_in_year(ben: &mut Bencher<'_>) { 24 | ben.iter(|| { 25 | for year in 0..400 { 26 | black_box(util::weeks_in_year(year)); 27 | } 28 | }); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /time-core/src/hint.rs: -------------------------------------------------------------------------------- 1 | //! Hints to the compiler that affects how code should be emitted or optimized. 2 | 3 | #![expect( 4 | dead_code, 5 | reason = "may be used in the future and has minimal overhead" 6 | )] 7 | 8 | /// Indicate that a given branch is **not** likely to be taken, relatively speaking. 9 | #[inline(always)] 10 | #[cold] 11 | pub(crate) const fn cold_path() {} 12 | 13 | /// Indicate that a given condition is likely to be true. 14 | #[inline(always)] 15 | pub(crate) const fn likely(b: bool) -> bool { 16 | if !b { 17 | cold_path(); 18 | } 19 | b 20 | } 21 | 22 | /// Indicate that a given condition is likely to be false. 23 | #[inline(always)] 24 | pub(crate) const fn unlikely(b: bool) -> bool { 25 | if b { 26 | cold_path(); 27 | } 28 | b 29 | } 30 | -------------------------------------------------------------------------------- /time/tests/integration/compile-fail/invalid_datetime.stderr: -------------------------------------------------------------------------------- 1 | error: invalid component: ordinal was 0 2 | --> $WORKSPACE/tests/compile-fail/invalid_datetime.rs 3 | | 4 | | let _ = datetime!(2021-000 0:00); 5 | | ^^^ 6 | 7 | error: invalid component: hour was 24 8 | --> $WORKSPACE/tests/compile-fail/invalid_datetime.rs 9 | | 10 | | let _ = datetime!(2021-001 24:00); 11 | | ^^ 12 | 13 | error: unexpected token: 0 14 | --> $WORKSPACE/tests/compile-fail/invalid_datetime.rs 15 | | 16 | | let _ = datetime!(2021-001 0:00 0); 17 | | ^ 18 | 19 | error: unexpected token: x 20 | --> $WORKSPACE/tests/compile-fail/invalid_datetime.rs 21 | | 22 | | let _ = datetime!(2021-001 0:00 UTC x); 23 | | ^ 24 | -------------------------------------------------------------------------------- /time-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | lints.workspace = true 2 | 3 | [package] 4 | name = "time-macros" 5 | version = "0.2.24" 6 | categories = ["date-and-time"] 7 | description = """ 8 | Procedural macros for the time crate. 9 | This crate is an implementation detail and should not be relied upon directly. 10 | """ 11 | 12 | authors.workspace = true 13 | edition.workspace = true 14 | keywords.workspace = true 15 | license.workspace = true 16 | repository.workspace = true 17 | rust-version.workspace = true 18 | 19 | [package.metadata.docs.rs] 20 | all-features = true 21 | targets = ["x86_64-unknown-linux-gnu"] 22 | rustdoc-args = ["--generate-link-to-definition"] 23 | 24 | [features] 25 | formatting = [] 26 | large-dates = [] 27 | parsing = [] 28 | serde = [] 29 | 30 | [lib] 31 | proc-macro = true 32 | 33 | [dependencies] 34 | time-core.workspace = true 35 | num-conv.workspace = true 36 | -------------------------------------------------------------------------------- /time/benchmarks/rand08.rs: -------------------------------------------------------------------------------- 1 | use criterion::Bencher; 2 | use rand08::rngs::mock::StepRng; 3 | use rand08::Rng; 4 | use time::{Date, Duration, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset, Weekday}; 5 | 6 | macro_rules! bench_rand { 7 | ($($name:ident : $type:ty),* $(,)?) => { 8 | setup_benchmark! { 9 | "Random", 10 | $(fn $name(ben: &mut Bencher<'_>) { 11 | iter_batched_ref!( 12 | ben, 13 | || StepRng::new(0, 1), 14 | [|rng| rng.r#gen::<$type>()] 15 | ); 16 | })* 17 | } 18 | } 19 | } 20 | 21 | bench_rand![ 22 | time: Time, 23 | date: Date, 24 | utc_offset: UtcOffset, 25 | primitive_date_time: PrimitiveDateTime, 26 | offset_date_time: OffsetDateTime, 27 | duration: Duration, 28 | weekday: Weekday, 29 | month: Month, 30 | ]; 31 | -------------------------------------------------------------------------------- /time/src/sys/local_offset_at/mod.rs: -------------------------------------------------------------------------------- 1 | //! A method to obtain the local offset from UTC. 2 | 3 | #![allow( 4 | clippy::missing_const_for_fn, 5 | reason = "system APIs are inherently not const, so this will only trigger on the fallback" 6 | )] 7 | 8 | #[cfg_attr(target_family = "windows", path = "windows.rs")] 9 | #[cfg_attr(target_family = "unix", path = "unix.rs")] 10 | #[cfg_attr( 11 | all( 12 | target_family = "wasm", 13 | not(any(target_os = "emscripten", target_os = "wasi")), 14 | feature = "wasm-bindgen" 15 | ), 16 | path = "wasm_js.rs" 17 | )] 18 | mod imp; 19 | 20 | use crate::{OffsetDateTime, UtcOffset}; 21 | 22 | /// Attempt to obtain the system's UTC offset. If the offset cannot be determined, `None` is 23 | /// returned. 24 | #[inline] 25 | pub(crate) fn local_offset_at(datetime: OffsetDateTime) -> Option { 26 | imp::local_offset_at(datetime) 27 | } 28 | -------------------------------------------------------------------------------- /time/src/ext/digit_count.rs: -------------------------------------------------------------------------------- 1 | use num_conv::prelude::*; 2 | 3 | /// A trait that indicates the formatted width of the value can be determined. 4 | /// 5 | /// Note that this should not be implemented for any signed integers. This forces the caller to 6 | /// write the sign if desired. 7 | pub(crate) trait DigitCount { 8 | /// The number of digits in the stringified value. 9 | fn num_digits(self) -> u8; 10 | } 11 | 12 | /// A macro to generate implementations of `DigitCount` for unsigned integers. 13 | macro_rules! impl_digit_count { 14 | ($($t:ty),* $(,)?) => { 15 | $(impl DigitCount for $t { 16 | #[inline] 17 | fn num_digits(self) -> u8 { 18 | match self.checked_ilog10() { 19 | Some(n) => n.truncate::() + 1, 20 | None => 1, 21 | } 22 | } 23 | })* 24 | }; 25 | } 26 | 27 | impl_digit_count!(u8, u16, u32); 28 | -------------------------------------------------------------------------------- /time/src/interop/js_sys_date_utcdatetime.rs: -------------------------------------------------------------------------------- 1 | use crate::convert::*; 2 | use crate::UtcDateTime; 3 | 4 | impl From for UtcDateTime { 5 | /// # Panics 6 | /// 7 | /// This may panic if the timestamp can not be represented. 8 | #[track_caller] 9 | fn from(js_date: js_sys::Date) -> Self { 10 | // get_time() returns milliseconds 11 | let timestamp_nanos = (js_date.get_time() * Nanosecond::per_t::(Millisecond)) as i128; 12 | Self::from_unix_timestamp_nanos(timestamp_nanos) 13 | .expect("invalid timestamp: Timestamp cannot fit in range") 14 | } 15 | } 16 | 17 | impl From for js_sys::Date { 18 | fn from(datetime: UtcDateTime) -> Self { 19 | // new Date() takes milliseconds 20 | let timestamp = 21 | (datetime.unix_timestamp_nanos() / Nanosecond::per_t::(Millisecond)) as f64; 22 | Self::new(×tamp.into()) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /time/src/interop/js_sys_date_offsetdatetime.rs: -------------------------------------------------------------------------------- 1 | use crate::convert::*; 2 | use crate::OffsetDateTime; 3 | 4 | impl From for OffsetDateTime { 5 | /// # Panics 6 | /// 7 | /// This may panic if the timestamp can not be represented. 8 | #[track_caller] 9 | fn from(js_date: js_sys::Date) -> Self { 10 | // get_time() returns milliseconds 11 | let timestamp_nanos = js_date.get_time() as i128 * Nanosecond::per_t::(Millisecond); 12 | Self::from_unix_timestamp_nanos(timestamp_nanos) 13 | .expect("invalid timestamp: Timestamp cannot fit in range") 14 | } 15 | } 16 | 17 | impl From for js_sys::Date { 18 | fn from(datetime: OffsetDateTime) -> Self { 19 | // new Date() takes milliseconds 20 | let timestamp = 21 | (datetime.unix_timestamp_nanos() / Nanosecond::per_t::(Millisecond)) as f64; 22 | Self::new(×tamp.into()) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /time-macros/src/utc_datetime.rs: -------------------------------------------------------------------------------- 1 | use std::iter::Peekable; 2 | 3 | use proc_macro::{token_stream, TokenStream}; 4 | 5 | use crate::date::Date; 6 | use crate::error::Error; 7 | use crate::time::Time; 8 | use crate::to_tokens::ToTokenStream; 9 | use crate::{date, time}; 10 | 11 | pub(crate) struct UtcDateTime { 12 | date: Date, 13 | time: Time, 14 | } 15 | 16 | pub(crate) fn parse(chars: &mut Peekable) -> Result { 17 | let date = date::parse(chars)?; 18 | let time = time::parse(chars)?; 19 | 20 | if let Some(token) = chars.peek() { 21 | return Err(Error::UnexpectedToken { 22 | tree: token.clone(), 23 | }); 24 | } 25 | 26 | Ok(UtcDateTime { date, time }) 27 | } 28 | 29 | impl ToTokenStream for UtcDateTime { 30 | fn append_to(self, ts: &mut TokenStream) { 31 | quote_append! { ts 32 | ::time::UtcDateTime::new( 33 | #S(self.date), 34 | #S(self.time), 35 | ) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /time/src/error/invalid_variant.rs: -------------------------------------------------------------------------------- 1 | //! Invalid variant error 2 | 3 | use core::fmt; 4 | 5 | /// An error type indicating that a [`FromStr`](core::str::FromStr) call failed because the value 6 | /// was not a valid variant. 7 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 8 | pub struct InvalidVariant; 9 | 10 | impl fmt::Display for InvalidVariant { 11 | #[inline] 12 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 13 | write!(f, "value was not a valid variant") 14 | } 15 | } 16 | 17 | impl core::error::Error for InvalidVariant {} 18 | 19 | impl From for crate::Error { 20 | #[inline] 21 | fn from(err: InvalidVariant) -> Self { 22 | Self::InvalidVariant(err) 23 | } 24 | } 25 | 26 | impl TryFrom for InvalidVariant { 27 | type Error = crate::error::DifferentVariant; 28 | 29 | #[inline] 30 | fn try_from(err: crate::Error) -> Result { 31 | match err { 32 | crate::Error::InvalidVariant(err) => Ok(err), 33 | _ => Err(crate::error::DifferentVariant), 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /time/src/error/different_variant.rs: -------------------------------------------------------------------------------- 1 | //! Different variant error 2 | 3 | use core::fmt; 4 | 5 | /// An error type indicating that a [`TryFrom`](core::convert::TryFrom) call failed because the 6 | /// original value was of a different variant. 7 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 8 | pub struct DifferentVariant; 9 | 10 | impl fmt::Display for DifferentVariant { 11 | #[inline] 12 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 13 | write!(f, "value was of a different variant than required") 14 | } 15 | } 16 | 17 | impl core::error::Error for DifferentVariant {} 18 | 19 | impl From for crate::Error { 20 | #[inline] 21 | fn from(err: DifferentVariant) -> Self { 22 | Self::DifferentVariant(err) 23 | } 24 | } 25 | 26 | impl TryFrom for DifferentVariant { 27 | type Error = Self; 28 | 29 | #[inline] 30 | fn try_from(err: crate::Error) -> Result { 31 | match err { 32 | crate::Error::DifferentVariant(err) => Ok(err), 33 | _ => Err(Self), 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /time/tests/integration/compile-fail/invalid_utc_datetime.stderr: -------------------------------------------------------------------------------- 1 | error: invalid component: ordinal was 0 2 | --> $WORKSPACE/tests/compile-fail/invalid_utc_datetime.rs 3 | | 4 | | let _ = utc_datetime!(2021-000 0:00); 5 | | ^^^ 6 | 7 | error: invalid component: hour was 24 8 | --> $WORKSPACE/tests/compile-fail/invalid_utc_datetime.rs 9 | | 10 | | let _ = utc_datetime!(2021-001 24:00); 11 | | ^^ 12 | 13 | error: unexpected token: 0 14 | --> $WORKSPACE/tests/compile-fail/invalid_utc_datetime.rs 15 | | 16 | | let _ = utc_datetime!(2021-001 0:00 0); 17 | | ^ 18 | 19 | error: unexpected token: UTC 20 | --> $WORKSPACE/tests/compile-fail/invalid_utc_datetime.rs 21 | | 22 | | let _ = utc_datetime!(2021-001 0:00 UTC); 23 | | ^^^ 24 | 25 | error: unexpected token: UTC 26 | --> $WORKSPACE/tests/compile-fail/invalid_utc_datetime.rs 27 | | 28 | | let _ = utc_datetime!(2021-001 0:00 UTC x); 29 | | ^^^ 30 | -------------------------------------------------------------------------------- /logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /time/tests/integration/compile-fail/invalid_serializer.rs: -------------------------------------------------------------------------------- 1 | use time::serde; 2 | 3 | serde::format_description!(); // unexpected end of input 4 | serde::format_description!("bad string", OffsetDateTime, "[year] [month]"); // module name is not ident 5 | serde::format_description!(my_format: OffsetDateTime, "[year] [month]"); // not a comma 6 | serde::format_description!(my_format,); // missing formattable and string 7 | serde::format_description!(my_format, "[year] [month]"); // missing formattable 8 | serde::format_description!(OffsetDateTime, "[year] [month]"); // missing ident 9 | serde::format_description!(my_format, OffsetDateTime); // missing string format 10 | serde::format_description!(my_format, OffsetDateTime,); // missing string format 11 | serde::format_description!(my_format, OffsetDateTime "[year] [month]"); // missing comma 12 | serde::format_description!(my_format, OffsetDateTime : "[year] [month]"); // not a comma 13 | serde::format_description!(my_format, OffsetDateTime, "[bad]"); // bad component name 14 | serde::format_description!(my_format, OffsetDateTime, not_string); // not in scope 15 | 16 | fn main() {} 17 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) Jacob Pratt et al. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /time/src/error/indeterminate_offset.rs: -------------------------------------------------------------------------------- 1 | //! Indeterminate offset 2 | 3 | use core::fmt; 4 | 5 | use crate::error; 6 | 7 | /// The system's UTC offset could not be determined at the given datetime. 8 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 9 | pub struct IndeterminateOffset; 10 | 11 | impl fmt::Display for IndeterminateOffset { 12 | #[inline] 13 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 14 | f.write_str("The system's UTC offset could not be determined") 15 | } 16 | } 17 | 18 | impl core::error::Error for IndeterminateOffset {} 19 | 20 | impl From for crate::Error { 21 | #[inline] 22 | fn from(err: IndeterminateOffset) -> Self { 23 | Self::IndeterminateOffset(err) 24 | } 25 | } 26 | 27 | impl TryFrom for IndeterminateOffset { 28 | type Error = error::DifferentVariant; 29 | 30 | #[inline] 31 | fn try_from(err: crate::Error) -> Result { 32 | match err { 33 | crate::Error::IndeterminateOffset(err) => Ok(err), 34 | _ => Err(error::DifferentVariant), 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /time/src/error/conversion_range.rs: -------------------------------------------------------------------------------- 1 | //! Conversion range error 2 | 3 | use core::fmt; 4 | 5 | use crate::error; 6 | 7 | /// An error type indicating that a conversion failed because the target type could not store the 8 | /// initial value. 9 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 10 | pub struct ConversionRange; 11 | 12 | impl fmt::Display for ConversionRange { 13 | #[inline] 14 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 15 | f.write_str("Source value is out of range for the target type") 16 | } 17 | } 18 | 19 | impl core::error::Error for ConversionRange {} 20 | 21 | impl From for crate::Error { 22 | #[inline] 23 | fn from(err: ConversionRange) -> Self { 24 | Self::ConversionRange(err) 25 | } 26 | } 27 | 28 | impl TryFrom for ConversionRange { 29 | type Error = error::DifferentVariant; 30 | 31 | #[inline] 32 | fn try_from(err: crate::Error) -> Result { 33 | match err { 34 | crate::Error::ConversionRange(err) => Ok(err), 35 | _ => Err(error::DifferentVariant), 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /time/src/format_description/well_known/rfc3339.rs: -------------------------------------------------------------------------------- 1 | //! The format described in RFC 3339. 2 | 3 | /// The format described in [RFC 3339](https://tools.ietf.org/html/rfc3339#section-5.6). 4 | /// 5 | /// Format example: 1985-04-12T23:20:50.52Z 6 | /// 7 | /// # Examples 8 | #[cfg_attr(feature = "parsing", doc = "```rust")] 9 | #[cfg_attr(not(feature = "parsing"), doc = "```rust,ignore")] 10 | /// # use time::{format_description::well_known::Rfc3339, OffsetDateTime}; 11 | /// # use time_macros::datetime; 12 | /// assert_eq!( 13 | /// OffsetDateTime::parse("1985-04-12T23:20:50.52Z", &Rfc3339)?, 14 | /// datetime!(1985-04-12 23:20:50.52 +00:00) 15 | /// ); 16 | /// # Ok::<_, time::Error>(()) 17 | /// ``` 18 | /// 19 | #[cfg_attr(feature = "formatting", doc = "```rust")] 20 | #[cfg_attr(not(feature = "formatting"), doc = "```rust,ignore")] 21 | /// # use time::format_description::well_known::Rfc3339; 22 | /// # use time_macros::datetime; 23 | /// assert_eq!( 24 | /// datetime!(1985-04-12 23:20:50.52 +00:00).format(&Rfc3339)?, 25 | /// "1985-04-12T23:20:50.52Z" 26 | /// ); 27 | /// # Ok::<_, time::Error>(()) 28 | /// ``` 29 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 30 | pub struct Rfc3339; 31 | -------------------------------------------------------------------------------- /time/src/format_description/well_known/rfc2822.rs: -------------------------------------------------------------------------------- 1 | //! The format described in RFC 2822. 2 | 3 | /// The format described in [RFC 2822](https://tools.ietf.org/html/rfc2822#section-3.3). 4 | /// 5 | /// Example: Fri, 21 Nov 1997 09:55:06 -0600 6 | /// 7 | /// # Examples 8 | #[cfg_attr(feature = "parsing", doc = "```rust")] 9 | #[cfg_attr(not(feature = "parsing"), doc = "```rust,ignore")] 10 | /// # use time::{format_description::well_known::Rfc2822, OffsetDateTime}; 11 | /// use time_macros::datetime; 12 | /// assert_eq!( 13 | /// OffsetDateTime::parse("Sat, 12 Jun 1993 13:25:19 GMT", &Rfc2822)?, 14 | /// datetime!(1993-06-12 13:25:19 +00:00) 15 | /// ); 16 | /// # Ok::<_, time::Error>(()) 17 | /// ``` 18 | /// 19 | #[cfg_attr(feature = "formatting", doc = "```rust")] 20 | #[cfg_attr(not(feature = "formatting"), doc = "```rust,ignore")] 21 | /// # use time::format_description::well_known::Rfc2822; 22 | /// # use time_macros::datetime; 23 | /// assert_eq!( 24 | /// datetime!(1997-11-21 09:55:06 -06:00).format(&Rfc2822)?, 25 | /// "Fri, 21 Nov 1997 09:55:06 -0600" 26 | /// ); 27 | /// # Ok::<_, time::Error>(()) 28 | /// ``` 29 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 30 | pub struct Rfc2822; 31 | -------------------------------------------------------------------------------- /time/src/interop/mod.rs: -------------------------------------------------------------------------------- 1 | //! Comparison, arithmetic, and conversion between various types in `time` and the standard library. 2 | //! 3 | //! Currently, full interoperability is present between [`OffsetDateTime`](crate::OffsetDateTime), 4 | //! [`UtcDateTime`](crate::UtcDateTime), and [`SystemTime`](std::time::SystemTime). Partial 5 | //! interoperability is present with [`js_sys::Date`]. Note that 6 | //! [`PrimitiveDateTime`](crate::PrimitiveDateTime) is not interoperable with any of these types due 7 | //! to the lack of an associated UTC offset. 8 | 9 | // Module names should have the two types sorted in alphabetical order. This avoids any question 10 | // of which type should be the "primary" type in the module name. 11 | 12 | #[cfg(all( 13 | target_family = "wasm", 14 | not(any(target_os = "emscripten", target_os = "wasi")), 15 | feature = "wasm-bindgen" 16 | ))] 17 | mod js_sys_date_offsetdatetime; 18 | #[cfg(all( 19 | target_family = "wasm", 20 | not(any(target_os = "emscripten", target_os = "wasi")), 21 | feature = "wasm-bindgen" 22 | ))] 23 | mod js_sys_date_utcdatetime; 24 | #[cfg(feature = "std")] 25 | mod offsetdatetime_systemtime; 26 | mod offsetdatetime_utcdatetime; 27 | #[cfg(feature = "std")] 28 | mod utcdatetime_systemtime; 29 | -------------------------------------------------------------------------------- /time-macros/src/format_description/public/component.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::{Ident, Span, TokenStream}; 2 | 3 | use super::modifier; 4 | use crate::to_tokens::ToTokenStream; 5 | 6 | macro_rules! declare_component { 7 | ($($name:ident)*) => { 8 | pub(crate) enum Component {$( 9 | $name(modifier::$name), 10 | )*} 11 | 12 | impl ToTokenStream for Component { 13 | fn append_to(self, ts: &mut TokenStream) { 14 | let mut mts = TokenStream::new(); 15 | 16 | let component = match self {$( 17 | Self::$name(modifier) => { 18 | modifier.append_to(&mut mts); 19 | stringify!($name) 20 | } 21 | )*}; 22 | let component = Ident::new(component, Span::mixed_site()); 23 | 24 | quote_append! { ts 25 | Component::#(component)(#S(mts)) 26 | } 27 | } 28 | } 29 | }; 30 | } 31 | 32 | declare_component! { 33 | Day 34 | Month 35 | Ordinal 36 | Weekday 37 | WeekNumber 38 | Year 39 | Hour 40 | Minute 41 | Period 42 | Second 43 | Subsecond 44 | OffsetHour 45 | OffsetMinute 46 | OffsetSecond 47 | Ignore 48 | UnixTimestamp 49 | End 50 | } 51 | -------------------------------------------------------------------------------- /time-macros/src/datetime.rs: -------------------------------------------------------------------------------- 1 | use std::iter::Peekable; 2 | 3 | use proc_macro::{token_stream, TokenStream}; 4 | 5 | use crate::date::Date; 6 | use crate::error::Error; 7 | use crate::offset::Offset; 8 | use crate::time::Time; 9 | use crate::to_tokens::ToTokenStream; 10 | use crate::{date, offset, time}; 11 | 12 | pub(crate) struct DateTime { 13 | date: Date, 14 | time: Time, 15 | offset: Option, 16 | } 17 | 18 | pub(crate) fn parse(chars: &mut Peekable) -> Result { 19 | let date = date::parse(chars)?; 20 | let time = time::parse(chars)?; 21 | let offset = match offset::parse(chars) { 22 | Ok(offset) => Some(offset), 23 | Err(Error::UnexpectedEndOfInput | Error::MissingComponent { name: "sign", .. }) => None, 24 | Err(err) => return Err(err), 25 | }; 26 | 27 | if let Some(token) = chars.peek() { 28 | return Err(Error::UnexpectedToken { 29 | tree: token.clone(), 30 | }); 31 | } 32 | 33 | Ok(DateTime { date, time, offset }) 34 | } 35 | 36 | impl ToTokenStream for DateTime { 37 | fn append_to(self, ts: &mut TokenStream) { 38 | let maybe_offset = match self.offset { 39 | Some(offset) => quote_! { .assume_offset(#S(offset)) }, 40 | None => quote_! {}, 41 | }; 42 | 43 | quote_append! { ts 44 | ::time::PrimitiveDateTime::new( 45 | #S(self.date), 46 | #S(self.time), 47 | ) #S(maybe_offset) 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /time/tests/integration/compile-fail/invalid_offset.stderr: -------------------------------------------------------------------------------- 1 | error: invalid component: hour was 26 2 | --> $WORKSPACE/tests/compile-fail/invalid_offset.rs 3 | | 4 | | let _ = offset!(+26); 5 | | ^^ 6 | 7 | error: invalid component: minute was 60 8 | --> $WORKSPACE/tests/compile-fail/invalid_offset.rs 9 | | 10 | | let _ = offset!(+0:60); 11 | | ^^ 12 | 13 | error: invalid component: second was 60 14 | --> $WORKSPACE/tests/compile-fail/invalid_offset.rs 15 | | 16 | | let _ = offset!(+0:00:60); 17 | | ^^ 18 | 19 | error: unexpected token: 0 20 | --> $WORKSPACE/tests/compile-fail/invalid_offset.rs 21 | | 22 | | let _ = offset!(0); 23 | | ^ 24 | 25 | error: missing component: sign 26 | --> $WORKSPACE/tests/compile-fail/invalid_offset.rs 27 | | 28 | | let _ = offset!(); 29 | | ^^^^^^^^^ 30 | | 31 | = note: this error originates in the macro `offset` (in Nightly builds, run with -Z macro-backtrace for more info) 32 | 33 | error: invalid component: hour was 0a 34 | --> $WORKSPACE/tests/compile-fail/invalid_offset.rs 35 | | 36 | | let _ = offset!(+0a); 37 | | ^^ 38 | 39 | error: invalid component: minute was 0a 40 | --> $WORKSPACE/tests/compile-fail/invalid_offset.rs 41 | | 42 | | let _ = offset!(+0:0a); 43 | | ^^ 44 | 45 | error: invalid component: second was 0a 46 | --> $WORKSPACE/tests/compile-fail/invalid_offset.rs 47 | | 48 | | let _ = offset!(+0:00:0a); 49 | | ^^ 50 | -------------------------------------------------------------------------------- /time/benchmarks/rand09.rs: -------------------------------------------------------------------------------- 1 | use criterion::Bencher; 2 | use rand09::Rng; 3 | use time::{Date, Duration, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset, Weekday}; 4 | 5 | macro_rules! bench_rand { 6 | ($($name:ident : $type:ty),* $(,)?) => { 7 | setup_benchmark! { 8 | "Random", 9 | $(fn $name(ben: &mut Bencher<'_>) { 10 | iter_batched_ref!( 11 | ben, 12 | || StepRng::new(0, 1), 13 | [|rng| rng.random::<$type>()] 14 | ); 15 | })* 16 | } 17 | } 18 | } 19 | 20 | bench_rand![ 21 | time: Time, 22 | date: Date, 23 | utc_offset: UtcOffset, 24 | primitive_date_time: PrimitiveDateTime, 25 | offset_date_time: OffsetDateTime, 26 | duration: Duration, 27 | weekday: Weekday, 28 | month: Month, 29 | ]; 30 | 31 | // copy of `StepRng` from rand 0.8 to avoid deprecation warnings 32 | #[derive(Debug, Clone)] 33 | struct StepRng { 34 | v: u64, 35 | a: u64, 36 | } 37 | 38 | impl StepRng { 39 | const fn new(initial: u64, increment: u64) -> Self { 40 | Self { 41 | v: initial, 42 | a: increment, 43 | } 44 | } 45 | } 46 | 47 | impl rand09::RngCore for StepRng { 48 | fn next_u32(&mut self) -> u32 { 49 | self.next_u64() as u32 50 | } 51 | 52 | fn next_u64(&mut self) -> u64 { 53 | let res = self.v; 54 | self.v = self.v.wrapping_add(self.a); 55 | res 56 | } 57 | 58 | fn fill_bytes(&mut self, dst: &mut [u8]) { 59 | rand09::rand_core::impls::fill_bytes_via_next(self, dst) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /time/src/format_description/mod.rs: -------------------------------------------------------------------------------- 1 | //! Description of how types should be formatted and parsed. 2 | //! 3 | //! The formatted value will be output to the provided writer. Format descriptions can be 4 | //! [well-known](crate::format_description::well_known) or obtained by using the 5 | //! [`format_description!`](crate::macros::format_description) macro or a function listed below. 6 | //! 7 | //! For examples, see the implementors of [Formattable](crate::formatting::Formattable), 8 | //! e.g. [`well_known::Rfc3339`]. 9 | 10 | mod borrowed_format_item; 11 | mod component; 12 | pub mod modifier; 13 | #[cfg(feature = "alloc")] 14 | mod owned_format_item; 15 | #[cfg(feature = "alloc")] 16 | mod parse; 17 | 18 | pub use borrowed_format_item::BorrowedFormatItem; 19 | #[doc(hidden)] 20 | #[deprecated(since = "0.3.37", note = "use `BorrowedFormatItem` for clarity")] 21 | pub use borrowed_format_item::BorrowedFormatItem as FormatItem; 22 | #[cfg(feature = "alloc")] 23 | pub use owned_format_item::OwnedFormatItem; 24 | 25 | pub use self::component::Component; 26 | #[cfg(feature = "alloc")] 27 | pub use self::parse::{ 28 | parse, parse_borrowed, parse_owned, parse_strftime_borrowed, parse_strftime_owned, 29 | }; 30 | 31 | /// The type output by the [`format_description!`](crate::macros::format_description) macro. 32 | pub type StaticFormatDescription = &'static [BorrowedFormatItem<'static>]; 33 | 34 | /// Well-known formats, typically standards. 35 | pub mod well_known { 36 | pub mod iso8601; 37 | mod rfc2822; 38 | mod rfc3339; 39 | 40 | #[doc(inline)] 41 | pub use iso8601::Iso8601; 42 | pub use rfc2822::Rfc2822; 43 | pub use rfc3339::Rfc3339; 44 | } 45 | -------------------------------------------------------------------------------- /time/src/parsing/shim.rs: -------------------------------------------------------------------------------- 1 | //! Extension traits for things either not implemented or not yet stable in the MSRV. 2 | 3 | /// Equivalent of `foo.parse()` for slices. 4 | pub(crate) trait IntegerParseBytes { 5 | #[allow(clippy::missing_docs_in_private_items)] 6 | fn parse_bytes(&self) -> Option; 7 | } 8 | 9 | impl IntegerParseBytes for [u8] { 10 | #[inline] 11 | fn parse_bytes(&self) -> Option { 12 | T::parse_bytes(self) 13 | } 14 | } 15 | 16 | /// Marker trait for all integer types, including `NonZero*` 17 | pub(crate) trait Integer: Sized { 18 | #[allow(clippy::missing_docs_in_private_items)] 19 | fn parse_bytes(src: &[u8]) -> Option; 20 | } 21 | 22 | /// Parse the given types from bytes. 23 | macro_rules! impl_parse_bytes { 24 | ($($t:ty)*) => ($( 25 | impl Integer for $t { 26 | #[allow(trivial_numeric_casts)] 27 | #[inline] 28 | fn parse_bytes(src: &[u8]) -> Option { 29 | src.iter().try_fold::(0, |result, c| { 30 | result.checked_mul(10)?.checked_add((c - b'0') as Self) 31 | }) 32 | } 33 | } 34 | )*) 35 | } 36 | impl_parse_bytes! { u8 u16 u32 u128 } 37 | 38 | /// Parse the given types from bytes. 39 | macro_rules! impl_parse_bytes_nonzero { 40 | ($($t:ty)*) => {$( 41 | impl Integer for $t { 42 | #[inline] 43 | fn parse_bytes(src: &[u8]) -> Option { 44 | Self::new(src.parse_bytes()?) 45 | } 46 | } 47 | )*} 48 | } 49 | 50 | impl_parse_bytes_nonzero! { 51 | core::num::NonZero 52 | core::num::NonZero 53 | } 54 | -------------------------------------------------------------------------------- /time/tests/integration/serde/rfc2822.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use serde_test::{assert_tokens, Configure, Token}; 3 | use time::serde::rfc2822; 4 | use time::OffsetDateTime; 5 | use time_macros::datetime; 6 | 7 | #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] 8 | struct Test { 9 | #[serde(with = "rfc2822")] 10 | dt: OffsetDateTime, 11 | #[serde(with = "rfc2822::option")] 12 | option_dt: Option, 13 | } 14 | 15 | #[test] 16 | fn serialize_deserialize() { 17 | let value = Test { 18 | dt: datetime!(2000-01-01 00:00:00 UTC), 19 | option_dt: Some(datetime!(2000-01-01 00:00:00 UTC)), 20 | }; 21 | assert_tokens( 22 | &value.compact(), 23 | &[ 24 | Token::Struct { 25 | name: "Test", 26 | len: 2, 27 | }, 28 | Token::Str("dt"), 29 | Token::BorrowedStr("Sat, 01 Jan 2000 00:00:00 +0000"), 30 | Token::Str("option_dt"), 31 | Token::Some, 32 | Token::BorrowedStr("Sat, 01 Jan 2000 00:00:00 +0000"), 33 | Token::StructEnd, 34 | ], 35 | ); 36 | } 37 | 38 | #[test] 39 | fn parse_json() -> serde_json::Result<()> { 40 | #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] 41 | #[serde(untagged)] 42 | enum Wrapper { 43 | A(Test), 44 | } 45 | assert_eq!( 46 | serde_json::from_str::( 47 | r#"{"dt": "Sat, 01 Jan 2000 00:00:00 +0000", "option_dt": null}"# 48 | )?, 49 | Wrapper::A(Test { 50 | dt: datetime!(2000-01-01 00:00:00 UTC), 51 | option_dt: None, 52 | }) 53 | ); 54 | 55 | Ok(()) 56 | } 57 | -------------------------------------------------------------------------------- /time/src/format_description/component.rs: -------------------------------------------------------------------------------- 1 | //! Part of a format description. 2 | 3 | use crate::format_description::modifier; 4 | 5 | /// A component of a larger format description. 6 | #[non_exhaustive] 7 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 8 | pub enum Component { 9 | /// Day of the month. 10 | Day(modifier::Day), 11 | /// Month of the year. 12 | Month(modifier::Month), 13 | /// Ordinal day of the year. 14 | Ordinal(modifier::Ordinal), 15 | /// Day of the week. 16 | Weekday(modifier::Weekday), 17 | /// Week within the year. 18 | WeekNumber(modifier::WeekNumber), 19 | /// Year of the date. 20 | Year(modifier::Year), 21 | /// Hour of the day. 22 | Hour(modifier::Hour), 23 | /// Minute within the hour. 24 | Minute(modifier::Minute), 25 | /// AM/PM part of the time. 26 | Period(modifier::Period), 27 | /// Second within the minute. 28 | Second(modifier::Second), 29 | /// Subsecond within the second. 30 | Subsecond(modifier::Subsecond), 31 | /// Hour of the UTC offset. 32 | OffsetHour(modifier::OffsetHour), 33 | /// Minute within the hour of the UTC offset. 34 | OffsetMinute(modifier::OffsetMinute), 35 | /// Second within the minute of the UTC offset. 36 | OffsetSecond(modifier::OffsetSecond), 37 | /// A number of bytes to ignore when parsing. This has no effect on formatting. 38 | Ignore(modifier::Ignore), 39 | /// A Unix timestamp. 40 | UnixTimestamp(modifier::UnixTimestamp), 41 | /// The end of input. Parsing this component will fail if there is any input remaining. This 42 | /// component neither affects formatting nor consumes any input when parsing. 43 | End(modifier::End), 44 | } 45 | -------------------------------------------------------------------------------- /time/tests/integration/serde/error_conditions.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize, Serializer}; 2 | use serde_test::{assert_ser_tokens_error, Token}; 3 | use time::macros::{datetime, format_description}; 4 | use time::{error, OffsetDateTime}; 5 | 6 | /// Trigger `time::error::Format::StdIo` errors. 7 | /// 8 | /// `StdIo` won't be reached during normal serde operation: it's instantiated 9 | /// only during calls to `format_into()`, but most `Serializable` 10 | /// implementations will only call `format()` because serde `Serializer` 11 | /// interface doesn't expose the underlying `io::Write`. 12 | /// 13 | /// Therefore, we need a contrived serializer to trigger coverage. 14 | fn serialize(datetime: &OffsetDateTime, _serializer: S) -> Result { 15 | Err(datetime 16 | .format_into( 17 | &mut [0u8; 0].as_mut_slice(), 18 | format_description!("nonempty format description"), 19 | ) 20 | .map_err(error::Format::into_invalid_serde_value::) 21 | .expect_err("Writing to a zero-length buffer should always error.")) 22 | } 23 | 24 | #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] 25 | struct TestBadIo { 26 | #[serde(serialize_with = "serialize")] 27 | dt: OffsetDateTime, 28 | } 29 | 30 | #[test] 31 | fn custom_serialize_io_error() { 32 | let value = TestBadIo { 33 | dt: datetime!(2000-01-01 00:00 -4:00), 34 | }; 35 | assert_ser_tokens_error::( 36 | &value, 37 | &[ 38 | Token::Struct { 39 | name: "TestBadIo", 40 | len: 1, 41 | }, 42 | Token::Str("dt"), 43 | ], 44 | "failed to write whole buffer", 45 | ); 46 | } 47 | -------------------------------------------------------------------------------- /time/benchmarks/utc_offset.rs: -------------------------------------------------------------------------------- 1 | use criterion::Bencher; 2 | use time::{OffsetDateTime, UtcOffset}; 3 | 4 | setup_benchmark! { 5 | "UtcOffset", 6 | 7 | fn from_hms(ben: &mut Bencher<'_>) { 8 | ben.iter(|| UtcOffset::from_hms(0, 0, 0)); 9 | } 10 | 11 | fn from_whole_seconds(ben: &mut Bencher<'_>) { 12 | ben.iter(|| UtcOffset::from_whole_seconds(0)); 13 | } 14 | 15 | fn as_hms(ben: &mut Bencher<'_>) { 16 | ben.iter(|| UtcOffset::UTC.as_hms()); 17 | } 18 | 19 | fn whole_hours(ben: &mut Bencher<'_>) { 20 | ben.iter(|| UtcOffset::UTC.whole_hours()); 21 | } 22 | 23 | fn whole_minutes(ben: &mut Bencher<'_>) { 24 | ben.iter(|| UtcOffset::UTC.whole_minutes()); 25 | } 26 | 27 | fn minutes_past_hour(ben: &mut Bencher<'_>) { 28 | ben.iter(|| UtcOffset::UTC.minutes_past_hour()); 29 | } 30 | 31 | fn whole_seconds(ben: &mut Bencher<'_>) { 32 | ben.iter(|| UtcOffset::UTC.whole_seconds()); 33 | } 34 | 35 | fn seconds_past_minute(ben: &mut Bencher<'_>) { 36 | ben.iter(|| UtcOffset::UTC.seconds_past_minute()); 37 | } 38 | 39 | fn is_utc(ben: &mut Bencher<'_>) { 40 | ben.iter(|| UtcOffset::UTC.is_utc()); 41 | } 42 | 43 | fn is_positive(ben: &mut Bencher<'_>) { 44 | ben.iter(|| UtcOffset::UTC.is_positive()); 45 | } 46 | 47 | fn is_negative(ben: &mut Bencher<'_>) { 48 | ben.iter(|| UtcOffset::UTC.is_negative()); 49 | } 50 | 51 | fn local_offset_at(ben: &mut Bencher<'_>) { 52 | ben.iter(|| UtcOffset::local_offset_at(OffsetDateTime::UNIX_EPOCH)); 53 | } 54 | 55 | fn current_local_offset(ben: &mut Bencher<'_>) { 56 | ben.iter(UtcOffset::current_local_offset); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /time-macros/src/format_description/public/mod.rs: -------------------------------------------------------------------------------- 1 | mod component; 2 | pub(super) mod modifier; 3 | 4 | use proc_macro::TokenStream; 5 | 6 | pub(crate) use self::component::Component; 7 | use crate::to_tokens::ToTokenStream; 8 | 9 | pub(crate) enum OwnedFormatItem { 10 | Literal(Box<[u8]>), 11 | Component(Component), 12 | Compound(Box<[Self]>), 13 | Optional(Box), 14 | First(Box<[Self]>), 15 | } 16 | 17 | impl ToTokenStream for OwnedFormatItem { 18 | fn append_to(self, ts: &mut TokenStream) { 19 | match self { 20 | Self::Literal(bytes) => quote_append! { ts 21 | BorrowedFormatItem::Literal(#(Literal::byte_string(bytes.as_ref()))) 22 | }, 23 | Self::Component(component) => quote_append! { ts 24 | BorrowedFormatItem::Component { 0: #S(component) } 25 | }, 26 | Self::Compound(items) => { 27 | let items = items 28 | .into_vec() 29 | .into_iter() 30 | .map(|item| quote_! { #S(item), }) 31 | .collect::(); 32 | quote_append! { ts 33 | BorrowedFormatItem::Compound { 0: &[#S(items)] } 34 | } 35 | } 36 | Self::Optional(item) => quote_append! { ts 37 | BorrowedFormatItem::Optional { 0: &#S(*item) } 38 | }, 39 | Self::First(items) => { 40 | let items = items 41 | .into_vec() 42 | .into_iter() 43 | .map(|item| quote_! { #S(item), }) 44 | .collect::(); 45 | quote_append! { ts 46 | BorrowedFormatItem::First { 0: &[#S(items)] } 47 | } 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /time/src/error/parse_from_description.rs: -------------------------------------------------------------------------------- 1 | //! Error parsing an input into a [`Parsed`](crate::parsing::Parsed) struct 2 | 3 | use core::fmt; 4 | 5 | use crate::error; 6 | 7 | /// An error that occurred while parsing the input into a [`Parsed`](crate::parsing::Parsed) struct. 8 | #[non_exhaustive] 9 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 10 | pub enum ParseFromDescription { 11 | /// A string literal was not what was expected. 12 | #[non_exhaustive] 13 | InvalidLiteral, 14 | /// A dynamic component was not valid. 15 | InvalidComponent(&'static str), 16 | /// The input was expected to have ended, but there are characters that remain. 17 | #[non_exhaustive] 18 | UnexpectedTrailingCharacters, 19 | } 20 | 21 | impl fmt::Display for ParseFromDescription { 22 | #[inline] 23 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 24 | match self { 25 | Self::InvalidLiteral => f.write_str("a character literal was not valid"), 26 | Self::InvalidComponent(name) => { 27 | write!(f, "the '{name}' component could not be parsed") 28 | } 29 | Self::UnexpectedTrailingCharacters => { 30 | f.write_str("unexpected trailing characters; the end of input was expected") 31 | } 32 | } 33 | } 34 | } 35 | 36 | impl core::error::Error for ParseFromDescription {} 37 | 38 | impl From for crate::Error { 39 | #[inline] 40 | fn from(original: ParseFromDescription) -> Self { 41 | Self::ParseFromDescription(original) 42 | } 43 | } 44 | 45 | impl TryFrom for ParseFromDescription { 46 | type Error = error::DifferentVariant; 47 | 48 | #[inline] 49 | fn try_from(err: crate::Error) -> Result { 50 | match err { 51 | crate::Error::ParseFromDescription(err) => Ok(err), 52 | _ => Err(error::DifferentVariant), 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /.clippy.toml: -------------------------------------------------------------------------------- 1 | msrv = "1.83.0" 2 | 3 | missing-docs-in-crate-items = true 4 | disallowed-macros = [ 5 | "std::column", 6 | "std::env", 7 | "std::file", 8 | "std::include_bytes", 9 | "std::include_str", 10 | "std::include", 11 | "std::line", 12 | "std::module_path", 13 | "std::option_env", 14 | ] 15 | disallowed-names = ["alpha", "beta", "gamma", "delta"] 16 | disallowed-types = [ 17 | { path = "core::num::NonZeroU8", reason = "use generic `NonZero`", replacement = "core::num::NonZero" }, 18 | { path = "core::num::NonZeroU16", reason = "use generic `NonZero`", replacement = "core::num::NonZero" }, 19 | { path = "core::num::NonZeroU32", reason = "use generic `NonZero`", replacement = "core::num::NonZero" }, 20 | { path = "core::num::NonZeroU64", reason = "use generic `NonZero`", replacement = "core::num::NonZero" }, 21 | { path = "core::num::NonZeroU128", reason = "use generic `NonZero`", replacement = "core::num::NonZero" }, 22 | { path = "core::num::NonZeroUsize", reason = "use generic `NonZero`", replacement = "core::num::NonZero" }, 23 | { path = "core::num::NonZeroI8", reason = "use generic `NonZero`", replacement = "core::num::NonZero" }, 24 | { path = "core::num::NonZeroI16", reason = "use generic `NonZero`", replacement = "core::num::NonZero" }, 25 | { path = "core::num::NonZeroI32", reason = "use generic `NonZero`", replacement = "core::num::NonZero" }, 26 | { path = "core::num::NonZeroI64", reason = "use generic `NonZero`", replacement = "core::num::NonZero" }, 27 | { path = "core::num::NonZeroI128", reason = "use generic `NonZero`", replacement = "core::num::NonZero" }, 28 | { path = "core::num::NonZeroIsize", reason = "use generic `NonZero`", replacement = "core::num::NonZero" }, 29 | ] 30 | matches-for-let-else = "AllTypes" 31 | enforced-import-renames = [ 32 | { path = "std::time::Duration", rename = "StdDuration" }, 33 | { path = "std::time::Instant", rename = "StdInstant" }, 34 | ] 35 | semicolon-outside-block-ignore-multiline = true 36 | -------------------------------------------------------------------------------- /time/tests/integration/compile-fail/invalid_format_description.rs: -------------------------------------------------------------------------------- 1 | use time::macros::format_description; 2 | 3 | fn main() { 4 | let _ = format_description!(); 5 | let _ = format_description!("[]"); 6 | let _ = format_description!("[foo]"); 7 | let _ = format_description!("["); 8 | let _ = format_description!("[hour foo]"); 9 | let _ = format_description!("" x); 10 | let _ = format_description!(x); 11 | let _ = format_description!(0); 12 | let _ = format_description!({}); 13 | 14 | let _ = format_description!("[ invalid ]"); 15 | let _ = format_description!("["); 16 | let _ = format_description!("[ "); 17 | let _ = format_description!("[]"); 18 | let _ = format_description!("[day sign:mandatory]"); 19 | let _ = format_description!("[day sign:]"); 20 | let _ = format_description!("[day :mandatory]"); 21 | let _ = format_description!("[day sign:mandatory"); 22 | let _ = format_description!("[day padding:invalid]"); 23 | 24 | let _ = format_description!(version); 25 | let _ = format_description!(version ""); 26 | let _ = format_description!(version =); 27 | let _ = format_description!(version = 0); 28 | let _ = format_description!(version = 1); 29 | let _ = format_description!(version = 3); 30 | let _ = format_description!(version = two); 31 | 32 | let _ = format_description!(version = 2, r"\a"); 33 | let _ = format_description!(version = 2, r"\"); 34 | 35 | let _ = format_description!(version = 2, "[year [month]]"); 36 | let _ = format_description!(version = 2, "[optional[]]"); 37 | let _ = format_description!(version = 2, "[first[]]"); 38 | let _ = format_description!(version = 2, "[optional []"); 39 | let _ = format_description!(version = 2, "[first []"); 40 | let _ = format_description!(version = 2, "[optional ["); 41 | let _ = format_description!(version = 2, "[optional [[year"); 42 | let _ = format_description!(version = 2, "[optional "); 43 | 44 | let _ = format_description!("[ignore]"); 45 | let _ = format_description!("[ignore count:0]"); 46 | } 47 | -------------------------------------------------------------------------------- /time/tests/integration/rand.rs: -------------------------------------------------------------------------------- 1 | use rand08::Rng as _; 2 | use rand09::Rng as _; 3 | use time::{Date, Duration, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset, Weekday}; 4 | 5 | #[test] 6 | fn support08() { 7 | // Work around rust-random/rand#1020. 8 | let mut rng = rand08::rngs::mock::StepRng::new(0, 656_175_560); 9 | 10 | for _ in 0..7 { 11 | let _ = rng.r#gen::(); 12 | } 13 | for _ in 0..12 { 14 | let _ = rng.r#gen::(); 15 | } 16 | let _ = rng.r#gen::