From a1c0b5f1ffd2b9e2ede7592b14c2914e357205e0 Mon Sep 17 00:00:00 2001 From: Joshua Liebow-Feeser Date: Tue, 3 Feb 2026 22:58:46 +0000 Subject: [PATCH] [WIP] Cleanup gherrit-pr-id: G0b9216c6738c722e3424be5ee5e085157b5813f9 --- tools/Cargo.lock | 70 + tools/hermes/Cargo.toml | 8 +- tools/hermes/src/desugar.rs | 26 +- tools/hermes/src/docs.rs | 74 + tools/hermes/src/include/__hermes_std.rs | 10 + tools/hermes/src/lib.rs | 1 + tools/hermes/src/main.rs | 1 + tools/hermes/src/orchestration.rs | 34 +- tools/hermes/src/parser.rs | 170 +- tools/hermes/src/pipeline.rs | 37 +- tools/hermes/src/shadow.rs | 88 +- tools/hermes/src/translator.rs | 11 +- .../tests/cases/success/read_shim_success.rs | 2 +- .../tests/cases/success/unused_vars_check.rs | 17 + tools/hermes/tests/integration.rs | 51 +- tools/vendor/env_filter/.cargo-checksum.json | 1 + tools/vendor/env_filter/.cargo_vcs_info.json | 6 + tools/vendor/env_filter/Cargo.lock | 210 + tools/vendor/env_filter/Cargo.toml | 179 + tools/vendor/env_filter/Cargo.toml.orig | 40 + tools/vendor/env_filter/LICENSE-APACHE | 202 + tools/vendor/env_filter/LICENSE-MIT | 19 + tools/vendor/env_filter/README.md | 6 + tools/vendor/env_filter/src/directive.rs | 20 + tools/vendor/env_filter/src/filter.rs | 595 ++ tools/vendor/env_filter/src/filtered_log.rs | 45 + tools/vendor/env_filter/src/lib.rs | 63 + tools/vendor/env_filter/src/op.rs | 42 + tools/vendor/env_filter/src/parser.rs | 521 ++ tools/vendor/env_logger/.cargo-checksum.json | 1 + tools/vendor/env_logger/.cargo_vcs_info.json | 6 + tools/vendor/env_logger/Cargo.lock | 282 + tools/vendor/env_logger/Cargo.toml | 256 + tools/vendor/env_logger/Cargo.toml.orig | 153 + tools/vendor/env_logger/LICENSE-APACHE | 202 + tools/vendor/env_logger/LICENSE-MIT | 19 + tools/vendor/env_logger/README.md | 174 + .../examples/custom_default_format.rs | 39 + .../env_logger/examples/custom_format.rs | 53 + tools/vendor/env_logger/examples/default.rs | 37 + .../env_logger/examples/direct_logger.rs | 47 + .../env_logger/examples/filters_from_code.rs | 17 + tools/vendor/env_logger/examples/in_tests.rs | 53 + .../examples/syslog_friendly_format.rs | 24 + tools/vendor/env_logger/src/fmt/humantime.rs | 134 + tools/vendor/env_logger/src/fmt/kv.rs | 69 + tools/vendor/env_logger/src/fmt/mod.rs | 1003 +++ .../env_logger/src/fmt/writer/buffer.rs | 175 + tools/vendor/env_logger/src/fmt/writer/mod.rs | 189 + .../env_logger/src/fmt/writer/target.rs | 26 + tools/vendor/env_logger/src/lib.rs | 272 + tools/vendor/env_logger/src/logger.rs | 1058 +++ .../tests/init-twice-retains-filter.rs | 39 + tools/vendor/env_logger/tests/log-in-log.rs | 40 + .../vendor/env_logger/tests/log_tls_dtors.rs | 67 + .../vendor/env_logger/tests/regexp_filter.rs | 55 + tools/vendor/jiff-static/.cargo-checksum.json | 1 + tools/vendor/jiff-static/.cargo_vcs_info.json | 6 + tools/vendor/jiff-static/COPYING | 3 + tools/vendor/jiff-static/Cargo.lock | 54 + tools/vendor/jiff-static/Cargo.toml | 68 + tools/vendor/jiff-static/Cargo.toml.orig | 42 + tools/vendor/jiff-static/LICENSE-MIT | 21 + tools/vendor/jiff-static/README.md | 19 + tools/vendor/jiff-static/UNLICENSE | 24 + tools/vendor/jiff-static/src/lib.rs | 462 ++ .../jiff-static/src/shared/crc32/mod.rs | 48 + .../jiff-static/src/shared/crc32/table.rs | 798 ++ .../jiff-static/src/shared/error/itime.rs | 100 + .../jiff-static/src/shared/error/mod.rs | 57 + tools/vendor/jiff-static/src/shared/mod.rs | 500 ++ tools/vendor/jiff-static/src/shared/posix.rs | 3421 ++++++++ tools/vendor/jiff-static/src/shared/tzif.rs | 1433 ++++ .../jiff-static/src/shared/util/array_str.rs | 211 + .../jiff-static/src/shared/util/error.rs | 32 + .../jiff-static/src/shared/util/escape.rs | 125 + .../jiff-static/src/shared/util/itime.rs | 967 +++ .../vendor/jiff-static/src/shared/util/mod.rs | 4 + .../jiff-static/src/shared/util/utf8.rs | 92 + tools/vendor/jiff/.cargo-checksum.json | 1 + tools/vendor/jiff/.cargo_vcs_info.json | 6 + tools/vendor/jiff/CHANGELOG.md | 1400 ++++ tools/vendor/jiff/COMPARE.md | 1278 +++ tools/vendor/jiff/COPYING | 3 + tools/vendor/jiff/Cargo.lock | 953 +++ tools/vendor/jiff/Cargo.toml | 225 + tools/vendor/jiff/Cargo.toml.orig | 293 + tools/vendor/jiff/DESIGN.md | 638 ++ tools/vendor/jiff/LICENSE-MIT | 21 + tools/vendor/jiff/PLATFORM.md | 422 + tools/vendor/jiff/README.md | 194 + tools/vendor/jiff/UNLICENSE | 24 + tools/vendor/jiff/src/civil/date.rs | 4079 +++++++++ tools/vendor/jiff/src/civil/datetime.rs | 4556 ++++++++++ tools/vendor/jiff/src/civil/iso_week_date.rs | 991 +++ tools/vendor/jiff/src/civil/mod.rs | 290 + tools/vendor/jiff/src/civil/time.rs | 3524 ++++++++ tools/vendor/jiff/src/civil/weekday.rs | 858 ++ tools/vendor/jiff/src/duration.rs | 146 + tools/vendor/jiff/src/error/civil.rs | 84 + tools/vendor/jiff/src/error/duration.rs | 36 + tools/vendor/jiff/src/error/fmt/friendly.rs | 82 + tools/vendor/jiff/src/error/fmt/mod.rs | 87 + tools/vendor/jiff/src/error/fmt/offset.rs | 153 + tools/vendor/jiff/src/error/fmt/rfc2822.rs | 231 + tools/vendor/jiff/src/error/fmt/rfc9557.rs | 114 + tools/vendor/jiff/src/error/fmt/strtime.rs | 517 ++ tools/vendor/jiff/src/error/fmt/temporal.rs | 354 + tools/vendor/jiff/src/error/fmt/util.rs | 115 + tools/vendor/jiff/src/error/mod.rs | 906 ++ .../vendor/jiff/src/error/signed_duration.rs | 55 + tools/vendor/jiff/src/error/span.rs | 139 + tools/vendor/jiff/src/error/timestamp.rs | 38 + tools/vendor/jiff/src/error/tz/ambiguous.rs | 49 + .../vendor/jiff/src/error/tz/concatenated.rs | 119 + tools/vendor/jiff/src/error/tz/db.rs | 141 + tools/vendor/jiff/src/error/tz/mod.rs | 8 + tools/vendor/jiff/src/error/tz/offset.rs | 110 + tools/vendor/jiff/src/error/tz/posix.rs | 35 + tools/vendor/jiff/src/error/tz/system.rs | 104 + tools/vendor/jiff/src/error/tz/timezone.rs | 40 + tools/vendor/jiff/src/error/tz/zic.rs | 323 + tools/vendor/jiff/src/error/util.rs | 194 + tools/vendor/jiff/src/error/zoned.rs | 71 + tools/vendor/jiff/src/fmt/buffer.rs | 1353 +++ tools/vendor/jiff/src/fmt/friendly/mod.rs | 712 ++ tools/vendor/jiff/src/fmt/friendly/parser.rs | 1363 +++ .../jiff/src/fmt/friendly/parser_label.rs | 90 + tools/vendor/jiff/src/fmt/friendly/printer.rs | 4223 ++++++++++ tools/vendor/jiff/src/fmt/mod.rs | 470 ++ tools/vendor/jiff/src/fmt/offset.rs | 1069 +++ tools/vendor/jiff/src/fmt/rfc2822.rs | 1999 +++++ tools/vendor/jiff/src/fmt/rfc9557.rs | 996 +++ tools/vendor/jiff/src/fmt/serde.rs | 2179 +++++ tools/vendor/jiff/src/fmt/strtime/format.rs | 1627 ++++ tools/vendor/jiff/src/fmt/strtime/mod.rs | 3645 ++++++++ tools/vendor/jiff/src/fmt/strtime/parse.rs | 2092 +++++ tools/vendor/jiff/src/fmt/temporal/mod.rs | 2550 ++++++ tools/vendor/jiff/src/fmt/temporal/parser.rs | 2725 ++++++ tools/vendor/jiff/src/fmt/temporal/pieces.rs | 1727 ++++ tools/vendor/jiff/src/fmt/temporal/printer.rs | 1768 ++++ tools/vendor/jiff/src/fmt/util.rs | 1009 +++ tools/vendor/jiff/src/lib.rs | 865 ++ tools/vendor/jiff/src/logging.rs | 147 + tools/vendor/jiff/src/now.rs | 110 + tools/vendor/jiff/src/shared/crc32/mod.rs | 46 + tools/vendor/jiff/src/shared/crc32/table.rs | 796 ++ tools/vendor/jiff/src/shared/mod.rs | 529 ++ tools/vendor/jiff/src/shared/posix.rs | 3432 ++++++++ tools/vendor/jiff/src/shared/tzif.rs | 1454 ++++ .../vendor/jiff/src/shared/util/array_str.rs | 209 + tools/vendor/jiff/src/shared/util/itime.rs | 965 +++ tools/vendor/jiff/src/shared/util/mod.rs | 2 + tools/vendor/jiff/src/signed_duration.rs | 3248 ++++++++ tools/vendor/jiff/src/span.rs | 7364 +++++++++++++++++ tools/vendor/jiff/src/timestamp.rs | 3943 +++++++++ tools/vendor/jiff/src/tz/ambiguous.rs | 1277 +++ tools/vendor/jiff/src/tz/concatenated.rs | 1091 +++ .../vendor/jiff/src/tz/db/bundled/disabled.rs | 30 + .../vendor/jiff/src/tz/db/bundled/enabled.rs | 151 + tools/vendor/jiff/src/tz/db/bundled/mod.rs | 20 + .../jiff/src/tz/db/concatenated/disabled.rs | 44 + .../jiff/src/tz/db/concatenated/enabled.rs | 571 ++ .../vendor/jiff/src/tz/db/concatenated/mod.rs | 8 + tools/vendor/jiff/src/tz/db/mod.rs | 826 ++ .../jiff/src/tz/db/zoneinfo/disabled.rs | 44 + .../vendor/jiff/src/tz/db/zoneinfo/enabled.rs | 858 ++ tools/vendor/jiff/src/tz/db/zoneinfo/mod.rs | 8 + tools/vendor/jiff/src/tz/mod.rs | 358 + tools/vendor/jiff/src/tz/offset.rs | 2119 +++++ tools/vendor/jiff/src/tz/posix.rs | 359 + tools/vendor/jiff/src/tz/system/android.rs | 316 + tools/vendor/jiff/src/tz/system/mod.rs | 283 + tools/vendor/jiff/src/tz/system/unix.rs | 117 + tools/vendor/jiff/src/tz/system/wasm_js.rs | 57 + .../vendor/jiff/src/tz/system/windows/mod.rs | 144 + .../src/tz/system/windows/windows_zones.rs | 143 + tools/vendor/jiff/src/tz/testdata.rs | 133 + tools/vendor/jiff/src/tz/timezone.rs | 3908 +++++++++ tools/vendor/jiff/src/tz/tzif.rs | 786 ++ tools/vendor/jiff/src/tz/zic.rs | 2699 ++++++ tools/vendor/jiff/src/util/array_str.rs | 5 + tools/vendor/jiff/src/util/borrow.rs | 106 + tools/vendor/jiff/src/util/c.rs | 57 + tools/vendor/jiff/src/util/cache.rs | 48 + tools/vendor/jiff/src/util/constant.rs | 13 + tools/vendor/jiff/src/util/escape.rs | 124 + tools/vendor/jiff/src/util/fs.rs | 70 + tools/vendor/jiff/src/util/libm.rs | 252 + tools/vendor/jiff/src/util/mod.rs | 21 + tools/vendor/jiff/src/util/parse.rs | 214 + tools/vendor/jiff/src/util/rangeint.rs | 2624 ++++++ tools/vendor/jiff/src/util/round/increment.rs | 153 + tools/vendor/jiff/src/util/round/mod.rs | 2 + tools/vendor/jiff/src/util/round/mode.rs | 401 + tools/vendor/jiff/src/util/sync.rs | 48 + tools/vendor/jiff/src/util/t.rs | 648 ++ tools/vendor/jiff/src/util/utf8.rs | 122 + tools/vendor/jiff/src/zoned.rs | 6108 ++++++++++++++ tools/vendor/jiff/tests/lib.rs | 90 + tools/vendor/log/.cargo-checksum.json | 1 + tools/vendor/log/.cargo_vcs_info.json | 6 + tools/vendor/log/.github/workflows/main.yml | 117 + tools/vendor/log/CHANGELOG.md | 423 + tools/vendor/log/Cargo.lock | 283 + tools/vendor/log/Cargo.toml | 152 + tools/vendor/log/Cargo.toml.orig | 76 + tools/vendor/log/LICENSE-APACHE | 201 + tools/vendor/log/LICENSE-MIT | 25 + tools/vendor/log/README.md | 134 + tools/vendor/log/benches/value.rs | 27 + tools/vendor/log/src/__private_api.rs | 151 + tools/vendor/log/src/kv/error.rs | 94 + tools/vendor/log/src/kv/key.rs | 164 + tools/vendor/log/src/kv/mod.rs | 265 + tools/vendor/log/src/kv/source.rs | 515 ++ tools/vendor/log/src/kv/value.rs | 1396 ++++ tools/vendor/log/src/lib.rs | 2010 +++++ tools/vendor/log/src/macros.rs | 579 ++ tools/vendor/log/src/serde.rs | 397 + tools/vendor/log/tests/integration.rs | 101 + tools/vendor/log/tests/macros.rs | 429 + tools/vendor/log/triagebot.toml | 1 + .../portable-atomic-util/.cargo-checksum.json | 1 + .../portable-atomic-util/.cargo_vcs_info.json | 6 + .../vendor/portable-atomic-util/CHANGELOG.md | 127 + tools/vendor/portable-atomic-util/Cargo.lock | 23 + tools/vendor/portable-atomic-util/Cargo.toml | 203 + .../portable-atomic-util/Cargo.toml.orig | 52 + .../portable-atomic-util/LICENSE-APACHE | 177 + tools/vendor/portable-atomic-util/LICENSE-MIT | 23 + tools/vendor/portable-atomic-util/README.md | 77 + tools/vendor/portable-atomic-util/build.rs | 101 + tools/vendor/portable-atomic-util/src/arc.rs | 3410 ++++++++ tools/vendor/portable-atomic-util/src/lib.rs | 117 + tools/vendor/portable-atomic-util/src/task.rs | 166 + .../vendor/portable-atomic-util/src/utils.rs | 107 + .../vendor/portable-atomic-util/tests/arc.rs | 822 ++ tools/vendor/portable-atomic-util/version.rs | 136 + .../portable-atomic/.cargo-checksum.json | 1 + .../portable-atomic/.cargo_vcs_info.json | 6 + tools/vendor/portable-atomic/CHANGELOG.md | 629 ++ tools/vendor/portable-atomic/Cargo.lock | 250 + tools/vendor/portable-atomic/Cargo.toml | 268 + tools/vendor/portable-atomic/Cargo.toml.orig | 163 + tools/vendor/portable-atomic/LICENSE-APACHE | 177 + tools/vendor/portable-atomic/LICENSE-MIT | 23 + tools/vendor/portable-atomic/README.md | 245 + tools/vendor/portable-atomic/build.rs | 614 ++ tools/vendor/portable-atomic/matrix-old.json | 398 + tools/vendor/portable-atomic/src/cfgs.rs | 593 ++ tools/vendor/portable-atomic/src/gen/build.rs | 80 + tools/vendor/portable-atomic/src/gen/utils.rs | 164 + .../src/imp/atomic128/README.md | 39 + .../src/imp/atomic128/aarch64.rs | 2235 +++++ .../src/imp/atomic128/intrinsics.rs | 506 ++ .../src/imp/atomic128/macros.rs | 349 + .../portable-atomic/src/imp/atomic128/mod.rs | 131 + .../src/imp/atomic128/powerpc64.rs | 1044 +++ .../src/imp/atomic128/riscv64.rs | 622 ++ .../src/imp/atomic128/s390x.rs | 522 ++ .../src/imp/atomic128/x86_64.rs | 1001 +++ .../src/imp/atomic64/README.md | 24 + .../src/imp/atomic64/arm_linux.rs | 335 + .../src/imp/atomic64/macros.rs | 230 + .../portable-atomic/src/imp/atomic64/mod.rs | 43 + .../src/imp/atomic64/riscv32.rs | 624 ++ tools/vendor/portable-atomic/src/imp/avr.rs | 99 + .../portable-atomic/src/imp/core_atomic.rs | 543 ++ .../portable-atomic/src/imp/detect/README.md | 38 + .../src/imp/detect/aarch64_aa64reg.rs | 618 ++ .../src/imp/detect/aarch64_apple.rs | 301 + .../src/imp/detect/aarch64_fuchsia.rs | 83 + .../src/imp/detect/aarch64_illumos.rs | 63 + .../src/imp/detect/aarch64_windows.rs | 57 + .../portable-atomic/src/imp/detect/auxv.rs | 1288 +++ .../portable-atomic/src/imp/detect/common.rs | 270 + .../src/imp/detect/powerpc64_aix.rs | 54 + .../src/imp/detect/riscv_linux.rs | 241 + .../portable-atomic/src/imp/detect/x86_64.rs | 109 + .../portable-atomic/src/imp/fallback/mod.rs | 551 ++ .../src/imp/fallback/outline_atomics.rs | 184 + .../src/imp/fallback/seq_lock.rs | 191 + .../src/imp/fallback/seq_lock_wide.rs | 234 + .../portable-atomic/src/imp/fallback/utils.rs | 165 + .../portable-atomic/src/imp/float/aarch64.rs | 266 + .../portable-atomic/src/imp/float/int.rs | 224 + .../portable-atomic/src/imp/float/mod.rs | 33 + .../src/imp/interrupt/README.md | 42 + .../src/imp/interrupt/armv4t.rs | 160 + .../src/imp/interrupt/armv6m.rs | 64 + .../portable-atomic/src/imp/interrupt/avr.rs | 79 + .../portable-atomic/src/imp/interrupt/mod.rs | 701 ++ .../src/imp/interrupt/msp430.rs | 86 + .../src/imp/interrupt/riscv.rs | 102 + .../src/imp/interrupt/xtensa.rs | 67 + tools/vendor/portable-atomic/src/imp/mod.rs | 590 ++ .../vendor/portable-atomic/src/imp/msp430.rs | 283 + tools/vendor/portable-atomic/src/imp/riscv.rs | 945 +++ tools/vendor/portable-atomic/src/imp/x86.rs | 277 + tools/vendor/portable-atomic/src/lib.rs | 5182 ++++++++++++ tools/vendor/portable-atomic/src/rustdoc.css | 92 + .../portable-atomic/src/tests/helper.rs | 3080 +++++++ tools/vendor/portable-atomic/src/tests/mod.rs | 497 ++ tools/vendor/portable-atomic/src/utils.rs | 968 +++ tools/vendor/portable-atomic/tests/.gitignore | 1 + tools/vendor/portable-atomic/version.rs | 136 + 307 files changed, 164849 insertions(+), 223 deletions(-) create mode 100644 tools/hermes/src/docs.rs create mode 100644 tools/hermes/tests/cases/success/unused_vars_check.rs create mode 100644 tools/vendor/env_filter/.cargo-checksum.json create mode 100644 tools/vendor/env_filter/.cargo_vcs_info.json create mode 100644 tools/vendor/env_filter/Cargo.lock create mode 100644 tools/vendor/env_filter/Cargo.toml create mode 100644 tools/vendor/env_filter/Cargo.toml.orig create mode 100644 tools/vendor/env_filter/LICENSE-APACHE create mode 100644 tools/vendor/env_filter/LICENSE-MIT create mode 100644 tools/vendor/env_filter/README.md create mode 100644 tools/vendor/env_filter/src/directive.rs create mode 100644 tools/vendor/env_filter/src/filter.rs create mode 100644 tools/vendor/env_filter/src/filtered_log.rs create mode 100644 tools/vendor/env_filter/src/lib.rs create mode 100644 tools/vendor/env_filter/src/op.rs create mode 100644 tools/vendor/env_filter/src/parser.rs create mode 100644 tools/vendor/env_logger/.cargo-checksum.json create mode 100644 tools/vendor/env_logger/.cargo_vcs_info.json create mode 100644 tools/vendor/env_logger/Cargo.lock create mode 100644 tools/vendor/env_logger/Cargo.toml create mode 100644 tools/vendor/env_logger/Cargo.toml.orig create mode 100644 tools/vendor/env_logger/LICENSE-APACHE create mode 100644 tools/vendor/env_logger/LICENSE-MIT create mode 100644 tools/vendor/env_logger/README.md create mode 100644 tools/vendor/env_logger/examples/custom_default_format.rs create mode 100644 tools/vendor/env_logger/examples/custom_format.rs create mode 100644 tools/vendor/env_logger/examples/default.rs create mode 100644 tools/vendor/env_logger/examples/direct_logger.rs create mode 100644 tools/vendor/env_logger/examples/filters_from_code.rs create mode 100644 tools/vendor/env_logger/examples/in_tests.rs create mode 100644 tools/vendor/env_logger/examples/syslog_friendly_format.rs create mode 100644 tools/vendor/env_logger/src/fmt/humantime.rs create mode 100644 tools/vendor/env_logger/src/fmt/kv.rs create mode 100644 tools/vendor/env_logger/src/fmt/mod.rs create mode 100644 tools/vendor/env_logger/src/fmt/writer/buffer.rs create mode 100644 tools/vendor/env_logger/src/fmt/writer/mod.rs create mode 100644 tools/vendor/env_logger/src/fmt/writer/target.rs create mode 100644 tools/vendor/env_logger/src/lib.rs create mode 100644 tools/vendor/env_logger/src/logger.rs create mode 100644 tools/vendor/env_logger/tests/init-twice-retains-filter.rs create mode 100644 tools/vendor/env_logger/tests/log-in-log.rs create mode 100644 tools/vendor/env_logger/tests/log_tls_dtors.rs create mode 100644 tools/vendor/env_logger/tests/regexp_filter.rs create mode 100644 tools/vendor/jiff-static/.cargo-checksum.json create mode 100644 tools/vendor/jiff-static/.cargo_vcs_info.json create mode 100644 tools/vendor/jiff-static/COPYING create mode 100644 tools/vendor/jiff-static/Cargo.lock create mode 100644 tools/vendor/jiff-static/Cargo.toml create mode 100644 tools/vendor/jiff-static/Cargo.toml.orig create mode 100644 tools/vendor/jiff-static/LICENSE-MIT create mode 100644 tools/vendor/jiff-static/README.md create mode 100644 tools/vendor/jiff-static/UNLICENSE create mode 100644 tools/vendor/jiff-static/src/lib.rs create mode 100644 tools/vendor/jiff-static/src/shared/crc32/mod.rs create mode 100644 tools/vendor/jiff-static/src/shared/crc32/table.rs create mode 100644 tools/vendor/jiff-static/src/shared/error/itime.rs create mode 100644 tools/vendor/jiff-static/src/shared/error/mod.rs create mode 100644 tools/vendor/jiff-static/src/shared/mod.rs create mode 100644 tools/vendor/jiff-static/src/shared/posix.rs create mode 100644 tools/vendor/jiff-static/src/shared/tzif.rs create mode 100644 tools/vendor/jiff-static/src/shared/util/array_str.rs create mode 100644 tools/vendor/jiff-static/src/shared/util/error.rs create mode 100644 tools/vendor/jiff-static/src/shared/util/escape.rs create mode 100644 tools/vendor/jiff-static/src/shared/util/itime.rs create mode 100644 tools/vendor/jiff-static/src/shared/util/mod.rs create mode 100644 tools/vendor/jiff-static/src/shared/util/utf8.rs create mode 100644 tools/vendor/jiff/.cargo-checksum.json create mode 100644 tools/vendor/jiff/.cargo_vcs_info.json create mode 100644 tools/vendor/jiff/CHANGELOG.md create mode 100644 tools/vendor/jiff/COMPARE.md create mode 100644 tools/vendor/jiff/COPYING create mode 100644 tools/vendor/jiff/Cargo.lock create mode 100644 tools/vendor/jiff/Cargo.toml create mode 100644 tools/vendor/jiff/Cargo.toml.orig create mode 100644 tools/vendor/jiff/DESIGN.md create mode 100644 tools/vendor/jiff/LICENSE-MIT create mode 100644 tools/vendor/jiff/PLATFORM.md create mode 100644 tools/vendor/jiff/README.md create mode 100644 tools/vendor/jiff/UNLICENSE create mode 100644 tools/vendor/jiff/src/civil/date.rs create mode 100644 tools/vendor/jiff/src/civil/datetime.rs create mode 100644 tools/vendor/jiff/src/civil/iso_week_date.rs create mode 100644 tools/vendor/jiff/src/civil/mod.rs create mode 100644 tools/vendor/jiff/src/civil/time.rs create mode 100644 tools/vendor/jiff/src/civil/weekday.rs create mode 100644 tools/vendor/jiff/src/duration.rs create mode 100644 tools/vendor/jiff/src/error/civil.rs create mode 100644 tools/vendor/jiff/src/error/duration.rs create mode 100644 tools/vendor/jiff/src/error/fmt/friendly.rs create mode 100644 tools/vendor/jiff/src/error/fmt/mod.rs create mode 100644 tools/vendor/jiff/src/error/fmt/offset.rs create mode 100644 tools/vendor/jiff/src/error/fmt/rfc2822.rs create mode 100644 tools/vendor/jiff/src/error/fmt/rfc9557.rs create mode 100644 tools/vendor/jiff/src/error/fmt/strtime.rs create mode 100644 tools/vendor/jiff/src/error/fmt/temporal.rs create mode 100644 tools/vendor/jiff/src/error/fmt/util.rs create mode 100644 tools/vendor/jiff/src/error/mod.rs create mode 100644 tools/vendor/jiff/src/error/signed_duration.rs create mode 100644 tools/vendor/jiff/src/error/span.rs create mode 100644 tools/vendor/jiff/src/error/timestamp.rs create mode 100644 tools/vendor/jiff/src/error/tz/ambiguous.rs create mode 100644 tools/vendor/jiff/src/error/tz/concatenated.rs create mode 100644 tools/vendor/jiff/src/error/tz/db.rs create mode 100644 tools/vendor/jiff/src/error/tz/mod.rs create mode 100644 tools/vendor/jiff/src/error/tz/offset.rs create mode 100644 tools/vendor/jiff/src/error/tz/posix.rs create mode 100644 tools/vendor/jiff/src/error/tz/system.rs create mode 100644 tools/vendor/jiff/src/error/tz/timezone.rs create mode 100644 tools/vendor/jiff/src/error/tz/zic.rs create mode 100644 tools/vendor/jiff/src/error/util.rs create mode 100644 tools/vendor/jiff/src/error/zoned.rs create mode 100644 tools/vendor/jiff/src/fmt/buffer.rs create mode 100644 tools/vendor/jiff/src/fmt/friendly/mod.rs create mode 100644 tools/vendor/jiff/src/fmt/friendly/parser.rs create mode 100644 tools/vendor/jiff/src/fmt/friendly/parser_label.rs create mode 100644 tools/vendor/jiff/src/fmt/friendly/printer.rs create mode 100644 tools/vendor/jiff/src/fmt/mod.rs create mode 100644 tools/vendor/jiff/src/fmt/offset.rs create mode 100644 tools/vendor/jiff/src/fmt/rfc2822.rs create mode 100644 tools/vendor/jiff/src/fmt/rfc9557.rs create mode 100644 tools/vendor/jiff/src/fmt/serde.rs create mode 100644 tools/vendor/jiff/src/fmt/strtime/format.rs create mode 100644 tools/vendor/jiff/src/fmt/strtime/mod.rs create mode 100644 tools/vendor/jiff/src/fmt/strtime/parse.rs create mode 100644 tools/vendor/jiff/src/fmt/temporal/mod.rs create mode 100644 tools/vendor/jiff/src/fmt/temporal/parser.rs create mode 100644 tools/vendor/jiff/src/fmt/temporal/pieces.rs create mode 100644 tools/vendor/jiff/src/fmt/temporal/printer.rs create mode 100644 tools/vendor/jiff/src/fmt/util.rs create mode 100644 tools/vendor/jiff/src/lib.rs create mode 100644 tools/vendor/jiff/src/logging.rs create mode 100644 tools/vendor/jiff/src/now.rs create mode 100644 tools/vendor/jiff/src/shared/crc32/mod.rs create mode 100644 tools/vendor/jiff/src/shared/crc32/table.rs create mode 100644 tools/vendor/jiff/src/shared/mod.rs create mode 100644 tools/vendor/jiff/src/shared/posix.rs create mode 100644 tools/vendor/jiff/src/shared/tzif.rs create mode 100644 tools/vendor/jiff/src/shared/util/array_str.rs create mode 100644 tools/vendor/jiff/src/shared/util/itime.rs create mode 100644 tools/vendor/jiff/src/shared/util/mod.rs create mode 100644 tools/vendor/jiff/src/signed_duration.rs create mode 100644 tools/vendor/jiff/src/span.rs create mode 100644 tools/vendor/jiff/src/timestamp.rs create mode 100644 tools/vendor/jiff/src/tz/ambiguous.rs create mode 100644 tools/vendor/jiff/src/tz/concatenated.rs create mode 100644 tools/vendor/jiff/src/tz/db/bundled/disabled.rs create mode 100644 tools/vendor/jiff/src/tz/db/bundled/enabled.rs create mode 100644 tools/vendor/jiff/src/tz/db/bundled/mod.rs create mode 100644 tools/vendor/jiff/src/tz/db/concatenated/disabled.rs create mode 100644 tools/vendor/jiff/src/tz/db/concatenated/enabled.rs create mode 100644 tools/vendor/jiff/src/tz/db/concatenated/mod.rs create mode 100644 tools/vendor/jiff/src/tz/db/mod.rs create mode 100644 tools/vendor/jiff/src/tz/db/zoneinfo/disabled.rs create mode 100644 tools/vendor/jiff/src/tz/db/zoneinfo/enabled.rs create mode 100644 tools/vendor/jiff/src/tz/db/zoneinfo/mod.rs create mode 100644 tools/vendor/jiff/src/tz/mod.rs create mode 100644 tools/vendor/jiff/src/tz/offset.rs create mode 100644 tools/vendor/jiff/src/tz/posix.rs create mode 100644 tools/vendor/jiff/src/tz/system/android.rs create mode 100644 tools/vendor/jiff/src/tz/system/mod.rs create mode 100644 tools/vendor/jiff/src/tz/system/unix.rs create mode 100644 tools/vendor/jiff/src/tz/system/wasm_js.rs create mode 100644 tools/vendor/jiff/src/tz/system/windows/mod.rs create mode 100644 tools/vendor/jiff/src/tz/system/windows/windows_zones.rs create mode 100644 tools/vendor/jiff/src/tz/testdata.rs create mode 100644 tools/vendor/jiff/src/tz/timezone.rs create mode 100644 tools/vendor/jiff/src/tz/tzif.rs create mode 100644 tools/vendor/jiff/src/tz/zic.rs create mode 100644 tools/vendor/jiff/src/util/array_str.rs create mode 100644 tools/vendor/jiff/src/util/borrow.rs create mode 100644 tools/vendor/jiff/src/util/c.rs create mode 100644 tools/vendor/jiff/src/util/cache.rs create mode 100644 tools/vendor/jiff/src/util/constant.rs create mode 100644 tools/vendor/jiff/src/util/escape.rs create mode 100644 tools/vendor/jiff/src/util/fs.rs create mode 100644 tools/vendor/jiff/src/util/libm.rs create mode 100644 tools/vendor/jiff/src/util/mod.rs create mode 100644 tools/vendor/jiff/src/util/parse.rs create mode 100644 tools/vendor/jiff/src/util/rangeint.rs create mode 100644 tools/vendor/jiff/src/util/round/increment.rs create mode 100644 tools/vendor/jiff/src/util/round/mod.rs create mode 100644 tools/vendor/jiff/src/util/round/mode.rs create mode 100644 tools/vendor/jiff/src/util/sync.rs create mode 100644 tools/vendor/jiff/src/util/t.rs create mode 100644 tools/vendor/jiff/src/util/utf8.rs create mode 100644 tools/vendor/jiff/src/zoned.rs create mode 100644 tools/vendor/jiff/tests/lib.rs create mode 100644 tools/vendor/log/.cargo-checksum.json create mode 100644 tools/vendor/log/.cargo_vcs_info.json create mode 100644 tools/vendor/log/.github/workflows/main.yml create mode 100644 tools/vendor/log/CHANGELOG.md create mode 100644 tools/vendor/log/Cargo.lock create mode 100644 tools/vendor/log/Cargo.toml create mode 100644 tools/vendor/log/Cargo.toml.orig create mode 100644 tools/vendor/log/LICENSE-APACHE create mode 100644 tools/vendor/log/LICENSE-MIT create mode 100644 tools/vendor/log/README.md create mode 100644 tools/vendor/log/benches/value.rs create mode 100644 tools/vendor/log/src/__private_api.rs create mode 100644 tools/vendor/log/src/kv/error.rs create mode 100644 tools/vendor/log/src/kv/key.rs create mode 100644 tools/vendor/log/src/kv/mod.rs create mode 100644 tools/vendor/log/src/kv/source.rs create mode 100644 tools/vendor/log/src/kv/value.rs create mode 100644 tools/vendor/log/src/lib.rs create mode 100644 tools/vendor/log/src/macros.rs create mode 100644 tools/vendor/log/src/serde.rs create mode 100644 tools/vendor/log/tests/integration.rs create mode 100644 tools/vendor/log/tests/macros.rs create mode 100644 tools/vendor/log/triagebot.toml create mode 100644 tools/vendor/portable-atomic-util/.cargo-checksum.json create mode 100644 tools/vendor/portable-atomic-util/.cargo_vcs_info.json create mode 100644 tools/vendor/portable-atomic-util/CHANGELOG.md create mode 100644 tools/vendor/portable-atomic-util/Cargo.lock create mode 100644 tools/vendor/portable-atomic-util/Cargo.toml create mode 100644 tools/vendor/portable-atomic-util/Cargo.toml.orig create mode 100644 tools/vendor/portable-atomic-util/LICENSE-APACHE create mode 100644 tools/vendor/portable-atomic-util/LICENSE-MIT create mode 100644 tools/vendor/portable-atomic-util/README.md create mode 100644 tools/vendor/portable-atomic-util/build.rs create mode 100644 tools/vendor/portable-atomic-util/src/arc.rs create mode 100644 tools/vendor/portable-atomic-util/src/lib.rs create mode 100644 tools/vendor/portable-atomic-util/src/task.rs create mode 100644 tools/vendor/portable-atomic-util/src/utils.rs create mode 100644 tools/vendor/portable-atomic-util/tests/arc.rs create mode 100644 tools/vendor/portable-atomic-util/version.rs create mode 100644 tools/vendor/portable-atomic/.cargo-checksum.json create mode 100644 tools/vendor/portable-atomic/.cargo_vcs_info.json create mode 100644 tools/vendor/portable-atomic/CHANGELOG.md create mode 100644 tools/vendor/portable-atomic/Cargo.lock create mode 100644 tools/vendor/portable-atomic/Cargo.toml create mode 100644 tools/vendor/portable-atomic/Cargo.toml.orig create mode 100644 tools/vendor/portable-atomic/LICENSE-APACHE create mode 100644 tools/vendor/portable-atomic/LICENSE-MIT create mode 100644 tools/vendor/portable-atomic/README.md create mode 100644 tools/vendor/portable-atomic/build.rs create mode 100644 tools/vendor/portable-atomic/matrix-old.json create mode 100644 tools/vendor/portable-atomic/src/cfgs.rs create mode 100644 tools/vendor/portable-atomic/src/gen/build.rs create mode 100644 tools/vendor/portable-atomic/src/gen/utils.rs create mode 100644 tools/vendor/portable-atomic/src/imp/atomic128/README.md create mode 100644 tools/vendor/portable-atomic/src/imp/atomic128/aarch64.rs create mode 100644 tools/vendor/portable-atomic/src/imp/atomic128/intrinsics.rs create mode 100644 tools/vendor/portable-atomic/src/imp/atomic128/macros.rs create mode 100644 tools/vendor/portable-atomic/src/imp/atomic128/mod.rs create mode 100644 tools/vendor/portable-atomic/src/imp/atomic128/powerpc64.rs create mode 100644 tools/vendor/portable-atomic/src/imp/atomic128/riscv64.rs create mode 100644 tools/vendor/portable-atomic/src/imp/atomic128/s390x.rs create mode 100644 tools/vendor/portable-atomic/src/imp/atomic128/x86_64.rs create mode 100644 tools/vendor/portable-atomic/src/imp/atomic64/README.md create mode 100644 tools/vendor/portable-atomic/src/imp/atomic64/arm_linux.rs create mode 100644 tools/vendor/portable-atomic/src/imp/atomic64/macros.rs create mode 100644 tools/vendor/portable-atomic/src/imp/atomic64/mod.rs create mode 100644 tools/vendor/portable-atomic/src/imp/atomic64/riscv32.rs create mode 100644 tools/vendor/portable-atomic/src/imp/avr.rs create mode 100644 tools/vendor/portable-atomic/src/imp/core_atomic.rs create mode 100644 tools/vendor/portable-atomic/src/imp/detect/README.md create mode 100644 tools/vendor/portable-atomic/src/imp/detect/aarch64_aa64reg.rs create mode 100644 tools/vendor/portable-atomic/src/imp/detect/aarch64_apple.rs create mode 100644 tools/vendor/portable-atomic/src/imp/detect/aarch64_fuchsia.rs create mode 100644 tools/vendor/portable-atomic/src/imp/detect/aarch64_illumos.rs create mode 100644 tools/vendor/portable-atomic/src/imp/detect/aarch64_windows.rs create mode 100644 tools/vendor/portable-atomic/src/imp/detect/auxv.rs create mode 100644 tools/vendor/portable-atomic/src/imp/detect/common.rs create mode 100644 tools/vendor/portable-atomic/src/imp/detect/powerpc64_aix.rs create mode 100644 tools/vendor/portable-atomic/src/imp/detect/riscv_linux.rs create mode 100644 tools/vendor/portable-atomic/src/imp/detect/x86_64.rs create mode 100644 tools/vendor/portable-atomic/src/imp/fallback/mod.rs create mode 100644 tools/vendor/portable-atomic/src/imp/fallback/outline_atomics.rs create mode 100644 tools/vendor/portable-atomic/src/imp/fallback/seq_lock.rs create mode 100644 tools/vendor/portable-atomic/src/imp/fallback/seq_lock_wide.rs create mode 100644 tools/vendor/portable-atomic/src/imp/fallback/utils.rs create mode 100644 tools/vendor/portable-atomic/src/imp/float/aarch64.rs create mode 100644 tools/vendor/portable-atomic/src/imp/float/int.rs create mode 100644 tools/vendor/portable-atomic/src/imp/float/mod.rs create mode 100644 tools/vendor/portable-atomic/src/imp/interrupt/README.md create mode 100644 tools/vendor/portable-atomic/src/imp/interrupt/armv4t.rs create mode 100644 tools/vendor/portable-atomic/src/imp/interrupt/armv6m.rs create mode 100644 tools/vendor/portable-atomic/src/imp/interrupt/avr.rs create mode 100644 tools/vendor/portable-atomic/src/imp/interrupt/mod.rs create mode 100644 tools/vendor/portable-atomic/src/imp/interrupt/msp430.rs create mode 100644 tools/vendor/portable-atomic/src/imp/interrupt/riscv.rs create mode 100644 tools/vendor/portable-atomic/src/imp/interrupt/xtensa.rs create mode 100644 tools/vendor/portable-atomic/src/imp/mod.rs create mode 100644 tools/vendor/portable-atomic/src/imp/msp430.rs create mode 100644 tools/vendor/portable-atomic/src/imp/riscv.rs create mode 100644 tools/vendor/portable-atomic/src/imp/x86.rs create mode 100644 tools/vendor/portable-atomic/src/lib.rs create mode 100644 tools/vendor/portable-atomic/src/rustdoc.css create mode 100644 tools/vendor/portable-atomic/src/tests/helper.rs create mode 100644 tools/vendor/portable-atomic/src/tests/mod.rs create mode 100644 tools/vendor/portable-atomic/src/utils.rs create mode 100644 tools/vendor/portable-atomic/tests/.gitignore create mode 100644 tools/vendor/portable-atomic/version.rs diff --git a/tools/Cargo.lock b/tools/Cargo.lock index 2e1643fb1f..d352dd5e06 100644 --- a/tools/Cargo.lock +++ b/tools/Cargo.lock @@ -83,6 +83,8 @@ dependencies = [ "anyhow", "cargo_metadata", "clap", + "env_logger", + "log", "quote", "regex", "syn", @@ -165,6 +167,29 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +[[package]] +name = "env_filter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -212,6 +237,36 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +[[package]] +name = "jiff" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67e8da4c49d6d9909fe03361f9b620f58898859f5c7aded68351e85e71ecf50" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", +] + +[[package]] +name = "jiff-static" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + [[package]] name = "memchr" version = "2.7.6" @@ -224,6 +279,21 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "portable-atomic-util" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9db96d7fa8782dd8c15ce32ffe8680bbd1e978a43bf51a34d39483540495f5" +dependencies = [ + "portable-atomic", +] + [[package]] name = "proc-macro2" version = "1.0.105" diff --git a/tools/hermes/Cargo.toml b/tools/hermes/Cargo.toml index 672936a5b3..d3564028e8 100644 --- a/tools/hermes/Cargo.toml +++ b/tools/hermes/Cargo.toml @@ -5,9 +5,11 @@ edition = "2024" [dependencies] anyhow = "1.0.100" +cargo_metadata = "0.18" clap = { version = "4.5.56", features = ["derive"] } +env_logger = "0.11" +log = "0.4" +quote = "1.0.43" regex = "1.12.2" -walkdir = "2.5.0" -cargo_metadata = "0.18" syn = { version = "2.0.114", features = ["full", "visit", "visit-mut", "extra-traits"] } -quote = "1.0.43" +walkdir = "2.5.0" diff --git a/tools/hermes/src/desugar.rs b/tools/hermes/src/desugar.rs index 4141fa39b4..16d84e7d37 100644 --- a/tools/hermes/src/desugar.rs +++ b/tools/hermes/src/desugar.rs @@ -34,7 +34,7 @@ pub fn desugar_spec( // Check if this file looks like signature args. // It might be `(x y : Usize)` or `add_mod (x y : Usize)`. // We also support instance arguments like `[Layout T]`. - if let Some(start_idx) = line.find(|c| c == '(' || c == '[') { + if let Some(start_idx) = line.find(['(', '[']) { // Assume everything from start_idx onwards is args. // And ignore what's before it (likely function name). signature_args = Some(line[start_idx..].to_string()); @@ -86,17 +86,17 @@ pub fn desugar_spec( fn parse_binders(ensures_content: &str) -> Result<(Vec, String)> { // |ret, x_final| logic - if let Some(start) = ensures_content.find('|') { - if let Some(end) = ensures_content[start + 1..].find('|') { - let binders_str = &ensures_content[start + 1..start + 1 + end]; - let logic = &ensures_content[start + 1 + end + 1..]; - let binders = binders_str - .split(',') - .map(|s| s.trim().to_string()) - .filter(|s| !s.is_empty()) - .collect(); - return Ok((binders, logic.trim().to_string())); - } + if let Some(start) = ensures_content.find('|') + && let Some(end) = ensures_content[start + 1..].find('|') + { + let binders_str = &ensures_content[start + 1..start + 1 + end]; + let logic = &ensures_content[start + 1 + end + 1..]; + let binders = binders_str + .split(',') + .map(|s| s.trim().to_string()) + .filter(|s| !s.is_empty()) + .collect(); + return Ok((binders, logic.trim().to_string())); } bail!("Malformed ensures clause: missing |...| binders"); } @@ -116,7 +116,7 @@ fn generate_body( out.push_str("exists"); for b in binders { if b != "_" { - out.push_str(" "); + out.push(' '); out.push_str(b); } else { // If it's "_", we usually need a name for the exists, diff --git a/tools/hermes/src/docs.rs b/tools/hermes/src/docs.rs new file mode 100644 index 0000000000..4359e47866 --- /dev/null +++ b/tools/hermes/src/docs.rs @@ -0,0 +1,74 @@ +// Copyright 2026 The Fuchsia Authors +// +// Licensed under a BSD-style license , Apache License, Version 2.0 +// , or the MIT +// license , at your option. +// This file may not be copied, modified, or distributed except according to +// those terms. + +use syn::{Attribute, Expr, ExprLit, Lit, Meta}; + +/// Extracts the string content of a `#[doc = "..."]` or `/// ...` attribute. +/// Returns `None` if the attribute is not a doc comment. +pub fn parse_doc_attr(attr: &Attribute) -> Option { + if !attr.path().is_ident("doc") { + return None; + } + + match &attr.meta { + Meta::NameValue(nv) => match &nv.value { + Expr::Lit(ExprLit { lit: Lit::Str(s), .. }) => Some(s.value()), + _ => None, + }, + _ => None, + } +} + +/// Helper to check if a line is a Hermes directive. +/// Returns the trimmed content if it matches the prefix, otherwise None. +/// This handles the `///@` syntax. +/// Parses a specific Hermes tag from a line. +/// Returns `Some(content)` if the line starts with `///@ ` or `///@`. +/// Content is trimmed. +/// Example: `parse_hermes_tag("@ lean spec", "lean spec")` -> `Some(...)` +pub fn parse_hermes_tag<'a>(line: &'a str, tag: &str) -> Option<&'a str> { + let trimmed = line.trim(); + if !trimmed.starts_with('@') { + return None; + } + + // Check for "@ " or "@" + // We expect usage like "@ lean spec" + let prefix_space = format!("@ {}", tag); + if let Some(rest) = trimmed.strip_prefix(&prefix_space) { + return Some(rest.trim()); + } + + let prefix_nospace = format!("@{}", tag); + if let Some(rest) = trimmed.strip_prefix(&prefix_nospace) { + return Some(rest.trim()); + } + + None +} + +/// Checks if a line is a Hermes directive (starts with `@`). +pub fn is_hermes_directive(line: &str) -> bool { + line.trim().starts_with('@') +} +/// Iterates over all doc attributes, parsing them, splitting into lines, +/// and yielding only those that are Hermes directives (start with `@`). +/// Returns trimmed lines. +pub fn iter_hermes_lines(attrs: &[Attribute]) -> impl Iterator + '_ { + attrs.iter().flat_map(|attr| { + if let Some(doc) = parse_doc_attr(attr) { + // We must collect to separate lifetime from `doc` + doc.lines() + .map(|line| line.trim().to_string()) + .filter(|line| is_hermes_directive(line)) + .collect::>() + } else { + Vec::new() + } + }) +} diff --git a/tools/hermes/src/include/__hermes_std.rs b/tools/hermes/src/include/__hermes_std.rs index f95486aea1..1aab53192a 100644 --- a/tools/hermes/src/include/__hermes_std.rs +++ b/tools/hermes/src/include/__hermes_std.rs @@ -1,3 +1,13 @@ +// Copyright 2026 The Fuchsia Authors +// +// Licensed under a BSD-style license , Apache License, Version 2.0 +// , or the MIT +// license , at your option. +// This file may not be copied, modified, or distributed except according to +// those terms. + +#![allow(dead_code, unused_imports)] + pub use ::std::*; // Re-export the prelude so 'use std::prelude::rust_2021::*' works. diff --git a/tools/hermes/src/lib.rs b/tools/hermes/src/lib.rs index f9727fbf70..93b29c721e 100644 --- a/tools/hermes/src/lib.rs +++ b/tools/hermes/src/lib.rs @@ -14,6 +14,7 @@ //! - Stitching generated Lean code with user-provided proofs. pub mod desugar; +pub mod docs; pub mod orchestration; pub mod parser; pub mod pipeline; diff --git a/tools/hermes/src/main.rs b/tools/hermes/src/main.rs index da5ddd0567..46df9ec1e4 100644 --- a/tools/hermes/src/main.rs +++ b/tools/hermes/src/main.rs @@ -50,6 +50,7 @@ pub enum Commands { } fn main() -> Result<()> { + env_logger::init(); let CargoCli::Hermes(args) = CargoCli::parse(); let Commands::Verify { crate_name, dest, aeneas_path, manifest_path, allow_sorry } = args.command; diff --git a/tools/hermes/src/orchestration.rs b/tools/hermes/src/orchestration.rs index 148eb9329a..d7cb107027 100644 --- a/tools/hermes/src/orchestration.rs +++ b/tools/hermes/src/orchestration.rs @@ -20,18 +20,23 @@ pub fn run_charon(crate_root: &Path, dest_file: &Path, manifest_path: Option<&Pa let crate_root = crate_root.to_str().unwrap(); let dest_file = dest_file.to_str().unwrap(); - println!("Running charon in {:?}", crate_root); + log::debug!("Running charon in {:?}", crate_root); let mut cmd = Command::new("charon"); - cmd.env_remove("CARGO_TARGET_DIR"); // Avoid deadlock with outer cargo + + // Avoid deadlock with outer cargo + cmd.env_remove("CARGO_TARGET_DIR"); + + // Triggered when we convert `unsafe { ... }` into `{ ... }`. + cmd.env("RUSTFLAGS", "-Aunused_braces"); cmd.current_dir(crate_root); cmd.arg("cargo"); // Charon args first - args(&mut cmd, &["--dest-file", dest_file, "--preset", "aeneas"]); + args(&mut cmd, &["--dest-file", dest_file, "--preset", "aeneas", "--error-on-warnings"]); if let Some(path) = manifest_path { cmd.arg("--"); - if path.extension().map_or(false, |e| e == "rs") { + if path.extension().is_some_and(|e| e == "rs") { cmd.arg("-Zscript"); } cmd.arg("--manifest-path"); @@ -45,6 +50,8 @@ pub fn run_charon(crate_root: &Path, dest_file: &Path, manifest_path: Option<&Pa Ok(()) } +const ALL_AENEAS_BACKENDS: &str = "BorrowCheck,Builtin,Contexts,Deps,Errors,Extract,FunsAnalysis,Interp,InterpAbs,InterpBorrows,InterpExpansion,InterpExpressions,InterpJoin,InterpLoops,InterpLoopsFixedPoint,InterpMatchCtxs,InterpPaths,InterpProjectors,InterpReduceCollapse,InterpStatements,Invariants,MainLogger,PrePasses,PureMicroPasses,PureMicroPasses.simplify_aggregates_unchanged_fields,PureTypeCheck,PureUtils,RegionsHierarchy,ReorderDecls,SCC,SymbolicToPure,SymbolicToPureAbs,SymbolicToPureExpressions,SymbolicToPureTypes,SymbolicToPureValues,Translate,TypesAnalysis"; + /// Runs the Aeneas tool to translate LLBC files into Lean code. /// /// # Arguments @@ -55,7 +62,23 @@ pub fn run_aeneas(llbc_path: &Path, dest: &Path) -> Result<()> { let dest = dest.to_str().unwrap(); let mut cmd = Command::new("aeneas"); - args(&mut cmd, &["-backend", "lean", llbc_path, "-dest", dest]); + args( + &mut cmd, + &[ + "-backend", + "lean", + // TODO: Uncomment these once we've suppressed all warnings or + // built a mechanism to conditionally disable passing this flag. + // + // "-log-error", + // ALL_AENEAS_BACKENDS, + "-warnings-as-errors", + "-no-progress-bar", + llbc_path, + "-dest", + dest, + ], + ); let status = cmd.status().context("Failed to execute aeneas. Ensure it is in your PATH.")?; if !status.success() { @@ -72,6 +95,7 @@ pub fn run_lake_build(dir: &Path) -> Result<()> { let status = Command::new("lake") .current_dir(dir) .arg("build") + .arg("--quiet") .status() .context("Failed to execute lake. Ensure Lean 4 is installed.")?; diff --git a/tools/hermes/src/parser.rs b/tools/hermes/src/parser.rs index 57e016c944..5974933fed 100644 --- a/tools/hermes/src/parser.rs +++ b/tools/hermes/src/parser.rs @@ -48,15 +48,15 @@ impl SpecVisitor { } fn check_attrs_for_misplaced_spec(&mut self, attrs: &[Attribute], item_kind: &str) { - for attr in attrs { - if let Some(doc_str) = parse_doc_attr(attr) { - if doc_str.trim_start().starts_with("@") { - self.errors.push(anyhow::anyhow!( - "Found `///@` spec usage on a {}, but it is only allowed on functions or structs.", - item_kind - )); - } - } + for _line in crate::docs::iter_hermes_lines(attrs) { + // We already filtered for is_hermes_directive + // But we specifically check if it *starts* with @ and we error if it's misplaced + // iter_hermes_lines ensures it filters for lines starting with @. + // So every line here is a spec usage. + self.errors.push(anyhow::anyhow!( + "Found `///@` spec usage on a {}, but it is only allowed on functions or structs.", + item_kind + )); } } } @@ -68,39 +68,38 @@ impl<'ast> Visit<'ast> for SpecVisitor { let mut current_mode = None; // None, Some("spec"), Some("proof") let mut is_model = false; - for attr in &node.attrs { - if let Some(doc_str) = parse_doc_attr(attr) { - let trimmed = doc_str.trim(); - // Check for ///@ marker (doc comment starting with @) - if trimmed.starts_with('@') { - // Check if it's a new block start - if let Some(content) = trimmed.strip_prefix("@ lean spec") { - current_mode = Some("spec"); - spec_lines.push(content.to_string()); - } else if let Some(content) = trimmed.strip_prefix("@ lean model") { - current_mode = Some("spec"); - is_model = true; - spec_lines.push(content.to_string()); - } else if let Some(content) = trimmed.strip_prefix("@ proof") { - current_mode = Some("proof"); - proof_lines.push(content.to_string()); - } else { - // Continuation line - match current_mode { - Some("spec") => { - let content = &trimmed[1..]; - spec_lines.push(content.to_string()); - } - Some("proof") => { - let content = &trimmed[1..]; - proof_lines.push(content.to_string()); - } - None => { - self.errors.push(anyhow::anyhow!("Found `///@` line without preceding `lean spec` or `proof` on function '{}'", node.sig.ident)); - } - _ => {} + for trimmed in crate::docs::iter_hermes_lines(&node.attrs) { + // Check if it's a new block start + if let Some(content) = crate::docs::parse_hermes_tag(&trimmed, "lean spec") { + current_mode = Some("spec"); + spec_lines.push(content.to_string()); + } else if let Some(content) = crate::docs::parse_hermes_tag(&trimmed, "lean model") { + current_mode = Some("spec"); + is_model = true; + spec_lines.push(content.to_string()); + } else if let Some(content) = crate::docs::parse_hermes_tag(&trimmed, "proof") { + current_mode = Some("proof"); + proof_lines.push(content.to_string()); + } else { + // Continuation line + match current_mode { + Some("spec") | Some("proof") => { + // For continuation, we might want to just take the whole line after `@`? + // Or does the current logic assume `@` is just a marker? + // Original: `let content = &trimmed[1..];` + // We can use `parse_hermes_tag(trimmed, "")` maybe? No, that expects space. + // Let's simplify: + let content = trimmed[1..].trim(); + if current_mode == Some("spec") { + spec_lines.push(content.to_string()); + } else { + proof_lines.push(content.to_string()); } } + None => { + self.errors.push(anyhow::anyhow!("Found `///@` line without preceding `lean spec` or `proof` on function '{}'", node.sig.ident)); + } + _ => {} } } } @@ -125,52 +124,45 @@ impl<'ast> Visit<'ast> for SpecVisitor { let mut invariant_lines = Vec::new(); let mut current_mode = None; // None, Some("invariant") - for attr in &node.attrs { - if let Some(doc_str) = parse_doc_attr(attr) { - let trimmed = doc_str.trim(); - if trimmed.starts_with('@') { - if let Some(content) = trimmed.strip_prefix("@ lean invariant") { - current_mode = Some("invariant"); - let mut content = content.trim(); - // Ignore if it's just the struct name or empty - // referencing node.ident - if content == node.ident.to_string() { - content = ""; - } + for trimmed in crate::docs::iter_hermes_lines(&node.attrs) { + if let Some(content) = crate::docs::parse_hermes_tag(&trimmed, "lean invariant") { + current_mode = Some("invariant"); + let mut content = content; + // Ignore if it's just the struct name or empty + // referencing node.ident + if node.ident == content { + content = ""; + } - // Strip "is_valid self :=" or "is_valid :=" - if let Some(rest) = content.strip_prefix("is_valid") { - let rest = rest.trim(); - if let Some(rest) = rest.strip_prefix("self") { - let rest = rest.trim(); - if let Some(rest) = rest.strip_prefix(":=") { - content = rest.trim(); - } - } else if let Some(rest) = rest.strip_prefix(":=") { - content = rest.trim(); - } + // Strip "is_valid self :=" or "is_valid :=" + if let Some(rest) = content.strip_prefix("is_valid") { + let rest = rest.trim(); + if let Some(rest) = rest.strip_prefix("self") { + let rest = rest.trim(); + if let Some(rest) = rest.strip_prefix(":=") { + content = rest.trim(); } + } else if let Some(rest) = rest.strip_prefix(":=") { + content = rest.trim(); + } + } - if !content.is_empty() { - invariant_lines.push(content.to_string()); - } - } else { - match current_mode { - Some("invariant") => { - let content = &trimmed[1..]; - invariant_lines.push(content.to_string()); - } - None => { - // Only error if it looks like a spec attempt? - // For now, we update check_attrs_for_misplaced_spec to strictly call out non-struct/fn - // But here we just ignore or could error. - // Let's rely on the fact that if we didn't handle it here, it might be misplaced if we didn't check. - // Actually, we should probably support it. - self.errors.push(anyhow::anyhow!("Found `///@` line without preceding `lean invariant` on struct '{}'", node.ident)); - } - _ => {} - } + if !content.is_empty() { + invariant_lines.push(content.to_string()); + } + } else { + match current_mode { + Some("invariant") => { + let content = trimmed[1..].trim(); + invariant_lines.push(content.to_string()); } + None => { + self.errors.push(anyhow::anyhow!( + "Found `///@` line without preceding `lean invariant` on struct '{}'", + node.ident + )); + } + _ => {} } } } @@ -234,20 +226,6 @@ impl<'ast> Visit<'ast> for SpecVisitor { } } -fn parse_doc_attr(attr: &Attribute) -> Option { - if !attr.path().is_ident("doc") { - return None; - } - - match &attr.meta { - syn::Meta::NameValue(nv) => match &nv.value { - syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(s), .. }) => Some(s.value()), - _ => None, - }, - _ => None, - } -} - pub fn extract_blocks(content: &str) -> Result { let ast = parse_file(content)?; let mut visitor = SpecVisitor::new(); diff --git a/tools/hermes/src/pipeline.rs b/tools/hermes/src/pipeline.rs index ae2bcd8eff..1eb13b1849 100644 --- a/tools/hermes/src/pipeline.rs +++ b/tools/hermes/src/pipeline.rs @@ -32,7 +32,7 @@ fn get_crate_name( let mut cmd = cargo_metadata::MetadataCommand::new(); if let Some(path) = manifest_path { - if path.extension().map_or(false, |e| e == "rs") { + if path.extension().is_some_and(|e| e == "rs") { return Ok(path.file_stem().unwrap().to_string_lossy().to_string()); } cmd.manifest_path(path); @@ -103,12 +103,12 @@ pub fn run_pipeline( // Only pass manifest_path as source_file if it is an .rs file (script) let source_file = if let Some(path) = &manifest_path { - if path.extension().map_or(false, |e| e == "rs") { Some(path.as_path()) } else { None } + if path.extension().is_some_and(|e| e == "rs") { Some(path.as_path()) } else { None } } else { None }; - println!("Step 1: Creating Shadow Crate..."); + log::info!("Step 1: Creating Shadow Crate..."); let (shadow_crate_root, shadow_source_file) = crate::shadow::create_shadow_crate(crate_root, source_file)?; @@ -131,10 +131,10 @@ pub fn run_pipeline( return Err(anyhow!("Charon did not produce expected LLBC file: {:?}", llbc_path)); } - println!("Step 2: Running Aeneas..."); + log::info!("Step 2: Running Aeneas..."); run_aeneas(&llbc_path, dest)?; - println!("Step 3: Stitching..."); + log::info!("Step 3: Stitching..."); let camel_name: String = crate_name_snake .split('_') @@ -154,11 +154,11 @@ pub fn run_pipeline( stitch_user_proofs(&shadow_crate_root, &crate_name_snake, &camel_name, dest, sorry_mode)?; - println!("Step 4: Verifying..."); + log::info!("Step 4: Verifying..."); write_lakefile(dest, &crate_name_snake, &camel_name, aeneas_path, sorry_mode)?; run_lake_build(dest)?; - println!("Verification Successful!"); + log::info!("Verification Successful!"); Ok(()) } @@ -176,7 +176,7 @@ fn stitch_user_proofs( if src_dir.exists() { for entry in WalkDir::new(src_dir) { let entry = entry?; - if entry.path().extension().map_or(false, |ext| ext == "rs") { + if entry.path().extension().is_some_and(|ext| ext == "rs") { let content = fs::read_to_string(entry.path())?; let extracted = extract_blocks(&content)?; all_functions.extend(extracted.functions); @@ -299,7 +299,7 @@ namespace {} let desugared = match desugar_spec(spec_content, &fn_name, &inputs, is_stateful) { Ok(d) => d, Err(e) => { - eprintln!("Skipping function '{}' due to spec error: {}", fn_name, e); + log::warn!("Skipping function '{}' due to spec error: {}", fn_name, e); continue; } }; @@ -317,16 +317,15 @@ namespace {} // For each arg `x : T`, inject `(h_x : Verifiable.is_valid x)` // We need to parse inputs to get names. for arg in &inputs { - if let syn::FnArg::Typed(pat_type) = arg { - if let syn::Pat::Ident(pat_ident) = &*pat_type.pat { - let name = &pat_ident.ident; - // We assume the type is verifiable. - // The signature args in `desugared.signature_args` already listed them as `(x : T)`. - // We just append validity hypotheses. - // Note: This relies on `x` being available in scope, which it is in the signature. - signature_parts - .push(format!("(h_{}_valid : Verifiable.is_valid {})", name, name)); - } + if let syn::FnArg::Typed(pat_type) = arg + && let syn::Pat::Ident(pat_ident) = &*pat_type.pat + { + let name = &pat_ident.ident; + // We assume the type is verifiable. + // The signature args in `desugared.signature_args` already listed them as `(x : T)`. + // We just append validity hypotheses. + // Note: This relies on `x` being available in scope, which it is in the signature. + signature_parts.push(format!("(h_{}_valid : Verifiable.is_valid {})", name, name)); } } diff --git a/tools/hermes/src/shadow.rs b/tools/hermes/src/shadow.rs index 52b115541a..5877249818 100644 --- a/tools/hermes/src/shadow.rs +++ b/tools/hermes/src/shadow.rs @@ -112,16 +112,6 @@ pub fn create_shadow_crate( Ok((shadow_root, shadow_source_file)) } -// Helper to process single source file BEFORE prelude injection -fn ensure_single_source(root: &Path, source: Option<&Path>) -> Result<()> { - if let Some(src) = source { - let file_name = src.file_name().context("Invalid source file name")?; - let dest = root.join(file_name); - process_file_content(src, &dest)?; - } - Ok(()) -} - const SHIM_CONTENT: &str = include_str!("include/__hermes_std.rs"); fn inject_prelude(path: &Path) -> Result<()> { @@ -192,7 +182,7 @@ fn sanitize_crate(root: &Path) -> Result<()> { for entry in WalkDir::new(&src_dir) { let entry = entry?; - if entry.file_type().is_file() && entry.path().extension().map_or(false, |e| e == "rs") { + if entry.file_type().is_file() && entry.path().extension().is_some_and(|e| e == "rs") { process_file(entry.path())?; } } @@ -231,7 +221,7 @@ impl VisitMut for ShadowVisitor { quote!(true) } else { let combined = model_requires.join(") && ("); - let combined_str = format!("({})", combined); + let combined_str = format!("{}", combined); // Parse as Expr to ensure validity and proper token nesting let expr = syn::parse_str::(&combined_str) .unwrap_or_else(|_| parse_quote!(true)); @@ -241,6 +231,13 @@ impl VisitMut for ShadowVisitor { let body_content = quote! { { if #preconditions { + // TODO: If the preconditions are met, we still + // panic. That means that any model we provide will + // be wrong (ie, it will claim that the function + // returns successfully, when in fact it panics). + // This will allow Lean to infer a contradiction, + // allowing any user's proof to be accepted + // regardless of its correctness. ::std::unimplemented!("Safe Shim") } else { ::std::panic!("Contract Violated") @@ -252,6 +249,12 @@ impl VisitMut for ShadowVisitor { if let Ok(block) = syn::parse2(body_content) { node.block = block; } + + // Append #[allow(unused_variables)] to suppress warnings/errors + // since the new body doesn't use any of its arguments. It's + // important that we add it at the end to override any + // preceding `#[warn(unused_variables)]` (or `deny`). + node.attrs.push(parse_quote!(#[allow(unused_variables)])); } else { // Case B: Unwrap Strategy // 1. Remove unsafe @@ -285,48 +288,21 @@ impl VisitMut for ShadowVisitor { } } -fn parse_doc_attr(attr: &Attribute) -> Option { - if !attr.path().is_ident("doc") { - return None; - } - match &attr.meta { - syn::Meta::NameValue(nv) => match &nv.value { - syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(s), .. }) => Some(s.value()), - _ => None, - }, - _ => None, - } -} - fn parse_model_specs(attrs: &[Attribute]) -> (bool, Vec) { let mut is_model = false; let mut requires = Vec::new(); - for attr in attrs { - if let Some(doc) = parse_doc_attr(attr) { - println!("DEBUG: Doc attr: {:?}", doc); - for line in doc.lines() { - let trimmed = line.trim(); - println!("DEBUG: Trimmed line: '{}'", trimmed); - // Check found marker e.g. "@ lean model" - if trimmed.starts_with('@') { - if trimmed.contains("lean model") { - is_model = true; - println!("DEBUG: Found model marker!"); - } + for trimmed in crate::docs::iter_hermes_lines(attrs) { + if let Some(_content) = crate::docs::parse_hermes_tag(&trimmed, "lean model") { + is_model = true; + log::debug!("Found model marker!"); + } - if let Some(rest) = trimmed.strip_prefix("@ requires") { - let content = rest.trim(); - // Strip binder name if present (e.g. "h : x > 0" -> "x > 0") - let condition = if let Some((_, expr)) = content.split_once(':') { - expr.trim() - } else { - content - }; - requires.push(condition.to_string()); - } - } - } + if let Some(content) = crate::docs::parse_hermes_tag(&trimmed, "requires") { + // Strip binder name if present (e.g. "h : x > 0" -> "x > 0") + let condition = + if let Some((_, expr)) = content.split_once(':') { expr.trim() } else { content }; + requires.push(condition.to_string()); } } (is_model, requires) @@ -365,6 +341,7 @@ mod tests { "#; let expected = r#" #[doc = "@ lean model foo ensures |ret| ret = 42"] + #[allow(unused_variables)] fn foo() -> i32 { if true { ::std::unimplemented!("Safe Shim") } else { ::std::panic!("Contract Violated") } } @@ -382,8 +359,9 @@ mod tests { let expected = r#" #[doc = "@ lean model safe_div(a b : u32)"] #[doc = "@ requires b > 0"] + #[allow(unused_variables)] fn safe_div(a: u32, b: u32) -> u32 { - if (b > 0) { ::std::unimplemented!("Safe Shim") } else { ::std::panic!("Contract Violated") } + if b > 0 { ::std::unimplemented!("Safe Shim") } else { ::std::panic!("Contract Violated") } } "#; assert_normalized_eq(&transform(input), expected); @@ -398,6 +376,7 @@ mod tests { // Signature should keep *const u32 let expected = r#" #[doc = "@ lean model read(ptr : Type) ..."] + #[allow(unused_variables)] fn read(ptr: *const u32) -> u32 { if true { ::std::unimplemented!("Safe Shim") } else { ::std::panic!("Contract Violated") } } @@ -479,6 +458,7 @@ mod tests { "#; let expected = r#" #[doc = "@ lean model ..."] + #[allow(unused_variables)] fn cast (x: *const T) -> T { if true { ::std::unimplemented!("Safe Shim") } else { ::std::panic!("Contract Violated") } } @@ -533,8 +513,9 @@ mod tests { let expected = r#" #[doc = "@ lean model foo"] #[doc = "@ requires x > 0"] + #[allow(unused_variables)] fn foo(x: i32) { - if (x > 0) { ::std::unimplemented!("Safe Shim") } else { ::std::panic!("Contract Violated") } + if x > 0 { ::std::unimplemented!("Safe Shim") } else { ::std::panic!("Contract Violated") } } "#; assert_normalized_eq(&transform(input), expected); @@ -551,7 +532,7 @@ mod tests { let diff = transform(input); // Normalize for check let diff_norm: String = diff.chars().filter(|c| !c.is_whitespace()).collect(); - assert!(diff_norm.contains("if(x>0)")); + assert!(diff_norm.contains("ifx>0")); assert!(diff_norm.contains("panic!(\"ContractViolated\")")); } @@ -566,8 +547,9 @@ mod tests { let expected = r#" #[doc = "@ lean model foo"] #[doc = "@ requires x == 10"] + #[allow(unused_variables)] fn foo(x: i32) { - if (x == 10) { ::std::unimplemented!("Safe Shim") } else { ::std::panic!("Contract Violated") } + if x == 10 { ::std::unimplemented!("Safe Shim") } else { ::std::panic!("Contract Violated") } } "#; assert_normalized_eq(&transform(input), expected); diff --git a/tools/hermes/src/translator.rs b/tools/hermes/src/translator.rs index 210e57840b..cfc2c90412 100644 --- a/tools/hermes/src/translator.rs +++ b/tools/hermes/src/translator.rs @@ -43,12 +43,11 @@ impl SignatureTranslator { pub fn detect_statefulness(args: &[FnArg]) -> bool { for arg in args { - if let FnArg::Typed(pat_type) = arg { - if let Type::Reference(type_ref) = &*pat_type.ty { - if type_ref.mutability.is_some() { - return true; - } - } + if let FnArg::Typed(pat_type) = arg + && let Type::Reference(type_ref) = &*pat_type.ty + && type_ref.mutability.is_some() + { + return true; } } false diff --git a/tools/hermes/tests/cases/success/read_shim_success.rs b/tools/hermes/tests/cases/success/read_shim_success.rs index cbe9d56947..79432d0a0e 100644 --- a/tools/hermes/tests/cases/success/read_shim_success.rs +++ b/tools/hermes/tests/cases/success/read_shim_success.rs @@ -4,7 +4,7 @@ ///@ level 10 ///@ proof ///@ simp_all -unsafe fn test_read_success(p: *const u32) -> u32 { +pub unsafe fn test_read_success(p: *const u32) -> u32 { unsafe { std::ptr::read(p) } } diff --git a/tools/hermes/tests/cases/success/unused_vars_check.rs b/tools/hermes/tests/cases/success/unused_vars_check.rs new file mode 100644 index 0000000000..b112f02e13 --- /dev/null +++ b/tools/hermes/tests/cases/success/unused_vars_check.rs @@ -0,0 +1,17 @@ +#![deny(unused_variables)] + +///@ lean model unused_vars_test +///@ requires true +#[deny(unused_variables)] +pub unsafe fn unused_vars_test(x: i32) { + unsafe { + // Real body uses x + let _ = x; + } +} + +pub fn main() { + unsafe { + unused_vars_test(42); + } +} diff --git a/tools/hermes/tests/integration.rs b/tools/hermes/tests/integration.rs index b717830835..c347db6a25 100644 --- a/tools/hermes/tests/integration.rs +++ b/tools/hermes/tests/integration.rs @@ -8,8 +8,8 @@ use std::{ env, fs, - io::Write as _, path::{Path, PathBuf}, + sync::Once, }; use cargo_hermes::pipeline::{Sorry, run_pipeline}; @@ -43,9 +43,13 @@ impl Drop for TestTempDir { } } +static INIT: Once = Once::new(); + fn setup_env() -> (PathBuf, PathBuf) { - println!("DEBUG: Starting setup_env"); - std::io::stdout().flush().unwrap(); + INIT.call_once(|| { + env_logger::builder().is_test(true).try_init().ok(); + }); + log::debug!("Starting setup_env"); let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR")); let cases_dir = manifest_dir.join("tests/cases"); @@ -56,8 +60,7 @@ fn setup_env() -> (PathBuf, PathBuf) { let charon_bin = aeneas_path.join("charon/bin"); let aeneas_bin = aeneas_path.join("bin"); if charon_bin.exists() { - println!("DEBUG: Found charon bin at {:?}", charon_bin); - std::io::stdout().flush().unwrap(); + log::debug!("Found charon bin at {:?}", charon_bin); let home_dir = env::var("HOME").map(PathBuf::from).unwrap_or_else(|_| PathBuf::from(".")); let elan_bin = home_dir.join(".elan/bin"); let current_path = env::var("PATH").unwrap_or_default(); @@ -72,14 +75,12 @@ fn setup_env() -> (PathBuf, PathBuf) { env::set_var("PATH", new_path); } } - println!("DEBUG: setup_env complete"); - std::io::stdout().flush().unwrap(); + log::debug!("setup_env complete"); (cases_dir, aeneas_lean_path) } fn run_suite(suite_name: &str, expect_success: bool) { - println!("DEBUG: Starting run_{}_cases", suite_name); - std::io::stdout().flush().unwrap(); + log::debug!("Starting run_{}_cases", suite_name); let (cases_dir, aeneas_lean_path) = setup_env(); let suite_dir = cases_dir.join(suite_name); if suite_dir.exists() { @@ -88,14 +89,14 @@ fn run_suite(suite_name: &str, expect_success: bool) { { let entry = entry.expect("Failed to read entry"); let path = entry.path(); - if path.extension().map_or(false, |e| e == "rs") { + if path.extension().is_some_and(|e| e == "rs") { let file_name = path.file_stem().unwrap().to_string_lossy().to_string(); - if let Ok(filter) = env::var("HERMES_FILTER") { - if !file_name.contains(&filter) { - continue; - } + if let Ok(filter) = env::var("HERMES_FILTER") + && !file_name.contains(&filter) + { + continue; } - println!("Running {} test case: {:?}", suite_name, path.file_name().unwrap()); + log::info!("Running {} test case: {:?}", suite_name, path.file_name().unwrap()); run_case( &path, &aeneas_lean_path, @@ -116,7 +117,7 @@ fn test_integration_suite() { // Should succeed with allow_sorry=true let (cases_dir, aeneas_lean_path) = setup_env(); let path = cases_dir.join("failure/missing_proof.rs"); - println!("Running allow_sorry test case: {:?}", path.file_name().unwrap()); + log::info!("Running allow_sorry test case: {:?}", path.file_name().unwrap()); run_case(&path, &aeneas_lean_path, Some("missing_proof".to_string()), true, Sorry::AllowSorry); } @@ -146,10 +147,8 @@ fn run_case( let dest_packages = dest_lake.join("packages"); // Force symlink packages - if dest_packages.exists() { - if !dest_packages.is_symlink() { - fs::remove_dir_all(&dest_packages).expect("Failed to remove existing packages dir"); - } + if dest_packages.exists() && !dest_packages.is_symlink() { + fs::remove_dir_all(&dest_packages).expect("Failed to remove existing packages dir"); } if !dest_packages.exists() { std::os::unix::fs::symlink(&shared_packages, &dest_packages) @@ -158,13 +157,11 @@ fn run_case( let dest_build = dest_lake.join("build"); // Force symlink build - if dest_build.exists() { - if !dest_build.is_symlink() { - if dest_build.is_dir() { - fs::remove_dir_all(&dest_build).expect("Failed to remove existing build dir"); - } else { - fs::remove_file(&dest_build).expect("Failed to remove existing build file"); - } + if dest_build.exists() && !dest_build.is_symlink() { + if dest_build.is_dir() { + fs::remove_dir_all(&dest_build).expect("Failed to remove existing build dir"); + } else { + fs::remove_file(&dest_build).expect("Failed to remove existing build file"); } } if !dest_build.exists() { diff --git a/tools/vendor/env_filter/.cargo-checksum.json b/tools/vendor/env_filter/.cargo-checksum.json new file mode 100644 index 0000000000..9d832c1532 --- /dev/null +++ b/tools/vendor/env_filter/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{".cargo_vcs_info.json":"7f95deeac85bceeeedf3050aa36d486bcec3d31a3e0dd11103acefc30210eba7","Cargo.lock":"02c1cf78b07a5abd3cf883bb3409083c2b7aff8aa595262269bd6182b5b83d79","Cargo.toml":"4349ba6632cf673083db87cb3a948a66b68478ade90e6fc77745d115e43a1c08","Cargo.toml.orig":"80b86af68c6fed486c7a52a85589950b2f5504e86e163bc594e5e43b12cf5728","LICENSE-APACHE":"c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08","LICENSE-MIT":"6efb0476a1cc085077ed49357026d8c173bf33017278ef440f222fb9cbcb66e6","README.md":"d3d7cb39fda6c7516c7f35f7ae88e1df49a5bf24a3415541e068391c34c2f6f3","src/directive.rs":"ba7d4d01a9dd76f31e8a761ffbc1d002fa97461d5606cfbf31e173392f90ffdc","src/filter.rs":"27dceef9956aae3e34c53b169b8e81069fbad09c0ac2c675064f9d11a889f80b","src/filtered_log.rs":"5a2c431b7967e288cba7d0ba4f327c12cb399b8fbe9107c9c7af64d014da7053","src/lib.rs":"b7af5b4acb2cea3ec0cafc69fe273f8610f67c546bb3b461fcf99b7a9099dc7a","src/op.rs":"2ee9a01e8eae9f1cabaa1f360a3ebd2fcc650523f9c2f6c76a82b195c7ca9b93","src/parser.rs":"fe519dfbbab6eb21f2c900ce6edb7c9e070b3ff93d911f3e254e3c0b34ff2728"},"package":"1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2"} \ No newline at end of file diff --git a/tools/vendor/env_filter/.cargo_vcs_info.json b/tools/vendor/env_filter/.cargo_vcs_info.json new file mode 100644 index 0000000000..55c9a8f835 --- /dev/null +++ b/tools/vendor/env_filter/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "1514675ecd6cb0f0d14a71eb1caa8919fe16c964" + }, + "path_in_vcs": "crates/env_filter" +} \ No newline at end of file diff --git a/tools/vendor/env_filter/Cargo.lock b/tools/vendor/env_filter/Cargo.lock new file mode 100644 index 0000000000..42e58a6357 --- /dev/null +++ b/tools/vendor/env_filter/Cargo.lock @@ -0,0 +1,210 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "env_filter" +version = "0.1.4" +dependencies = [ + "log", + "regex", + "snapbox", +] + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + +[[package]] +name = "regex" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" + +[[package]] +name = "similar" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640" + +[[package]] +name = "snapbox" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96dcfc4581e3355d70ac2ee14cfdf81dce3d85c85f1ed9e2c1d3013f53b3436b" +dependencies = [ + "anstream", + "anstyle", + "normalize-line-endings", + "similar", + "snapbox-macros", +] + +[[package]] +name = "snapbox-macros" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16569f53ca23a41bb6f62e0a5084aa1661f4814a67fa33696a79073e03a664af" +dependencies = [ + "anstream", +] + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" diff --git a/tools/vendor/env_filter/Cargo.toml b/tools/vendor/env_filter/Cargo.toml new file mode 100644 index 0000000000..f27693a2ef --- /dev/null +++ b/tools/vendor/env_filter/Cargo.toml @@ -0,0 +1,179 @@ +# 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.71" +name = "env_filter" +version = "0.1.4" +build = false +include = [ + "build.rs", + "src/**/*", + "Cargo.toml", + "Cargo.lock", + "LICENSE*", + "README.md", + "examples/**/*", +] +autolib = false +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = """ +Filter log events using environment variables +""" +readme = "README.md" +keywords = [ + "logging", + "log", + "logger", +] +categories = ["development-tools::debugging"] +license = "MIT OR Apache-2.0" +repository = "https://github.com/rust-cli/env_logger" + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--generate-link-to-definition"] + +[[package.metadata.release.pre-release-replacements]] +file = "CHANGELOG.md" +search = "Unreleased" +replace = "{{version}}" +min = 1 + +[[package.metadata.release.pre-release-replacements]] +file = "CHANGELOG.md" +search = '\.\.\.HEAD' +replace = "...{{tag_name}}" +exactly = 1 + +[[package.metadata.release.pre-release-replacements]] +file = "CHANGELOG.md" +search = "ReleaseDate" +replace = "{{date}}" +min = 1 + +[[package.metadata.release.pre-release-replacements]] +file = "CHANGELOG.md" +search = "" +replace = """ + +## [Unreleased] - ReleaseDate +""" +exactly = 1 + +[[package.metadata.release.pre-release-replacements]] +file = "CHANGELOG.md" +search = "" +replace = """ + +[Unreleased]: https://github.com/rust-cli/env_logger/compare/{{tag_name}}...HEAD""" +exactly = 1 + +[features] +default = ["regex"] +regex = ["dep:regex"] + +[lib] +name = "env_filter" +path = "src/lib.rs" + +[dependencies.log] +version = "0.4.8" +features = ["std"] + +[dependencies.regex] +version = "1.0.3" +features = [ + "std", + "perf", +] +optional = true +default-features = false + +[dev-dependencies.snapbox] +version = "0.6" + +[lints.clippy] +bool_assert_comparison = "allow" +branches_sharing_code = "allow" +checked_conversions = "warn" +collapsible_else_if = "allow" +create_dir = "warn" +dbg_macro = "warn" +debug_assert_with_mut_call = "warn" +doc_markdown = "warn" +empty_enum = "warn" +enum_glob_use = "warn" +expl_impl_clone_on_copy = "warn" +explicit_deref_methods = "warn" +explicit_into_iter_loop = "warn" +fallible_impl_from = "warn" +filter_map_next = "warn" +flat_map_option = "warn" +float_cmp_const = "warn" +fn_params_excessive_bools = "warn" +from_iter_instead_of_collect = "warn" +if_same_then_else = "allow" +implicit_clone = "warn" +imprecise_flops = "warn" +inconsistent_struct_constructor = "warn" +inefficient_to_string = "warn" +infinite_loop = "warn" +invalid_upcast_comparisons = "warn" +large_digit_groups = "warn" +large_stack_arrays = "warn" +large_types_passed_by_value = "warn" +let_and_return = "allow" +linkedlist = "warn" +lossy_float_literal = "warn" +macro_use_imports = "warn" +mem_forget = "warn" +mutex_integer = "warn" +needless_continue = "allow" +needless_for_each = "warn" +negative_feature_names = "warn" +path_buf_push_overwrite = "warn" +ptr_as_ptr = "warn" +rc_mutex = "warn" +redundant_feature_names = "warn" +ref_option_ref = "warn" +rest_pat_in_fully_bound_structs = "warn" +result_large_err = "allow" +same_functions_in_if_condition = "warn" +self_named_module_files = "warn" +semicolon_if_nothing_returned = "warn" +str_to_string = "warn" +string_add = "warn" +string_add_assign = "warn" +string_lit_as_bytes = "warn" +string_to_string = "warn" +todo = "warn" +trait_duplication_in_bounds = "warn" +uninlined_format_args = "warn" +verbose_file_reads = "warn" +wildcard_imports = "warn" +zero_sized_map_values = "warn" + +[lints.rust] +unnameable_types = "warn" +unreachable_pub = "warn" +unsafe_op_in_unsafe_fn = "warn" +unused_lifetimes = "warn" +unused_macro_rules = "warn" +unused_qualifications = "warn" + +[lints.rust.rust_2018_idioms] +level = "warn" +priority = -1 diff --git a/tools/vendor/env_filter/Cargo.toml.orig b/tools/vendor/env_filter/Cargo.toml.orig new file mode 100644 index 0000000000..7b92e33a28 --- /dev/null +++ b/tools/vendor/env_filter/Cargo.toml.orig @@ -0,0 +1,40 @@ +[package] +name = "env_filter" +version = "0.1.4" +description = """ +Filter log events using environment variables +""" +categories = ["development-tools::debugging"] +keywords = ["logging", "log", "logger"] +repository.workspace = true +license.workspace = true +edition.workspace = true +rust-version.workspace = true +include.workspace = true + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--generate-link-to-definition"] + +[package.metadata.release] +pre-release-replacements = [ + {file="CHANGELOG.md", search="Unreleased", replace="{{version}}", min=1}, + {file="CHANGELOG.md", search="\\.\\.\\.HEAD", replace="...{{tag_name}}", exactly=1}, + {file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}", min=1}, + {file="CHANGELOG.md", search="", replace="\n## [Unreleased] - ReleaseDate\n", exactly=1}, + {file="CHANGELOG.md", search="", replace="\n[Unreleased]: https://github.com/rust-cli/env_logger/compare/{{tag_name}}...HEAD", exactly=1}, +] + +[features] +default = ["regex"] +regex = ["dep:regex"] + +[dependencies] +log = { version = "0.4.8", features = ["std"] } +regex = { version = "1.0.3", optional = true, default-features=false, features=["std", "perf"] } + +[dev-dependencies] +snapbox = "0.6" + +[lints] +workspace = true diff --git a/tools/vendor/env_filter/LICENSE-APACHE b/tools/vendor/env_filter/LICENSE-APACHE new file mode 100644 index 0000000000..8f71f43fee --- /dev/null +++ b/tools/vendor/env_filter/LICENSE-APACHE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/tools/vendor/env_filter/LICENSE-MIT b/tools/vendor/env_filter/LICENSE-MIT new file mode 100644 index 0000000000..a2d01088b6 --- /dev/null +++ b/tools/vendor/env_filter/LICENSE-MIT @@ -0,0 +1,19 @@ +Copyright (c) Individual contributors + +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. diff --git a/tools/vendor/env_filter/README.md b/tools/vendor/env_filter/README.md new file mode 100644 index 0000000000..9e5164fbbe --- /dev/null +++ b/tools/vendor/env_filter/README.md @@ -0,0 +1,6 @@ +# env_filter + +[![crates.io](https://img.shields.io/crates/v/env_filter.svg)](https://crates.io/crates/env_filter) +[![Documentation](https://docs.rs/env_filter/badge.svg)](https://docs.rs/env_filter) + +> Filter log events using environment variables diff --git a/tools/vendor/env_filter/src/directive.rs b/tools/vendor/env_filter/src/directive.rs new file mode 100644 index 0000000000..c24fef2842 --- /dev/null +++ b/tools/vendor/env_filter/src/directive.rs @@ -0,0 +1,20 @@ +use log::Level; +use log::LevelFilter; + +#[derive(Debug, Clone)] +pub(crate) struct Directive { + pub(crate) name: Option, + pub(crate) level: LevelFilter, +} + +// Check whether a level and target are enabled by the set of directives. +pub(crate) fn enabled(directives: &[Directive], level: Level, target: &str) -> bool { + // Search for the longest match, the vector is assumed to be pre-sorted. + for directive in directives.iter().rev() { + match directive.name { + Some(ref name) if !target.starts_with(&**name) => {} + Some(..) | None => return level <= directive.level, + } + } + false +} diff --git a/tools/vendor/env_filter/src/filter.rs b/tools/vendor/env_filter/src/filter.rs new file mode 100644 index 0000000000..60bcf7a8ee --- /dev/null +++ b/tools/vendor/env_filter/src/filter.rs @@ -0,0 +1,595 @@ +use std::env; +use std::fmt; +use std::mem; + +use log::{LevelFilter, Metadata, Record}; + +use crate::enabled; +use crate::parse_spec; +use crate::parser::ParseResult; +use crate::Directive; +use crate::FilterOp; +use crate::ParseError; + +/// A builder for a log filter. +/// +/// It can be used to parse a set of directives from a string before building +/// a [`Filter`] instance. +/// +/// ## Example +/// +/// ``` +/// # use std::env; +/// use env_filter::Builder; +/// +/// let mut builder = Builder::new(); +/// +/// // Parse a logging filter from an environment variable. +/// if let Ok(rust_log) = env::var("RUST_LOG") { +/// builder.parse(&rust_log); +/// } +/// +/// let filter = builder.build(); +/// ``` +pub struct Builder { + directives: Vec, + filter: Option, + built: bool, +} + +impl Builder { + /// Initializes the filter builder with defaults. + pub fn new() -> Builder { + Builder { + directives: Vec::new(), + filter: None, + built: false, + } + } + + /// Initializes the filter builder from an environment. + pub fn from_env(env: &str) -> Builder { + let mut builder = Builder::new(); + + if let Ok(s) = env::var(env) { + builder.parse(&s); + } + + builder + } + + /// Insert the directive replacing any directive with the same name. + fn insert_directive(&mut self, mut directive: Directive) { + if let Some(pos) = self + .directives + .iter() + .position(|d| d.name == directive.name) + { + mem::swap(&mut self.directives[pos], &mut directive); + } else { + self.directives.push(directive); + } + } + + /// Adds a directive to the filter for a specific module. + pub fn filter_module(&mut self, module: &str, level: LevelFilter) -> &mut Self { + self.filter(Some(module), level) + } + + /// Adds a directive to the filter for all modules. + pub fn filter_level(&mut self, level: LevelFilter) -> &mut Self { + self.filter(None, level) + } + + /// Adds a directive to the filter. + /// + /// The given module (if any) will log at most the specified level provided. + /// If no module is provided then the filter will apply to all log messages. + pub fn filter(&mut self, module: Option<&str>, level: LevelFilter) -> &mut Self { + self.insert_directive(Directive { + name: module.map(|s| s.to_owned()), + level, + }); + self + } + + /// Parses the directives string. + /// + /// See the [Enabling Logging] section for more details. + /// + /// [Enabling Logging]: ../index.html#enabling-logging + pub fn parse(&mut self, filters: &str) -> &mut Self { + #![allow(clippy::print_stderr)] // compatibility + + let ParseResult { + directives, + filter, + errors, + } = parse_spec(filters); + + for error in errors { + eprintln!("warning: {error}, ignoring it"); + } + + self.filter = filter; + + for directive in directives { + self.insert_directive(directive); + } + self + } + + /// Parses the directive string, returning an error if the given directive string is invalid. + /// + /// See the [Enabling Logging] section for more details. + /// + /// [Enabling Logging]: ../index.html#enabling-logging + pub fn try_parse(&mut self, filters: &str) -> Result<&mut Self, ParseError> { + let (directives, filter) = parse_spec(filters).ok()?; + + self.filter = filter; + + for directive in directives { + self.insert_directive(directive); + } + Ok(self) + } + + /// Build a log filter. + pub fn build(&mut self) -> Filter { + assert!(!self.built, "attempt to re-use consumed builder"); + self.built = true; + + let mut directives = Vec::new(); + if self.directives.is_empty() { + // Adds the default filter if none exist + directives.push(Directive { + name: None, + level: LevelFilter::Error, + }); + } else { + // Consume directives. + directives = mem::take(&mut self.directives); + // Sort the directives by length of their name, this allows a + // little more efficient lookup at runtime. + directives.sort_by(|a, b| { + let alen = a.name.as_ref().map(|a| a.len()).unwrap_or(0); + let blen = b.name.as_ref().map(|b| b.len()).unwrap_or(0); + alen.cmp(&blen) + }); + } + + Filter { + directives: mem::take(&mut directives), + filter: mem::take(&mut self.filter), + } + } +} + +impl Default for Builder { + fn default() -> Self { + Builder::new() + } +} + +impl fmt::Debug for Builder { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.built { + f.debug_struct("Filter").field("built", &true).finish() + } else { + f.debug_struct("Filter") + .field("filter", &self.filter) + .field("directives", &self.directives) + .finish() + } + } +} + +/// A log filter. +/// +/// This struct can be used to determine whether or not a log record +/// should be written to the output. +/// Use the [`Builder`] type to parse and construct a `Filter`. +/// +/// [`Builder`]: struct.Builder.html +#[derive(Clone)] +pub struct Filter { + directives: Vec, + filter: Option, +} + +impl Filter { + /// Returns the maximum `LevelFilter` that this filter instance is + /// configured to output. + /// + /// # Example + /// + /// ```rust + /// use log::LevelFilter; + /// use env_filter::Builder; + /// + /// let mut builder = Builder::new(); + /// builder.filter(Some("module1"), LevelFilter::Info); + /// builder.filter(Some("module2"), LevelFilter::Error); + /// + /// let filter = builder.build(); + /// assert_eq!(filter.filter(), LevelFilter::Info); + /// ``` + pub fn filter(&self) -> LevelFilter { + self.directives + .iter() + .map(|d| d.level) + .max() + .unwrap_or(LevelFilter::Off) + } + + /// Checks if this record matches the configured filter. + pub fn matches(&self, record: &Record<'_>) -> bool { + if !self.enabled(record.metadata()) { + return false; + } + + if let Some(filter) = self.filter.as_ref() { + if !filter.is_match(&record.args().to_string()) { + return false; + } + } + + true + } + + /// Determines if a log message with the specified metadata would be logged. + pub fn enabled(&self, metadata: &Metadata<'_>) -> bool { + let level = metadata.level(); + let target = metadata.target(); + + enabled(&self.directives, level, target) + } +} + +impl fmt::Debug for Filter { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Filter") + .field("filter", &self.filter) + .field("directives", &self.directives) + .finish() + } +} + +#[cfg(test)] +mod tests { + use log::{Level, LevelFilter}; + use snapbox::{assert_data_eq, str}; + + use super::{enabled, Builder, Directive, Filter}; + + fn make_logger_filter(dirs: Vec) -> Filter { + let mut logger = Builder::new().build(); + logger.directives = dirs; + logger + } + + #[test] + fn filter_info() { + let logger = Builder::new().filter(None, LevelFilter::Info).build(); + assert!(enabled(&logger.directives, Level::Info, "crate1")); + assert!(!enabled(&logger.directives, Level::Debug, "crate1")); + } + + #[test] + fn filter_beginning_longest_match() { + let logger = Builder::new() + .filter(Some("crate2"), LevelFilter::Info) + .filter(Some("crate2::mod"), LevelFilter::Debug) + .filter(Some("crate1::mod1"), LevelFilter::Warn) + .build(); + assert!(enabled(&logger.directives, Level::Debug, "crate2::mod1")); + assert!(!enabled(&logger.directives, Level::Debug, "crate2")); + } + + // Some of our tests are only correct or complete when they cover the full + // universe of variants for log::Level. In the unlikely event that a new + // variant is added in the future, this test will detect the scenario and + // alert us to the need to review and update the tests. In such a + // situation, this test will fail to compile, and the error message will + // look something like this: + // + // error[E0004]: non-exhaustive patterns: `NewVariant` not covered + // --> src/filter/mod.rs:413:15 + // | + // 413 | match level_universe { + // | ^^^^^^^^^^^^^^ pattern `NewVariant` not covered + #[test] + fn ensure_tests_cover_level_universe() { + let level_universe: Level = Level::Trace; // use of trace variant is arbitrary + match level_universe { + Level::Error | Level::Warn | Level::Info | Level::Debug | Level::Trace => (), + } + } + + #[test] + fn parse_default() { + let logger = Builder::new().parse("info,crate1::mod1=warn").build(); + assert!(enabled(&logger.directives, Level::Warn, "crate1::mod1")); + assert!(enabled(&logger.directives, Level::Info, "crate2::mod2")); + } + + #[test] + fn parse_default_bare_level_off_lc() { + let logger = Builder::new().parse("off").build(); + assert!(!enabled(&logger.directives, Level::Error, "")); + assert!(!enabled(&logger.directives, Level::Warn, "")); + assert!(!enabled(&logger.directives, Level::Info, "")); + assert!(!enabled(&logger.directives, Level::Debug, "")); + assert!(!enabled(&logger.directives, Level::Trace, "")); + } + + #[test] + fn parse_default_bare_level_off_uc() { + let logger = Builder::new().parse("OFF").build(); + assert!(!enabled(&logger.directives, Level::Error, "")); + assert!(!enabled(&logger.directives, Level::Warn, "")); + assert!(!enabled(&logger.directives, Level::Info, "")); + assert!(!enabled(&logger.directives, Level::Debug, "")); + assert!(!enabled(&logger.directives, Level::Trace, "")); + } + + #[test] + fn parse_default_bare_level_error_lc() { + let logger = Builder::new().parse("error").build(); + assert!(enabled(&logger.directives, Level::Error, "")); + assert!(!enabled(&logger.directives, Level::Warn, "")); + assert!(!enabled(&logger.directives, Level::Info, "")); + assert!(!enabled(&logger.directives, Level::Debug, "")); + assert!(!enabled(&logger.directives, Level::Trace, "")); + } + + #[test] + fn parse_default_bare_level_error_uc() { + let logger = Builder::new().parse("ERROR").build(); + assert!(enabled(&logger.directives, Level::Error, "")); + assert!(!enabled(&logger.directives, Level::Warn, "")); + assert!(!enabled(&logger.directives, Level::Info, "")); + assert!(!enabled(&logger.directives, Level::Debug, "")); + assert!(!enabled(&logger.directives, Level::Trace, "")); + } + + #[test] + fn parse_default_bare_level_warn_lc() { + let logger = Builder::new().parse("warn").build(); + assert!(enabled(&logger.directives, Level::Error, "")); + assert!(enabled(&logger.directives, Level::Warn, "")); + assert!(!enabled(&logger.directives, Level::Info, "")); + assert!(!enabled(&logger.directives, Level::Debug, "")); + assert!(!enabled(&logger.directives, Level::Trace, "")); + } + + #[test] + fn parse_default_bare_level_warn_uc() { + let logger = Builder::new().parse("WARN").build(); + assert!(enabled(&logger.directives, Level::Error, "")); + assert!(enabled(&logger.directives, Level::Warn, "")); + assert!(!enabled(&logger.directives, Level::Info, "")); + assert!(!enabled(&logger.directives, Level::Debug, "")); + assert!(!enabled(&logger.directives, Level::Trace, "")); + } + + #[test] + fn parse_default_bare_level_info_lc() { + let logger = Builder::new().parse("info").build(); + assert!(enabled(&logger.directives, Level::Error, "")); + assert!(enabled(&logger.directives, Level::Warn, "")); + assert!(enabled(&logger.directives, Level::Info, "")); + assert!(!enabled(&logger.directives, Level::Debug, "")); + assert!(!enabled(&logger.directives, Level::Trace, "")); + } + + #[test] + fn parse_default_bare_level_info_uc() { + let logger = Builder::new().parse("INFO").build(); + assert!(enabled(&logger.directives, Level::Error, "")); + assert!(enabled(&logger.directives, Level::Warn, "")); + assert!(enabled(&logger.directives, Level::Info, "")); + assert!(!enabled(&logger.directives, Level::Debug, "")); + assert!(!enabled(&logger.directives, Level::Trace, "")); + } + + #[test] + fn parse_default_bare_level_debug_lc() { + let logger = Builder::new().parse("debug").build(); + assert!(enabled(&logger.directives, Level::Error, "")); + assert!(enabled(&logger.directives, Level::Warn, "")); + assert!(enabled(&logger.directives, Level::Info, "")); + assert!(enabled(&logger.directives, Level::Debug, "")); + assert!(!enabled(&logger.directives, Level::Trace, "")); + } + + #[test] + fn parse_default_bare_level_debug_uc() { + let logger = Builder::new().parse("DEBUG").build(); + assert!(enabled(&logger.directives, Level::Error, "")); + assert!(enabled(&logger.directives, Level::Warn, "")); + assert!(enabled(&logger.directives, Level::Info, "")); + assert!(enabled(&logger.directives, Level::Debug, "")); + assert!(!enabled(&logger.directives, Level::Trace, "")); + } + + #[test] + fn parse_default_bare_level_trace_lc() { + let logger = Builder::new().parse("trace").build(); + assert!(enabled(&logger.directives, Level::Error, "")); + assert!(enabled(&logger.directives, Level::Warn, "")); + assert!(enabled(&logger.directives, Level::Info, "")); + assert!(enabled(&logger.directives, Level::Debug, "")); + assert!(enabled(&logger.directives, Level::Trace, "")); + } + + #[test] + fn parse_default_bare_level_trace_uc() { + let logger = Builder::new().parse("TRACE").build(); + assert!(enabled(&logger.directives, Level::Error, "")); + assert!(enabled(&logger.directives, Level::Warn, "")); + assert!(enabled(&logger.directives, Level::Info, "")); + assert!(enabled(&logger.directives, Level::Debug, "")); + assert!(enabled(&logger.directives, Level::Trace, "")); + } + + // In practice, the desired log level is typically specified by a token + // that is either all lowercase (e.g., 'trace') or all uppercase (.e.g, + // 'TRACE'), but this tests serves as a reminder that + // log::Level::from_str() ignores all case variants. + #[test] + fn parse_default_bare_level_debug_mixed() { + { + let logger = Builder::new().parse("Debug").build(); + assert!(enabled(&logger.directives, Level::Error, "")); + assert!(enabled(&logger.directives, Level::Warn, "")); + assert!(enabled(&logger.directives, Level::Info, "")); + assert!(enabled(&logger.directives, Level::Debug, "")); + assert!(!enabled(&logger.directives, Level::Trace, "")); + } + { + let logger = Builder::new().parse("debuG").build(); + assert!(enabled(&logger.directives, Level::Error, "")); + assert!(enabled(&logger.directives, Level::Warn, "")); + assert!(enabled(&logger.directives, Level::Info, "")); + assert!(enabled(&logger.directives, Level::Debug, "")); + assert!(!enabled(&logger.directives, Level::Trace, "")); + } + { + let logger = Builder::new().parse("deBug").build(); + assert!(enabled(&logger.directives, Level::Error, "")); + assert!(enabled(&logger.directives, Level::Warn, "")); + assert!(enabled(&logger.directives, Level::Info, "")); + assert!(enabled(&logger.directives, Level::Debug, "")); + assert!(!enabled(&logger.directives, Level::Trace, "")); + } + { + let logger = Builder::new().parse("DeBuG").build(); // LaTeX flavor! + assert!(enabled(&logger.directives, Level::Error, "")); + assert!(enabled(&logger.directives, Level::Warn, "")); + assert!(enabled(&logger.directives, Level::Info, "")); + assert!(enabled(&logger.directives, Level::Debug, "")); + assert!(!enabled(&logger.directives, Level::Trace, "")); + } + } + + #[test] + fn try_parse_valid_filter() { + let logger = Builder::new() + .try_parse("info,crate1::mod1=warn") + .expect("valid filter returned error") + .build(); + assert!(enabled(&logger.directives, Level::Warn, "crate1::mod1")); + assert!(enabled(&logger.directives, Level::Info, "crate2::mod2")); + } + + #[test] + fn try_parse_invalid_filter() { + let error = Builder::new().try_parse("info,crate1=invalid").unwrap_err(); + assert_data_eq!( + error, + str!["error parsing logger filter: invalid logging spec 'invalid'"] + ); + } + + #[test] + fn match_full_path() { + let logger = make_logger_filter(vec![ + Directive { + name: Some("crate2".to_owned()), + level: LevelFilter::Info, + }, + Directive { + name: Some("crate1::mod1".to_owned()), + level: LevelFilter::Warn, + }, + ]); + assert!(enabled(&logger.directives, Level::Warn, "crate1::mod1")); + assert!(!enabled(&logger.directives, Level::Info, "crate1::mod1")); + assert!(enabled(&logger.directives, Level::Info, "crate2")); + assert!(!enabled(&logger.directives, Level::Debug, "crate2")); + } + + #[test] + fn no_match() { + let logger = make_logger_filter(vec![ + Directive { + name: Some("crate2".to_owned()), + level: LevelFilter::Info, + }, + Directive { + name: Some("crate1::mod1".to_owned()), + level: LevelFilter::Warn, + }, + ]); + assert!(!enabled(&logger.directives, Level::Warn, "crate3")); + } + + #[test] + fn match_beginning() { + let logger = make_logger_filter(vec![ + Directive { + name: Some("crate2".to_owned()), + level: LevelFilter::Info, + }, + Directive { + name: Some("crate1::mod1".to_owned()), + level: LevelFilter::Warn, + }, + ]); + assert!(enabled(&logger.directives, Level::Info, "crate2::mod1")); + } + + #[test] + fn match_beginning_longest_match() { + let logger = make_logger_filter(vec![ + Directive { + name: Some("crate2".to_owned()), + level: LevelFilter::Info, + }, + Directive { + name: Some("crate2::mod".to_owned()), + level: LevelFilter::Debug, + }, + Directive { + name: Some("crate1::mod1".to_owned()), + level: LevelFilter::Warn, + }, + ]); + assert!(enabled(&logger.directives, Level::Debug, "crate2::mod1")); + assert!(!enabled(&logger.directives, Level::Debug, "crate2")); + } + + #[test] + fn match_default() { + let logger = make_logger_filter(vec![ + Directive { + name: None, + level: LevelFilter::Info, + }, + Directive { + name: Some("crate1::mod1".to_owned()), + level: LevelFilter::Warn, + }, + ]); + assert!(enabled(&logger.directives, Level::Warn, "crate1::mod1")); + assert!(enabled(&logger.directives, Level::Info, "crate2::mod2")); + } + + #[test] + fn zero_level() { + let logger = make_logger_filter(vec![ + Directive { + name: None, + level: LevelFilter::Info, + }, + Directive { + name: Some("crate1::mod1".to_owned()), + level: LevelFilter::Off, + }, + ]); + assert!(!enabled(&logger.directives, Level::Error, "crate1::mod1")); + assert!(enabled(&logger.directives, Level::Info, "crate2::mod2")); + } +} diff --git a/tools/vendor/env_filter/src/filtered_log.rs b/tools/vendor/env_filter/src/filtered_log.rs new file mode 100644 index 0000000000..d72d44bcd2 --- /dev/null +++ b/tools/vendor/env_filter/src/filtered_log.rs @@ -0,0 +1,45 @@ +use log::Log; + +use crate::Filter; + +/// Decorate a [`log::Log`] with record [`Filter`]ing. +/// +/// Records that match the filter will be forwarded to the wrapped log. +/// Other records will be ignored. +#[derive(Debug)] +pub struct FilteredLog { + log: T, + filter: Filter, +} + +impl FilteredLog { + /// Create a new filtered log. + pub fn new(log: T, filter: Filter) -> Self { + Self { log, filter } + } +} + +impl Log for FilteredLog { + /// Determines if a log message with the specified metadata would be logged. + /// + /// For the wrapped log, this returns `true` only if both the filter and the wrapped log return `true`. + fn enabled(&self, metadata: &log::Metadata<'_>) -> bool { + self.filter.enabled(metadata) && self.log.enabled(metadata) + } + + /// Logs the record. + /// + /// Forwards the record to the wrapped log, but only if the record matches the filter. + fn log(&self, record: &log::Record<'_>) { + if self.filter.matches(record) { + self.log.log(record); + } + } + + /// Flushes any buffered records. + /// + /// Forwards directly to the wrapped log. + fn flush(&self) { + self.log.flush(); + } +} diff --git a/tools/vendor/env_filter/src/lib.rs b/tools/vendor/env_filter/src/lib.rs new file mode 100644 index 0000000000..0b350882a7 --- /dev/null +++ b/tools/vendor/env_filter/src/lib.rs @@ -0,0 +1,63 @@ +//! Filtering for log records. +//! +//! You can use the [`Filter`] type in your own logger implementation to use the same +//! filter parsing and matching as `env_logger`. +//! +//! ## Using `env_filter` in your own logger +//! +//! You can use `env_filter`'s filtering functionality with your own logger. +//! Call [`Builder::parse`] to parse directives from a string when constructing +//! your logger. Call [`Filter::matches`] to check whether a record should be +//! logged based on the parsed filters when log records are received. +//! +//! ``` +//! use env_filter::Filter; +//! use log::{Log, Metadata, Record}; +//! +//! struct PrintLogger; +//! +//! impl Log for PrintLogger { +//! fn enabled(&self, metadata: &Metadata) -> bool { +//! true +//! } +//! +//! fn log(&self, record: &Record) { +//! println!("{:?}", record); +//! } +//! +//! fn flush(&self) {} +//! } +//! +//! let mut builder = env_filter::Builder::new(); +//! // Parse a directives string from an environment variable +//! if let Ok(ref filter) = std::env::var("MY_LOG_LEVEL") { +//! builder.parse(filter); +//! } +//! +//! let logger = env_filter::FilteredLog::new(PrintLogger, builder.build()); +//! ``` + +#![cfg_attr(docsrs, feature(doc_cfg))] +#![warn(missing_docs)] +#![warn(clippy::print_stderr)] +#![warn(clippy::print_stdout)] + +mod directive; +mod filter; +mod filtered_log; +mod op; +mod parser; + +use directive::enabled; +use directive::Directive; +use op::FilterOp; +use parser::parse_spec; + +pub use filter::Builder; +pub use filter::Filter; +pub use filtered_log::FilteredLog; +pub use parser::ParseError; + +#[doc = include_str!("../README.md")] +#[cfg(doctest)] +pub struct ReadmeDoctests; diff --git a/tools/vendor/env_filter/src/op.rs b/tools/vendor/env_filter/src/op.rs new file mode 100644 index 0000000000..bfc6a5c6b3 --- /dev/null +++ b/tools/vendor/env_filter/src/op.rs @@ -0,0 +1,42 @@ +use std::fmt; + +#[derive(Debug, Clone)] +pub(crate) struct FilterOp { + #[cfg(feature = "regex")] + inner: regex::Regex, + #[cfg(not(feature = "regex"))] + inner: String, +} + +#[cfg(feature = "regex")] +impl FilterOp { + pub(crate) fn new(spec: &str) -> Result { + match regex::Regex::new(spec) { + Ok(r) => Ok(Self { inner: r }), + Err(e) => Err(e.to_string()), + } + } + + pub(crate) fn is_match(&self, s: &str) -> bool { + self.inner.is_match(s) + } +} + +#[cfg(not(feature = "regex"))] +impl FilterOp { + pub fn new(spec: &str) -> Result { + Ok(Self { + inner: spec.to_string(), + }) + } + + pub fn is_match(&self, s: &str) -> bool { + s.contains(&self.inner) + } +} + +impl fmt::Display for FilterOp { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.inner.fmt(f) + } +} diff --git a/tools/vendor/env_filter/src/parser.rs b/tools/vendor/env_filter/src/parser.rs new file mode 100644 index 0000000000..097058d397 --- /dev/null +++ b/tools/vendor/env_filter/src/parser.rs @@ -0,0 +1,521 @@ +use log::LevelFilter; +use std::error::Error; +use std::fmt::{Display, Formatter}; + +use crate::Directive; +use crate::FilterOp; + +#[derive(Default, Debug)] +pub(crate) struct ParseResult { + pub(crate) directives: Vec, + pub(crate) filter: Option, + pub(crate) errors: Vec, +} + +impl ParseResult { + fn add_directive(&mut self, directive: Directive) { + self.directives.push(directive); + } + + fn set_filter(&mut self, filter: FilterOp) { + self.filter = Some(filter); + } + + fn add_error(&mut self, message: String) { + self.errors.push(message); + } + + pub(crate) fn ok(self) -> Result<(Vec, Option), ParseError> { + let Self { + directives, + filter, + errors, + } = self; + if let Some(error) = errors.into_iter().next() { + Err(ParseError { details: error }) + } else { + Ok((directives, filter)) + } + } +} + +/// Error during logger directive parsing process. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct ParseError { + details: String, +} + +impl Display for ParseError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "error parsing logger filter: {}", self.details) + } +} + +impl Error for ParseError {} + +/// Parse a logging specification string (e.g: `crate1,crate2::mod3,crate3::x=error/foo`) +/// and return a vector with log directives. +pub(crate) fn parse_spec(spec: &str) -> ParseResult { + let mut result = ParseResult::default(); + + let mut parts = spec.split('/'); + let mods = parts.next(); + let filter = parts.next(); + if parts.next().is_some() { + result.add_error(format!("invalid logging spec '{spec}' (too many '/'s)")); + return result; + } + if let Some(m) = mods { + for s in m.split(',').map(|ss| ss.trim()) { + if s.is_empty() { + continue; + } + let mut parts = s.split('='); + let (log_level, name) = + match (parts.next(), parts.next().map(|s| s.trim()), parts.next()) { + (Some(part0), None, None) => { + // if the single argument is a log-level string or number, + // treat that as a global fallback + match part0.parse() { + Ok(num) => (num, None), + Err(_) => (LevelFilter::max(), Some(part0)), + } + } + (Some(part0), Some(""), None) => (LevelFilter::max(), Some(part0)), + (Some(part0), Some(part1), None) => { + if let Ok(num) = part1.parse() { + (num, Some(part0)) + } else { + result.add_error(format!("invalid logging spec '{part1}'")); + continue; + } + } + _ => { + result.add_error(format!("invalid logging spec '{s}'")); + continue; + } + }; + + result.add_directive(Directive { + name: name.map(|s| s.to_owned()), + level: log_level, + }); + } + } + + if let Some(filter) = filter { + match FilterOp::new(filter) { + Ok(filter_op) => result.set_filter(filter_op), + Err(err) => result.add_error(format!("invalid regex filter - {err}")), + } + } + + result +} + +#[cfg(test)] +mod tests { + use crate::ParseError; + use log::LevelFilter; + use snapbox::{assert_data_eq, str, Data, IntoData}; + + use super::{parse_spec, ParseResult}; + + impl IntoData for ParseError { + fn into_data(self) -> Data { + self.to_string().into_data() + } + } + + #[test] + fn parse_spec_valid() { + let ParseResult { + directives: dirs, + filter, + errors, + } = parse_spec("crate1::mod1=error,crate1::mod2,crate2=debug"); + + assert_eq!(dirs.len(), 3); + assert_eq!(dirs[0].name, Some("crate1::mod1".to_owned())); + assert_eq!(dirs[0].level, LevelFilter::Error); + + assert_eq!(dirs[1].name, Some("crate1::mod2".to_owned())); + assert_eq!(dirs[1].level, LevelFilter::max()); + + assert_eq!(dirs[2].name, Some("crate2".to_owned())); + assert_eq!(dirs[2].level, LevelFilter::Debug); + assert!(filter.is_none()); + + assert!(errors.is_empty()); + } + + #[test] + fn parse_spec_invalid_crate() { + // test parse_spec with multiple = in specification + let ParseResult { + directives: dirs, + filter, + errors, + } = parse_spec("crate1::mod1=warn=info,crate2=debug"); + + assert_eq!(dirs.len(), 1); + assert_eq!(dirs[0].name, Some("crate2".to_owned())); + assert_eq!(dirs[0].level, LevelFilter::Debug); + assert!(filter.is_none()); + + assert_eq!(errors.len(), 1); + assert_data_eq!( + &errors[0], + str!["invalid logging spec 'crate1::mod1=warn=info'"] + ); + } + + #[test] + fn parse_spec_invalid_level() { + // test parse_spec with 'noNumber' as log level + let ParseResult { + directives: dirs, + filter, + errors, + } = parse_spec("crate1::mod1=noNumber,crate2=debug"); + + assert_eq!(dirs.len(), 1); + assert_eq!(dirs[0].name, Some("crate2".to_owned())); + assert_eq!(dirs[0].level, LevelFilter::Debug); + assert!(filter.is_none()); + + assert_eq!(errors.len(), 1); + assert_data_eq!(&errors[0], str!["invalid logging spec 'noNumber'"]); + } + + #[test] + fn parse_spec_string_level() { + // test parse_spec with 'warn' as log level + let ParseResult { + directives: dirs, + filter, + errors, + } = parse_spec("crate1::mod1=wrong,crate2=warn"); + + assert_eq!(dirs.len(), 1); + assert_eq!(dirs[0].name, Some("crate2".to_owned())); + assert_eq!(dirs[0].level, LevelFilter::Warn); + assert!(filter.is_none()); + + assert_eq!(errors.len(), 1); + assert_data_eq!(&errors[0], str!["invalid logging spec 'wrong'"]); + } + + #[test] + fn parse_spec_empty_level() { + // test parse_spec with '' as log level + let ParseResult { + directives: dirs, + filter, + errors, + } = parse_spec("crate1::mod1=wrong,crate2="); + + assert_eq!(dirs.len(), 1); + assert_eq!(dirs[0].name, Some("crate2".to_owned())); + assert_eq!(dirs[0].level, LevelFilter::max()); + assert!(filter.is_none()); + + assert_eq!(errors.len(), 1); + assert_data_eq!(&errors[0], str!["invalid logging spec 'wrong'"]); + } + + #[test] + fn parse_spec_empty_level_isolated() { + // test parse_spec with "" as log level (and the entire spec str) + let ParseResult { + directives: dirs, + filter, + errors, + } = parse_spec(""); // should be ignored + assert_eq!(dirs.len(), 0); + assert!(filter.is_none()); + assert!(errors.is_empty()); + } + + #[test] + fn parse_spec_blank_level_isolated() { + // test parse_spec with a white-space-only string specified as the log + // level (and the entire spec str) + let ParseResult { + directives: dirs, + filter, + errors, + } = parse_spec(" "); // should be ignored + assert_eq!(dirs.len(), 0); + assert!(filter.is_none()); + assert!(errors.is_empty()); + } + + #[test] + fn parse_spec_blank_level_isolated_comma_only() { + // The spec should contain zero or more comma-separated string slices, + // so a comma-only string should be interpreted as two empty strings + // (which should both be treated as invalid, so ignored). + let ParseResult { + directives: dirs, + filter, + errors, + } = parse_spec(","); // should be ignored + assert_eq!(dirs.len(), 0); + assert!(filter.is_none()); + assert!(errors.is_empty()); + } + + #[test] + fn parse_spec_blank_level_isolated_comma_blank() { + // The spec should contain zero or more comma-separated string slices, + // so this bogus spec should be interpreted as containing one empty + // string and one blank string. Both should both be treated as + // invalid, so ignored. + let ParseResult { + directives: dirs, + filter, + errors, + } = parse_spec(", "); // should be ignored + assert_eq!(dirs.len(), 0); + assert!(filter.is_none()); + assert!(errors.is_empty()); + } + + #[test] + fn parse_spec_blank_level_isolated_blank_comma() { + // The spec should contain zero or more comma-separated string slices, + // so this bogus spec should be interpreted as containing one blank + // string and one empty string. Both should both be treated as + // invalid, so ignored. + let ParseResult { + directives: dirs, + filter, + errors, + } = parse_spec(" ,"); // should be ignored + assert_eq!(dirs.len(), 0); + assert!(filter.is_none()); + assert!(errors.is_empty()); + } + + #[test] + fn parse_spec_global() { + // test parse_spec with no crate + let ParseResult { + directives: dirs, + filter, + errors, + } = parse_spec("warn,crate2=debug"); + assert_eq!(dirs.len(), 2); + assert_eq!(dirs[0].name, None); + assert_eq!(dirs[0].level, LevelFilter::Warn); + assert_eq!(dirs[1].name, Some("crate2".to_owned())); + assert_eq!(dirs[1].level, LevelFilter::Debug); + assert!(filter.is_none()); + assert!(errors.is_empty()); + } + + #[test] + fn parse_spec_global_bare_warn_lc() { + // test parse_spec with no crate, in isolation, all lowercase + let ParseResult { + directives: dirs, + filter, + errors, + } = parse_spec("warn"); + assert_eq!(dirs.len(), 1); + assert_eq!(dirs[0].name, None); + assert_eq!(dirs[0].level, LevelFilter::Warn); + assert!(filter.is_none()); + assert!(errors.is_empty()); + } + + #[test] + fn parse_spec_global_bare_warn_uc() { + // test parse_spec with no crate, in isolation, all uppercase + let ParseResult { + directives: dirs, + filter, + errors, + } = parse_spec("WARN"); + assert_eq!(dirs.len(), 1); + assert_eq!(dirs[0].name, None); + assert_eq!(dirs[0].level, LevelFilter::Warn); + assert!(filter.is_none()); + assert!(errors.is_empty()); + } + + #[test] + fn parse_spec_global_bare_warn_mixed() { + // test parse_spec with no crate, in isolation, mixed case + let ParseResult { + directives: dirs, + filter, + errors, + } = parse_spec("wArN"); + assert_eq!(dirs.len(), 1); + assert_eq!(dirs[0].name, None); + assert_eq!(dirs[0].level, LevelFilter::Warn); + assert!(filter.is_none()); + assert!(errors.is_empty()); + } + + #[test] + fn parse_spec_valid_filter() { + let ParseResult { + directives: dirs, + filter, + errors, + } = parse_spec("crate1::mod1=error,crate1::mod2,crate2=debug/abc"); + assert_eq!(dirs.len(), 3); + assert_eq!(dirs[0].name, Some("crate1::mod1".to_owned())); + assert_eq!(dirs[0].level, LevelFilter::Error); + + assert_eq!(dirs[1].name, Some("crate1::mod2".to_owned())); + assert_eq!(dirs[1].level, LevelFilter::max()); + + assert_eq!(dirs[2].name, Some("crate2".to_owned())); + assert_eq!(dirs[2].level, LevelFilter::Debug); + assert!(filter.is_some() && filter.unwrap().to_string() == "abc"); + assert!(errors.is_empty()); + } + + #[test] + fn parse_spec_invalid_crate_filter() { + let ParseResult { + directives: dirs, + filter, + errors, + } = parse_spec("crate1::mod1=error=warn,crate2=debug/a.c"); + + assert_eq!(dirs.len(), 1); + assert_eq!(dirs[0].name, Some("crate2".to_owned())); + assert_eq!(dirs[0].level, LevelFilter::Debug); + assert!(filter.is_some() && filter.unwrap().to_string() == "a.c"); + + assert_eq!(errors.len(), 1); + assert_data_eq!( + &errors[0], + str!["invalid logging spec 'crate1::mod1=error=warn'"] + ); + } + + #[test] + fn parse_spec_empty_with_filter() { + let ParseResult { + directives: dirs, + filter, + errors, + } = parse_spec("crate1/a*c"); + assert_eq!(dirs.len(), 1); + assert_eq!(dirs[0].name, Some("crate1".to_owned())); + assert_eq!(dirs[0].level, LevelFilter::max()); + assert!(filter.is_some() && filter.unwrap().to_string() == "a*c"); + assert!(errors.is_empty()); + } + + #[test] + fn parse_spec_with_multiple_filters() { + let ParseResult { + directives: dirs, + filter, + errors, + } = parse_spec("debug/abc/a.c"); + assert!(dirs.is_empty()); + assert!(filter.is_none()); + + assert_eq!(errors.len(), 1); + assert_data_eq!( + &errors[0], + str!["invalid logging spec 'debug/abc/a.c' (too many '/'s)"] + ); + } + + #[test] + fn parse_spec_multiple_invalid_crates() { + // test parse_spec with multiple = in specification + let ParseResult { + directives: dirs, + filter, + errors, + } = parse_spec("crate1::mod1=warn=info,crate2=debug,crate3=error=error"); + + assert_eq!(dirs.len(), 1); + assert_eq!(dirs[0].name, Some("crate2".to_owned())); + assert_eq!(dirs[0].level, LevelFilter::Debug); + assert!(filter.is_none()); + + assert_eq!(errors.len(), 2); + assert_data_eq!( + &errors[0], + str!["invalid logging spec 'crate1::mod1=warn=info'"] + ); + assert_data_eq!( + &errors[1], + str!["invalid logging spec 'crate3=error=error'"] + ); + } + + #[test] + fn parse_spec_multiple_invalid_levels() { + // test parse_spec with 'noNumber' as log level + let ParseResult { + directives: dirs, + filter, + errors, + } = parse_spec("crate1::mod1=noNumber,crate2=debug,crate3=invalid"); + + assert_eq!(dirs.len(), 1); + assert_eq!(dirs[0].name, Some("crate2".to_owned())); + assert_eq!(dirs[0].level, LevelFilter::Debug); + assert!(filter.is_none()); + + assert_eq!(errors.len(), 2); + assert_data_eq!(&errors[0], str!["invalid logging spec 'noNumber'"]); + assert_data_eq!(&errors[1], str!["invalid logging spec 'invalid'"]); + } + + #[test] + fn parse_spec_invalid_crate_and_level() { + // test parse_spec with 'noNumber' as log level + let ParseResult { + directives: dirs, + filter, + errors, + } = parse_spec("crate1::mod1=debug=info,crate2=debug,crate3=invalid"); + + assert_eq!(dirs.len(), 1); + assert_eq!(dirs[0].name, Some("crate2".to_owned())); + assert_eq!(dirs[0].level, LevelFilter::Debug); + assert!(filter.is_none()); + + assert_eq!(errors.len(), 2); + assert_data_eq!( + &errors[0], + str!["invalid logging spec 'crate1::mod1=debug=info'"] + ); + assert_data_eq!(&errors[1], str!["invalid logging spec 'invalid'"]); + } + + #[test] + fn parse_error_message_single_error() { + let error = parse_spec("crate1::mod1=debug=info,crate2=debug") + .ok() + .unwrap_err(); + assert_data_eq!( + error, + str!["error parsing logger filter: invalid logging spec 'crate1::mod1=debug=info'"] + ); + } + + #[test] + fn parse_error_message_multiple_errors() { + let error = parse_spec("crate1::mod1=debug=info,crate2=debug,crate3=invalid") + .ok() + .unwrap_err(); + assert_data_eq!( + error, + str!["error parsing logger filter: invalid logging spec 'crate1::mod1=debug=info'"] + ); + } +} diff --git a/tools/vendor/env_logger/.cargo-checksum.json b/tools/vendor/env_logger/.cargo-checksum.json new file mode 100644 index 0000000000..04173b25ed --- /dev/null +++ b/tools/vendor/env_logger/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{".cargo_vcs_info.json":"396d6543501acc95d4884021b736d30ea17e488fb56c3af0e8891ed607447fc5","Cargo.lock":"1c07d9783e0255e9b69ad894b9f143e0cc2ed3856fac1f49e587a4409fad9255","Cargo.toml":"29051071338822727668bbdeefb7f8dc1b0fbd5b479ac13b3f4d9a45106ad662","Cargo.toml.orig":"fdc1b0d7e1655536edbd58d5df922eba14c4290667bdab42a76f9c3fca800fa1","LICENSE-APACHE":"c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08","LICENSE-MIT":"6efb0476a1cc085077ed49357026d8c173bf33017278ef440f222fb9cbcb66e6","README.md":"cd2e12044c22de17bca54af54294f2f4f88763374946b5c5beed339eb0285360","examples/custom_default_format.rs":"321df9186b781ab377a9fc8ce69108c2649d2943fe26eff86cf899d4d3dd69f5","examples/custom_format.rs":"01565992d6f7d58886d8dc52a573070118d4c051d229c1f75af8e41b0ce72171","examples/default.rs":"1e1d9f5b7744861e7f84f95f607d1ae709d93261096387323a4039df31cbf1c1","examples/direct_logger.rs":"24b2e102ff1f2b71d926def70f44de25ef6e1fbfa7c7011fd10551881c6fb068","examples/filters_from_code.rs":"a2e8dbcb949a19de9228654907019e876a0689750d1c86e04fea606585a79938","examples/in_tests.rs":"b31884a7eeb8d316b55a829a7bacc45da248733ae17a263dc1af68394fb309ab","examples/syslog_friendly_format.rs":"4269a86f549253bad54c7e8aae03d5ab09959a5c9d798d3fc6c4745cd5d96cd9","src/fmt/humantime.rs":"065fafd124e935e3e7ccad6050d2d50acb7e3920ad7df6994d6f992b55e5c244","src/fmt/kv.rs":"1a15b740f4b5a67014e3627bd30b5c7583d576ba06fdf6484a2bd95f20f05eb1","src/fmt/mod.rs":"14cd7164c9098b72f5235360cc5bf27863ec1f2e4b43efa10db02e4fc3f32200","src/fmt/writer/buffer.rs":"b39b509c8ee574d04ceae582705fbc8929c3ba473b6742d91d37000b3f090bac","src/fmt/writer/mod.rs":"2a0fed474d7b6c2de0131911552bbb1f852ba14c5c42f219fc27872204ffd3b2","src/fmt/writer/target.rs":"34d65a0db699a3848d26027c08246dd2753e395841a5614bf8325f23ed08c206","src/lib.rs":"322b7055d06e9151ece5b92308694d636c2d276c083c7ecd0777138383ef0108","src/logger.rs":"fe94c63cf8ea8e0069268450f74cea62c125461d157f5fecac744536c67787bc","tests/init-twice-retains-filter.rs":"020377ef695a2cd323dce440a17265957b33518d007bf48d3e697eb796af6c47","tests/log-in-log.rs":"807d8d5e3b9a00559ec1b3d4bbd0f3d0a3b5bca56976f087f94202dd3809dfc6","tests/log_tls_dtors.rs":"b7e37f828140029c46d1dc451759c954841a823b2d779d892d57d68bf45f42e3","tests/regexp_filter.rs":"8028de3748f5d57b321eeecb787c69d63af3460afc71d6f63daa7b9522437af9"},"package":"13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f"} \ No newline at end of file diff --git a/tools/vendor/env_logger/.cargo_vcs_info.json b/tools/vendor/env_logger/.cargo_vcs_info.json new file mode 100644 index 0000000000..10dac50b89 --- /dev/null +++ b/tools/vendor/env_logger/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "f0443b26fef10f5f2ec6561e5ed670f66107836a" + }, + "path_in_vcs": "" +} \ No newline at end of file diff --git a/tools/vendor/env_logger/Cargo.lock b/tools/vendor/env_logger/Cargo.lock new file mode 100644 index 0000000000..fa822ed199 --- /dev/null +++ b/tools/vendor/env_logger/Cargo.lock @@ -0,0 +1,282 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "env_filter" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.8" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + +[[package]] +name = "jiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c163c633eb184a4ad2a5e7a5dacf12a58c830d717a7963563d4eceb4ced079f" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", +] + +[[package]] +name = "jiff-static" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc3e0019b0f5f43038cf46471b1312136f29e36f54436c6042c8f155fec8789" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "portable-atomic" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" + +[[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 = "proc-macro2" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" + +[[package]] +name = "serde" +version = "1.0.218" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.218" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e02e925281e18ffd9d640e234264753c43edc62d64b2d4cf898f1bc5e75f3fc2" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" diff --git a/tools/vendor/env_logger/Cargo.toml b/tools/vendor/env_logger/Cargo.toml new file mode 100644 index 0000000000..48f6224338 --- /dev/null +++ b/tools/vendor/env_logger/Cargo.toml @@ -0,0 +1,256 @@ +# 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.71" +name = "env_logger" +version = "0.11.8" +build = false +include = [ + "build.rs", + "src/**/*", + "Cargo.toml", + "Cargo.lock", + "LICENSE*", + "README.md", + "benches/**/*", + "examples/**/*", + "tests/**/*", +] +autolib = false +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = """ +A logging implementation for `log` which is configured via an environment +variable. +""" +readme = "README.md" +keywords = [ + "logging", + "log", + "logger", +] +categories = ["development-tools::debugging"] +license = "MIT OR Apache-2.0" +repository = "https://github.com/rust-cli/env_logger" + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = [ + "--cfg", + "docsrs", + "--generate-link-to-definition", +] + +[[package.metadata.release.pre-release-replacements]] +file = "CHANGELOG.md" +min = 1 +replace = "{{version}}" +search = "Unreleased" + +[[package.metadata.release.pre-release-replacements]] +exactly = 1 +file = "CHANGELOG.md" +replace = "...{{tag_name}}" +search = '\.\.\.HEAD' + +[[package.metadata.release.pre-release-replacements]] +file = "CHANGELOG.md" +min = 1 +replace = "{{date}}" +search = "ReleaseDate" + +[[package.metadata.release.pre-release-replacements]] +exactly = 1 +file = "CHANGELOG.md" +replace = """ + +## [Unreleased] - ReleaseDate +""" +search = "" + +[[package.metadata.release.pre-release-replacements]] +exactly = 1 +file = "CHANGELOG.md" +replace = """ + +[Unreleased]: https://github.com/rust-cli/env_logger/compare/{{tag_name}}...HEAD""" +search = "" + +[features] +auto-color = [ + "color", + "anstream/auto", +] +color = [ + "dep:anstream", + "dep:anstyle", +] +default = [ + "auto-color", + "humantime", + "regex", +] +humantime = ["dep:jiff"] +kv = ["log/kv"] +regex = ["env_filter/regex"] +unstable-kv = ["kv"] + +[lib] +name = "env_logger" +path = "src/lib.rs" + +[[example]] +name = "custom_default_format" +path = "examples/custom_default_format.rs" + +[[example]] +name = "custom_format" +path = "examples/custom_format.rs" + +[[example]] +name = "default" +path = "examples/default.rs" + +[[example]] +name = "direct_logger" +path = "examples/direct_logger.rs" + +[[example]] +name = "filters_from_code" +path = "examples/filters_from_code.rs" + +[[example]] +name = "in_tests" +path = "examples/in_tests.rs" + +[[example]] +name = "syslog_friendly_format" +path = "examples/syslog_friendly_format.rs" + +[[test]] +name = "init-twice-retains-filter" +path = "tests/init-twice-retains-filter.rs" +harness = false + +[[test]] +name = "log-in-log" +path = "tests/log-in-log.rs" +harness = false + +[[test]] +name = "log_tls_dtors" +path = "tests/log_tls_dtors.rs" +harness = false + +[[test]] +name = "regexp_filter" +path = "tests/regexp_filter.rs" +harness = false + +[dependencies.anstream] +version = "0.6.11" +features = ["wincon"] +optional = true +default-features = false + +[dependencies.anstyle] +version = "1.0.6" +optional = true + +[dependencies.env_filter] +version = "0.1.0" +default-features = false + +[dependencies.jiff] +version = "0.2.3" +features = ["std"] +optional = true +default-features = false + +[dependencies.log] +version = "0.4.21" +features = ["std"] + +[lints.clippy] +bool_assert_comparison = "allow" +branches_sharing_code = "allow" +checked_conversions = "warn" +collapsible_else_if = "allow" +create_dir = "warn" +dbg_macro = "warn" +debug_assert_with_mut_call = "warn" +doc_markdown = "warn" +empty_enum = "warn" +enum_glob_use = "warn" +expl_impl_clone_on_copy = "warn" +explicit_deref_methods = "warn" +explicit_into_iter_loop = "warn" +fallible_impl_from = "warn" +filter_map_next = "warn" +flat_map_option = "warn" +float_cmp_const = "warn" +fn_params_excessive_bools = "warn" +from_iter_instead_of_collect = "warn" +if_same_then_else = "allow" +implicit_clone = "warn" +imprecise_flops = "warn" +inconsistent_struct_constructor = "warn" +inefficient_to_string = "warn" +infinite_loop = "warn" +invalid_upcast_comparisons = "warn" +large_digit_groups = "warn" +large_stack_arrays = "warn" +large_types_passed_by_value = "warn" +let_and_return = "allow" +linkedlist = "warn" +lossy_float_literal = "warn" +macro_use_imports = "warn" +mem_forget = "warn" +mutex_integer = "warn" +needless_continue = "warn" +needless_for_each = "warn" +negative_feature_names = "warn" +path_buf_push_overwrite = "warn" +ptr_as_ptr = "warn" +rc_mutex = "warn" +redundant_feature_names = "warn" +ref_option_ref = "warn" +rest_pat_in_fully_bound_structs = "warn" +result_large_err = "allow" +same_functions_in_if_condition = "warn" +self_named_module_files = "warn" +semicolon_if_nothing_returned = "warn" +str_to_string = "warn" +string_add = "warn" +string_add_assign = "warn" +string_lit_as_bytes = "warn" +string_to_string = "warn" +todo = "warn" +trait_duplication_in_bounds = "warn" +uninlined_format_args = "warn" +verbose_file_reads = "warn" +wildcard_imports = "warn" +zero_sized_map_values = "warn" + +[lints.rust] +unreachable_pub = "warn" +unsafe_op_in_unsafe_fn = "warn" +unused_lifetimes = "warn" +unused_macro_rules = "warn" +unused_qualifications = "warn" + +[lints.rust.rust_2018_idioms] +level = "warn" +priority = -1 diff --git a/tools/vendor/env_logger/Cargo.toml.orig b/tools/vendor/env_logger/Cargo.toml.orig new file mode 100644 index 0000000000..a3e5c8aa35 --- /dev/null +++ b/tools/vendor/env_logger/Cargo.toml.orig @@ -0,0 +1,153 @@ +[workspace] +resolver = "2" +members = ["crates/*"] + +[workspace.package] +repository = "https://github.com/rust-cli/env_logger" +license = "MIT OR Apache-2.0" +edition = "2021" +rust-version = "1.71" # MSRV +include = [ + "build.rs", + "src/**/*", + "Cargo.toml", + "Cargo.lock", + "LICENSE*", + "README.md", + "benches/**/*", + "examples/**/*", + "tests/**/*", +] + +[workspace.lints.rust] +rust_2018_idioms = { level = "warn", priority = -1 } +unreachable_pub = "warn" +unsafe_op_in_unsafe_fn = "warn" +unused_lifetimes = "warn" +unused_macro_rules = "warn" +unused_qualifications = "warn" + +[workspace.lints.clippy] +bool_assert_comparison = "allow" +branches_sharing_code = "allow" +checked_conversions = "warn" +collapsible_else_if = "allow" +create_dir = "warn" +dbg_macro = "warn" +debug_assert_with_mut_call = "warn" +doc_markdown = "warn" +empty_enum = "warn" +enum_glob_use = "warn" +expl_impl_clone_on_copy = "warn" +explicit_deref_methods = "warn" +explicit_into_iter_loop = "warn" +fallible_impl_from = "warn" +filter_map_next = "warn" +flat_map_option = "warn" +float_cmp_const = "warn" +fn_params_excessive_bools = "warn" +from_iter_instead_of_collect = "warn" +if_same_then_else = "allow" +implicit_clone = "warn" +imprecise_flops = "warn" +inconsistent_struct_constructor = "warn" +inefficient_to_string = "warn" +infinite_loop = "warn" +invalid_upcast_comparisons = "warn" +large_digit_groups = "warn" +large_stack_arrays = "warn" +large_types_passed_by_value = "warn" +let_and_return = "allow" # sometimes good to name what you are returning +linkedlist = "warn" +lossy_float_literal = "warn" +macro_use_imports = "warn" +mem_forget = "warn" +mutex_integer = "warn" +needless_continue = "warn" +needless_for_each = "warn" +negative_feature_names = "warn" +path_buf_push_overwrite = "warn" +ptr_as_ptr = "warn" +rc_mutex = "warn" +redundant_feature_names = "warn" +ref_option_ref = "warn" +rest_pat_in_fully_bound_structs = "warn" +result_large_err = "allow" +same_functions_in_if_condition = "warn" +self_named_module_files = "warn" +semicolon_if_nothing_returned = "warn" +str_to_string = "warn" +string_add = "warn" +string_add_assign = "warn" +string_lit_as_bytes = "warn" +string_to_string = "warn" +todo = "warn" +trait_duplication_in_bounds = "warn" +uninlined_format_args = "warn" +verbose_file_reads = "warn" +wildcard_imports = "warn" +zero_sized_map_values = "warn" + +[package] +name = "env_logger" +version = "0.11.8" +description = """ +A logging implementation for `log` which is configured via an environment +variable. +""" +categories = ["development-tools::debugging"] +keywords = ["logging", "log", "logger"] +repository.workspace = true +license.workspace = true +edition.workspace = true +rust-version.workspace = true +include.workspace = true + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs", "--generate-link-to-definition"] + +[package.metadata.release] +pre-release-replacements = [ + {file="CHANGELOG.md", search="Unreleased", replace="{{version}}", min=1}, + {file="CHANGELOG.md", search="\\.\\.\\.HEAD", replace="...{{tag_name}}", exactly=1}, + {file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}", min=1}, + {file="CHANGELOG.md", search="", replace="\n## [Unreleased] - ReleaseDate\n", exactly=1}, + {file="CHANGELOG.md", search="", replace="\n[Unreleased]: https://github.com/rust-cli/env_logger/compare/{{tag_name}}...HEAD", exactly=1}, +] + +[features] +default = ["auto-color", "humantime", "regex"] +color = ["dep:anstream", "dep:anstyle"] +auto-color = ["color", "anstream/auto"] +humantime = ["dep:jiff"] +regex = ["env_filter/regex"] +kv = ["log/kv"] +# Deprecated +unstable-kv = ["kv"] + +[dependencies] +log = { version = "0.4.21", features = ["std"] } +env_filter = { version = "0.1.0", path = "crates/env_filter", default-features = false } +jiff = { version = "0.2.3", default-features = false, features = ["std"], optional = true } +anstream = { version = "0.6.11", default-features = false, features = ["wincon"], optional = true } +anstyle = { version = "1.0.6", optional = true } + +[[test]] +name = "regexp_filter" +harness = false + +[[test]] +name = "log-in-log" +harness = false + +[[test]] +name = "log_tls_dtors" +harness = false + +[[test]] +name = "init-twice-retains-filter" +harness = false + +[lints] +workspace = true diff --git a/tools/vendor/env_logger/LICENSE-APACHE b/tools/vendor/env_logger/LICENSE-APACHE new file mode 100644 index 0000000000..8f71f43fee --- /dev/null +++ b/tools/vendor/env_logger/LICENSE-APACHE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/tools/vendor/env_logger/LICENSE-MIT b/tools/vendor/env_logger/LICENSE-MIT new file mode 100644 index 0000000000..a2d01088b6 --- /dev/null +++ b/tools/vendor/env_logger/LICENSE-MIT @@ -0,0 +1,19 @@ +Copyright (c) Individual contributors + +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. diff --git a/tools/vendor/env_logger/README.md b/tools/vendor/env_logger/README.md new file mode 100644 index 0000000000..d18a868e7c --- /dev/null +++ b/tools/vendor/env_logger/README.md @@ -0,0 +1,174 @@ +# env_logger + +[![crates.io](https://img.shields.io/crates/v/env_logger.svg)](https://crates.io/crates/env_logger) +[![Documentation](https://docs.rs/env_logger/badge.svg)](https://docs.rs/env_logger) + +Implements a logger that can be configured via environment variables. + +## Usage + +### In libraries + +`env_logger` makes sense when used in executables (binary projects). Libraries should use the [`log`](https://docs.rs/log) crate instead. + +### In executables + +It must be added along with `log` to the project dependencies: + +```console +$ cargo add log env_logger +``` + +`env_logger` must be initialized as early as possible in the project. After it's initialized, you can use the `log` macros to do actual logging. + +```rust +use log::info; + +fn main() { + env_logger::init(); + + info!("starting up"); + + // ... +} +``` + +Then when running the executable, specify a value for the **`RUST_LOG`** +environment variable that corresponds with the log messages you want to show. + +```bash +$ RUST_LOG=info ./main +[2018-11-03T06:09:06Z INFO default] starting up +``` + +The letter case is not significant for the logging level names; e.g., `debug`, +`DEBUG`, and `dEbuG` all represent the same logging level. Therefore, the +previous example could also have been written this way, specifying the log +level as `INFO` rather than as `info`: + +```bash +$ RUST_LOG=INFO ./main +[2018-11-03T06:09:06Z INFO default] starting up +``` + +So which form should you use? For consistency, our convention is to use lower +case names. Where our docs do use other forms, they do so in the context of +specific examples, so you won't be surprised if you see similar usage in the +wild. + +The log levels that may be specified correspond to the [`log::Level`][level-enum] +enum from the `log` crate. They are: + + * `error` + * `warn` + * `info` + * `debug` + * `trace` + +[level-enum]: https://docs.rs/log/latest/log/enum.Level.html "log::Level (docs.rs)" + +There is also a pseudo logging level, `off`, which may be specified to disable +all logging for a given module or for the entire application. As with the +logging levels, the letter case is not significant. + +`env_logger` can be configured in other ways besides an environment variable. See [the examples](https://github.com/rust-cli/env_logger/tree/main/examples) for more approaches. + +### In tests + +Tests can use the `env_logger` crate to see log messages generated during that test: + +```console +$ cargo add log +$ cargo add --dev env_logger +``` + +```rust +fn add_one(num: i32) -> i32 { + info!("add_one called with {}", num); + num + 1 +} + +#[cfg(test)] +mod tests { + use super::*; + use log::info; + + fn init() { + let _ = env_logger::builder().is_test(true).try_init(); + } + + #[test] + fn it_adds_one() { + init(); + + info!("can log from the test too"); + assert_eq!(3, add_one(2)); + } + + #[test] + fn it_handles_negative_numbers() { + init(); + + info!("logging from another test"); + assert_eq!(-7, add_one(-8)); + } +} +``` + +Assuming the module under test is called `my_lib`, running the tests with the +`RUST_LOG` filtering to info messages from this module looks like: + +```bash +$ RUST_LOG=my_lib=info cargo test + Running target/debug/my_lib-... + +running 2 tests +[INFO my_lib::tests] logging from another test +[INFO my_lib] add_one called with -8 +test tests::it_handles_negative_numbers ... ok +[INFO my_lib::tests] can log from the test too +[INFO my_lib] add_one called with 2 +test tests::it_adds_one ... ok + +test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured +``` + +Note that `env_logger::try_init()` needs to be called in each test in which you +want to enable logging. Additionally, the default behavior of tests to +run in parallel means that logging output may be interleaved with test output. +Either run tests in a single thread by specifying `RUST_TEST_THREADS=1` or by +running one test by specifying its name as an argument to the test binaries as +directed by the `cargo test` help docs: + +```bash +$ RUST_LOG=my_lib=info cargo test it_adds_one + Running target/debug/my_lib-... + +running 1 test +[INFO my_lib::tests] can log from the test too +[INFO my_lib] add_one called with 2 +test tests::it_adds_one ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured +``` + +## Configuring log target + +By default, `env_logger` logs to stderr. If you want to log to stdout instead, +you can use the `Builder` to change the log target: + +```rust +use std::env; +use env_logger::{Builder, Target}; + +let mut builder = Builder::from_default_env(); +builder.target(Target::Stdout); + +builder.init(); +``` + +## Stability of the default format + +The default format won't optimise for long-term stability, and explicitly makes no guarantees about the stability of its output across major, minor or patch version bumps during `0.x`. + +If you want to capture or interpret the output of `env_logger` programmatically then you should use a custom format. diff --git a/tools/vendor/env_logger/examples/custom_default_format.rs b/tools/vendor/env_logger/examples/custom_default_format.rs new file mode 100644 index 0000000000..81a1ef41ff --- /dev/null +++ b/tools/vendor/env_logger/examples/custom_default_format.rs @@ -0,0 +1,39 @@ +/*! +Disabling parts of the default format. + +Before running this example, try setting the `MY_LOG_LEVEL` environment variable to `info`: + +```no_run,shell +$ export MY_LOG_LEVEL='info' +``` + +Also try setting the `MY_LOG_STYLE` environment variable to `never` to disable colors +or `auto` to enable them: + +```no_run,shell +$ export MY_LOG_STYLE=never +``` + +If you want to control the logging output completely, see the `custom_logger` example. +*/ + +use log::info; + +use env_logger::{Builder, Env}; + +fn init_logger() { + let env = Env::default() + .filter("MY_LOG_LEVEL") + .write_style("MY_LOG_STYLE"); + + Builder::from_env(env) + .format_level(false) + .format_timestamp_nanos() + .init(); +} + +fn main() { + init_logger(); + + info!("a log from `MyLogger`"); +} diff --git a/tools/vendor/env_logger/examples/custom_format.rs b/tools/vendor/env_logger/examples/custom_format.rs new file mode 100644 index 0000000000..80b9aaa3ed --- /dev/null +++ b/tools/vendor/env_logger/examples/custom_format.rs @@ -0,0 +1,53 @@ +/*! +Changing the default logging format. + +Before running this example, try setting the `MY_LOG_LEVEL` environment variable to `info`: + +```no_run,shell +$ export MY_LOG_LEVEL='info' +``` + +Also try setting the `MY_LOG_STYLE` environment variable to `never` to disable colors +or `auto` to enable them: + +```no_run,shell +$ export MY_LOG_STYLE=never +``` + +If you want to control the logging output completely, see the `custom_logger` example. +*/ + +#[cfg(all(feature = "color", feature = "humantime"))] +fn main() { + use env_logger::{Builder, Env}; + + use std::io::Write; + + fn init_logger() { + let env = Env::default() + .filter("MY_LOG_LEVEL") + .write_style("MY_LOG_STYLE"); + + Builder::from_env(env) + .format(|buf, record| { + // We are reusing `anstyle` but there are `anstyle-*` crates to adapt it to your + // preferred styling crate. + let warn_style = buf.default_level_style(log::Level::Warn); + let timestamp = buf.timestamp(); + + writeln!( + buf, + "My formatted log ({timestamp}): {warn_style}{}{warn_style:#}", + record.args() + ) + }) + .init(); + } + + init_logger(); + + log::info!("a log from `MyLogger`"); +} + +#[cfg(not(all(feature = "color", feature = "humantime")))] +fn main() {} diff --git a/tools/vendor/env_logger/examples/default.rs b/tools/vendor/env_logger/examples/default.rs new file mode 100644 index 0000000000..9af2a6e2f8 --- /dev/null +++ b/tools/vendor/env_logger/examples/default.rs @@ -0,0 +1,37 @@ +/*! +Using `env_logger`. + +Before running this example, try setting the `MY_LOG_LEVEL` environment variable to `info`: + +```no_run,shell +$ export MY_LOG_LEVEL='info' +``` + +Also try setting the `MY_LOG_STYLE` environment variable to `never` to disable colors +or `auto` to enable them: + +```no_run,shell +$ export MY_LOG_STYLE=never +``` +*/ + +use log::{debug, error, info, trace, warn}; + +use env_logger::Env; + +fn main() { + // The `Env` lets us tweak what the environment + // variables to read are and what the default + // value is if they're missing + let env = Env::default() + .filter_or("MY_LOG_LEVEL", "trace") + .write_style_or("MY_LOG_STYLE", "always"); + + env_logger::init_from_env(env); + + trace!("some trace log"); + debug!("some debug log"); + info!("some information log"); + warn!("some warning log"); + error!("some error log"); +} diff --git a/tools/vendor/env_logger/examples/direct_logger.rs b/tools/vendor/env_logger/examples/direct_logger.rs new file mode 100644 index 0000000000..9eae1fa988 --- /dev/null +++ b/tools/vendor/env_logger/examples/direct_logger.rs @@ -0,0 +1,47 @@ +/*! +Using `env_logger::Logger` and the `log::Log` trait directly. + +This example doesn't rely on environment variables, or having a static logger installed. +*/ + +use env_logger::{Builder, WriteStyle}; + +use log::{Level, LevelFilter, Log, MetadataBuilder, Record}; + +#[cfg(feature = "kv")] +static KVS: (&str, &str) = ("test", "something"); + +fn record() -> Record<'static> { + let error_metadata = MetadataBuilder::new() + .target("myApp") + .level(Level::Error) + .build(); + + let mut builder = Record::builder(); + builder + .metadata(error_metadata) + .args(format_args!("Error!")) + .line(Some(433)) + .file(Some("app.rs")) + .module_path(Some("server")); + #[cfg(feature = "kv")] + { + builder.key_values(&KVS); + } + builder.build() +} + +fn main() { + let stylish_logger = Builder::new() + .filter(None, LevelFilter::Error) + .write_style(WriteStyle::Always) + .build(); + + let unstylish_logger = Builder::new() + .filter(None, LevelFilter::Error) + .write_style(WriteStyle::Never) + .build(); + + stylish_logger.log(&record()); + unstylish_logger.log(&record()); +} diff --git a/tools/vendor/env_logger/examples/filters_from_code.rs b/tools/vendor/env_logger/examples/filters_from_code.rs new file mode 100644 index 0000000000..b9e6258d6a --- /dev/null +++ b/tools/vendor/env_logger/examples/filters_from_code.rs @@ -0,0 +1,17 @@ +/*! +Specify logging filters in code instead of using an environment variable. +*/ + +use env_logger::Builder; + +use log::{debug, error, info, trace, warn, LevelFilter}; + +fn main() { + Builder::new().filter_level(LevelFilter::max()).init(); + + trace!("some trace log"); + debug!("some debug log"); + info!("some information log"); + warn!("some warning log"); + error!("some error log"); +} diff --git a/tools/vendor/env_logger/examples/in_tests.rs b/tools/vendor/env_logger/examples/in_tests.rs new file mode 100644 index 0000000000..1fff5c96b4 --- /dev/null +++ b/tools/vendor/env_logger/examples/in_tests.rs @@ -0,0 +1,53 @@ +/*! +Using `env_logger` in tests. + +Log events will be captured by `cargo` and only printed if the test fails. +You can run this example by calling: + +```text +cargo test --example in_tests +``` + +You should see the `it_does_not_work` test fail and include its log output. +*/ + +fn main() {} + +#[cfg(test)] +mod tests { + use log::debug; + + fn init_logger() { + let _ = env_logger::builder() + // Include all events in tests + .filter_level(log::LevelFilter::max()) + // Ensure events are captured by `cargo test` + .is_test(true) + // Ignore errors initializing the logger if tests race to configure it + .try_init(); + } + + #[test] + fn it_works() { + init_logger(); + + let a = 1; + let b = 2; + + debug!("checking whether {} + {} = 3", a, b); + + assert_eq!(3, a + b); + } + + #[test] + fn it_does_not_work() { + init_logger(); + + let a = 1; + let b = 2; + + debug!("checking whether {} + {} = 6", a, b); + + assert_eq!(6, a + b); + } +} diff --git a/tools/vendor/env_logger/examples/syslog_friendly_format.rs b/tools/vendor/env_logger/examples/syslog_friendly_format.rs new file mode 100644 index 0000000000..9809ab3f87 --- /dev/null +++ b/tools/vendor/env_logger/examples/syslog_friendly_format.rs @@ -0,0 +1,24 @@ +use std::io::Write; + +fn main() { + match std::env::var("RUST_LOG_STYLE") { + Ok(s) if s == "SYSTEMD" => env_logger::builder() + .format(|buf, record| { + writeln!( + buf, + "<{}>{}: {}", + match record.level() { + log::Level::Error => 3, + log::Level::Warn => 4, + log::Level::Info => 6, + log::Level::Debug => 7, + log::Level::Trace => 7, + }, + record.target(), + record.args() + ) + }) + .init(), + _ => env_logger::init(), + }; +} diff --git a/tools/vendor/env_logger/src/fmt/humantime.rs b/tools/vendor/env_logger/src/fmt/humantime.rs new file mode 100644 index 0000000000..a6bab88e8b --- /dev/null +++ b/tools/vendor/env_logger/src/fmt/humantime.rs @@ -0,0 +1,134 @@ +use std::fmt; +use std::time::SystemTime; + +use crate::fmt::{Formatter, TimestampPrecision}; + +impl Formatter { + /// Get a [`Timestamp`] for the current date and time in UTC. + /// + /// # Examples + /// + /// Include the current timestamp with the log record: + /// + /// ``` + /// use std::io::Write; + /// + /// let mut builder = env_logger::Builder::new(); + /// + /// builder.format(|buf, record| { + /// let ts = buf.timestamp(); + /// + /// writeln!(buf, "{}: {}: {}", ts, record.level(), record.args()) + /// }); + /// ``` + pub fn timestamp(&self) -> Timestamp { + Timestamp { + time: SystemTime::now(), + precision: TimestampPrecision::Seconds, + } + } + + /// Get a [`Timestamp`] for the current date and time in UTC with full + /// second precision. + pub fn timestamp_seconds(&self) -> Timestamp { + Timestamp { + time: SystemTime::now(), + precision: TimestampPrecision::Seconds, + } + } + + /// Get a [`Timestamp`] for the current date and time in UTC with + /// millisecond precision. + pub fn timestamp_millis(&self) -> Timestamp { + Timestamp { + time: SystemTime::now(), + precision: TimestampPrecision::Millis, + } + } + + /// Get a [`Timestamp`] for the current date and time in UTC with + /// microsecond precision. + pub fn timestamp_micros(&self) -> Timestamp { + Timestamp { + time: SystemTime::now(), + precision: TimestampPrecision::Micros, + } + } + + /// Get a [`Timestamp`] for the current date and time in UTC with + /// nanosecond precision. + pub fn timestamp_nanos(&self) -> Timestamp { + Timestamp { + time: SystemTime::now(), + precision: TimestampPrecision::Nanos, + } + } +} + +/// An [RFC3339] formatted timestamp. +/// +/// The timestamp implements [`Display`] and can be written to a [`Formatter`]. +/// +/// [RFC3339]: https://www.ietf.org/rfc/rfc3339.txt +/// [`Display`]: std::fmt::Display +pub struct Timestamp { + time: SystemTime, + precision: TimestampPrecision, +} + +impl fmt::Debug for Timestamp { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + /// A `Debug` wrapper for `Timestamp` that uses the `Display` implementation. + struct TimestampValue<'a>(&'a Timestamp); + + impl fmt::Debug for TimestampValue<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&self.0, f) + } + } + + f.debug_tuple("Timestamp") + .field(&TimestampValue(self)) + .finish() + } +} + +impl fmt::Display for Timestamp { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Ok(ts) = jiff::Timestamp::try_from(self.time) else { + return Err(fmt::Error); + }; + + match self.precision { + TimestampPrecision::Seconds => write!(f, "{ts:.0}"), + TimestampPrecision::Millis => write!(f, "{ts:.3}"), + TimestampPrecision::Micros => write!(f, "{ts:.6}"), + TimestampPrecision::Nanos => write!(f, "{ts:.9}"), + } + } +} + +#[cfg(test)] +mod tests { + use super::Timestamp; + use crate::TimestampPrecision; + + #[test] + fn test_display_timestamp() { + let mut ts = Timestamp { + time: std::time::SystemTime::UNIX_EPOCH, + precision: TimestampPrecision::Nanos, + }; + + assert_eq!("1970-01-01T00:00:00.000000000Z", format!("{ts}")); + + ts.precision = TimestampPrecision::Micros; + assert_eq!("1970-01-01T00:00:00.000000Z", format!("{ts}")); + + ts.precision = TimestampPrecision::Millis; + assert_eq!("1970-01-01T00:00:00.000Z", format!("{ts}")); + + ts.precision = TimestampPrecision::Seconds; + assert_eq!("1970-01-01T00:00:00Z", format!("{ts}")); + } +} diff --git a/tools/vendor/env_logger/src/fmt/kv.rs b/tools/vendor/env_logger/src/fmt/kv.rs new file mode 100644 index 0000000000..cbb7daeca9 --- /dev/null +++ b/tools/vendor/env_logger/src/fmt/kv.rs @@ -0,0 +1,69 @@ +use std::io::{self, Write}; + +#[cfg(feature = "color")] +use super::WriteStyle; +use super::{Formatter, StyledValue}; +#[cfg(feature = "color")] +use anstyle::Style; +use log::kv::{Error, Key, Source, Value, VisitSource}; + +/// Format function for serializing key/value pairs +/// +/// This function determines how key/value pairs for structured logs are serialized within the default +/// format. +pub(crate) type KvFormatFn = dyn Fn(&mut Formatter, &dyn Source) -> io::Result<()> + Sync + Send; + +/// Null Key Value Format +/// +/// This function is intended to be passed to +/// [`Builder::format_key_values`](crate::Builder::format_key_values). +/// +/// This key value format simply ignores any key/value fields and doesn't include them in the +/// output. +pub fn hidden_kv_format(_formatter: &mut Formatter, _fields: &dyn Source) -> io::Result<()> { + Ok(()) +} + +/// Default Key Value Format +/// +/// This function is intended to be passed to +/// [`Builder::format_key_values`](crate::Builder::format_key_values). +/// +/// This is the default key/value format. Which uses an "=" as the separator between the key and +/// value and a " " between each pair. +/// +/// For example: `ip=127.0.0.1 port=123456 path=/example` +pub fn default_kv_format(formatter: &mut Formatter, fields: &dyn Source) -> io::Result<()> { + fields + .visit(&mut DefaultVisitSource(formatter)) + .map_err(|e| io::Error::new(io::ErrorKind::Other, e)) +} + +struct DefaultVisitSource<'a>(&'a mut Formatter); + +impl<'kvs> VisitSource<'kvs> for DefaultVisitSource<'_> { + fn visit_pair(&mut self, key: Key<'_>, value: Value<'kvs>) -> Result<(), Error> { + write!(self.0, " {}={}", self.style_key(key), value)?; + Ok(()) + } +} + +impl DefaultVisitSource<'_> { + fn style_key<'k>(&self, text: Key<'k>) -> StyledValue> { + #[cfg(feature = "color")] + { + StyledValue { + style: if self.0.write_style == WriteStyle::Never { + Style::new() + } else { + Style::new().italic() + }, + value: text, + } + } + #[cfg(not(feature = "color"))] + { + text + } + } +} diff --git a/tools/vendor/env_logger/src/fmt/mod.rs b/tools/vendor/env_logger/src/fmt/mod.rs new file mode 100644 index 0000000000..bd2a2b8fee --- /dev/null +++ b/tools/vendor/env_logger/src/fmt/mod.rs @@ -0,0 +1,1003 @@ +//! Formatting for log records. +//! +//! This module contains a [`Formatter`] that can be used to format log records +//! into without needing temporary allocations. Usually you won't need to worry +//! about the contents of this module and can use the `Formatter` like an ordinary +//! [`Write`]. +//! +//! # Formatting log records +//! +//! The format used to print log records can be customised using the [`Builder::format`] +//! method. +//! +//! Terminal styling is done through ANSI escape codes and will be adapted to the capabilities of +//! the target stream.s +//! +//! For example, you could use one of: +//! - [anstyle](https://docs.rs/anstyle) is a minimal, runtime string styling API and is re-exported as [`style`] +//! - [owo-colors](https://docs.rs/owo-colors) is a feature rich runtime string styling API +//! - [color-print](https://docs.rs/color-print) for feature-rich compile-time styling API +//! +//! See also [`Formatter::default_level_style`] +//! +//! ``` +//! use std::io::Write; +//! +//! let mut builder = env_logger::Builder::new(); +//! +//! builder.format(|buf, record| { +//! writeln!(buf, "{}: {}", +//! record.level(), +//! record.args()) +//! }); +//! ``` +//! +//! # Key Value arguments +//! +//! If the `kv` feature is enabled, then the default format will include key values from +//! the log by default, but this can be disabled by calling [`Builder::format_key_values`] +//! with [`hidden_kv_format`] as the format function. +//! +//! The way these keys and values are formatted can also be customized with a separate format +//! function that is called by the default format with [`Builder::format_key_values`]. +//! +//! ``` +//! # #[cfg(feature= "kv")] +//! # { +//! use log::info; +//! env_logger::init(); +//! info!(x="45"; "Some message"); +//! info!(x="12"; "Another message {x}", x="12"); +//! # } +//! ``` +//! +//! See . +//! +//! [`Builder::format`]: crate::Builder::format +//! [`Write`]: std::io::Write +//! [`Builder::format_key_values`]: crate::Builder::format_key_values + +use std::cell::RefCell; +use std::fmt::Display; +use std::io::prelude::Write; +use std::rc::Rc; +use std::{fmt, io, mem}; + +#[cfg(feature = "color")] +use log::Level; +use log::Record; + +#[cfg(feature = "humantime")] +mod humantime; +#[cfg(feature = "kv")] +mod kv; +pub(crate) mod writer; + +#[cfg(feature = "color")] +pub use anstyle as style; + +#[cfg(feature = "humantime")] +pub use self::humantime::Timestamp; +#[cfg(feature = "kv")] +pub use self::kv::*; +pub use self::writer::Target; +pub use self::writer::WriteStyle; + +use self::writer::{Buffer, Writer}; + +/// Formatting precision of timestamps. +/// +/// Seconds give precision of full seconds, milliseconds give thousands of a +/// second (3 decimal digits), microseconds are millionth of a second (6 decimal +/// digits) and nanoseconds are billionth of a second (9 decimal digits). +#[allow(clippy::exhaustive_enums)] // compatibility +#[derive(Copy, Clone, Debug)] +pub enum TimestampPrecision { + /// Full second precision (0 decimal digits) + Seconds, + /// Millisecond precision (3 decimal digits) + Millis, + /// Microsecond precision (6 decimal digits) + Micros, + /// Nanosecond precision (9 decimal digits) + Nanos, +} + +/// The default timestamp precision is seconds. +impl Default for TimestampPrecision { + fn default() -> Self { + TimestampPrecision::Seconds + } +} + +/// A formatter to write logs into. +/// +/// `Formatter` implements the standard [`Write`] trait for writing log records. +/// It also supports terminal styling using ANSI escape codes. +/// +/// # Examples +/// +/// Use the [`writeln`] macro to format a log record. +/// An instance of a `Formatter` is passed to an `env_logger` format as `buf`: +/// +/// ``` +/// use std::io::Write; +/// +/// let mut builder = env_logger::Builder::new(); +/// +/// builder.format(|buf, record| writeln!(buf, "{}: {}", record.level(), record.args())); +/// ``` +/// +/// [`Write`]: std::io::Write +/// [`writeln`]: std::writeln +pub struct Formatter { + buf: Rc>, + write_style: WriteStyle, +} + +impl Formatter { + pub(crate) fn new(writer: &Writer) -> Self { + Formatter { + buf: Rc::new(RefCell::new(writer.buffer())), + write_style: writer.write_style(), + } + } + + pub(crate) fn write_style(&self) -> WriteStyle { + self.write_style + } + + pub(crate) fn print(&self, writer: &Writer) -> io::Result<()> { + writer.print(&self.buf.borrow()) + } + + pub(crate) fn clear(&mut self) { + self.buf.borrow_mut().clear(); + } +} + +#[cfg(feature = "color")] +impl Formatter { + /// Get the default [`style::Style`] for the given level. + /// + /// The style can be used to print other values besides the level. + /// + /// See [`style`] for how to adapt it to the styling crate of your choice + pub fn default_level_style(&self, level: Level) -> style::Style { + if self.write_style == WriteStyle::Never { + style::Style::new() + } else { + match level { + Level::Trace => style::AnsiColor::Cyan.on_default(), + Level::Debug => style::AnsiColor::Blue.on_default(), + Level::Info => style::AnsiColor::Green.on_default(), + Level::Warn => style::AnsiColor::Yellow.on_default(), + Level::Error => style::AnsiColor::Red + .on_default() + .effects(style::Effects::BOLD), + } + } + } +} + +impl Write for Formatter { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.buf.borrow_mut().write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + self.buf.borrow_mut().flush() + } +} + +impl fmt::Debug for Formatter { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let buf = self.buf.borrow(); + f.debug_struct("Formatter") + .field("buf", &buf) + .field("write_style", &self.write_style) + .finish() + } +} + +pub(crate) trait RecordFormat { + fn format(&self, formatter: &mut Formatter, record: &Record<'_>) -> io::Result<()>; +} + +impl RecordFormat for F +where + F: Fn(&mut Formatter, &Record<'_>) -> io::Result<()>, +{ + fn format(&self, formatter: &mut Formatter, record: &Record<'_>) -> io::Result<()> { + (self)(formatter, record) + } +} + +pub(crate) type FormatFn = Box; + +#[derive(Default)] +pub(crate) struct Builder { + pub(crate) default_format: ConfigurableFormat, + pub(crate) custom_format: Option, + built: bool, +} + +impl Builder { + /// Convert the format into a callable function. + /// + /// If the `custom_format` is `Some`, then any `default_format` switches are ignored. + /// If the `custom_format` is `None`, then a default format is returned. + /// Any `default_format` switches set to `false` won't be written by the format. + pub(crate) fn build(&mut self) -> FormatFn { + assert!(!self.built, "attempt to re-use consumed builder"); + + let built = mem::replace( + self, + Builder { + built: true, + ..Default::default() + }, + ); + + if let Some(fmt) = built.custom_format { + fmt + } else { + Box::new(built.default_format) + } + } +} + +#[cfg(feature = "color")] +type SubtleStyle = StyledValue<&'static str>; +#[cfg(not(feature = "color"))] +type SubtleStyle = &'static str; + +/// A value that can be printed using the given styles. +#[cfg(feature = "color")] +struct StyledValue { + style: style::Style, + value: T, +} + +#[cfg(feature = "color")] +impl Display for StyledValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let style = self.style; + + // We need to make sure `f`s settings don't get passed onto the styling but do get passed + // to the value + write!(f, "{style}")?; + self.value.fmt(f)?; + write!(f, "{style:#}")?; + Ok(()) + } +} + +#[cfg(not(feature = "color"))] +type StyledValue = T; + +/// A [custom format][crate::Builder::format] with settings for which fields to show +pub struct ConfigurableFormat { + // This format needs to work with any combination of crate features. + pub(crate) timestamp: Option, + pub(crate) module_path: bool, + pub(crate) target: bool, + pub(crate) level: bool, + pub(crate) source_file: bool, + pub(crate) source_line_number: bool, + pub(crate) indent: Option, + pub(crate) suffix: &'static str, + #[cfg(feature = "kv")] + pub(crate) kv_format: Option>, +} + +impl ConfigurableFormat { + /// Format the [`Record`] as configured for outputting + pub fn format(&self, formatter: &mut Formatter, record: &Record<'_>) -> io::Result<()> { + let fmt = ConfigurableFormatWriter { + format: self, + buf: formatter, + written_header_value: false, + }; + + fmt.write(record) + } +} + +impl ConfigurableFormat { + /// Whether or not to write the level in the default format. + pub fn level(&mut self, write: bool) -> &mut Self { + self.level = write; + self + } + + /// Whether or not to write the source file path in the default format. + pub fn file(&mut self, write: bool) -> &mut Self { + self.source_file = write; + self + } + + /// Whether or not to write the source line number path in the default format. + /// + /// Only has effect if `format_file` is also enabled + pub fn line_number(&mut self, write: bool) -> &mut Self { + self.source_line_number = write; + self + } + + /// Whether or not to write the module path in the default format. + pub fn module_path(&mut self, write: bool) -> &mut Self { + self.module_path = write; + self + } + + /// Whether or not to write the target in the default format. + pub fn target(&mut self, write: bool) -> &mut Self { + self.target = write; + self + } + + /// Configures the amount of spaces to use to indent multiline log records. + /// A value of `None` disables any kind of indentation. + pub fn indent(&mut self, indent: Option) -> &mut Self { + self.indent = indent; + self + } + + /// Configures if timestamp should be included and in what precision. + pub fn timestamp(&mut self, timestamp: Option) -> &mut Self { + self.timestamp = timestamp; + self + } + + /// Configures the end of line suffix. + pub fn suffix(&mut self, suffix: &'static str) -> &mut Self { + self.suffix = suffix; + self + } + + /// Set the format for structured key/value pairs in the log record + /// + /// With the default format, this function is called for each record and should format + /// the structured key-value pairs as returned by [`log::Record::key_values`]. + /// + /// The format function is expected to output the string directly to the `Formatter` so that + /// implementations can use the [`std::fmt`] macros, similar to the main format function. + /// + /// The default format uses a space to separate each key-value pair, with an "=" between + /// the key and value. + #[cfg(feature = "kv")] + pub fn key_values(&mut self, format: F) -> &mut Self + where + F: Fn(&mut Formatter, &dyn log::kv::Source) -> io::Result<()> + Sync + Send + 'static, + { + self.kv_format = Some(Box::new(format)); + self + } +} + +impl Default for ConfigurableFormat { + fn default() -> Self { + Self { + timestamp: Some(Default::default()), + module_path: false, + target: true, + level: true, + source_file: false, + source_line_number: false, + indent: Some(4), + suffix: "\n", + #[cfg(feature = "kv")] + kv_format: None, + } + } +} + +impl RecordFormat for ConfigurableFormat { + fn format(&self, formatter: &mut Formatter, record: &Record<'_>) -> io::Result<()> { + self.format(formatter, record) + } +} + +/// The default format. +/// +/// This format needs to work with any combination of crate features. +struct ConfigurableFormatWriter<'a> { + format: &'a ConfigurableFormat, + buf: &'a mut Formatter, + written_header_value: bool, +} + +impl ConfigurableFormatWriter<'_> { + fn write(mut self, record: &Record<'_>) -> io::Result<()> { + self.write_timestamp()?; + self.write_level(record)?; + self.write_module_path(record)?; + self.write_source_location(record)?; + self.write_target(record)?; + self.finish_header()?; + + self.write_args(record)?; + #[cfg(feature = "kv")] + self.write_kv(record)?; + write!(self.buf, "{}", self.format.suffix) + } + + fn subtle_style(&self, text: &'static str) -> SubtleStyle { + #[cfg(feature = "color")] + { + StyledValue { + style: if self.buf.write_style == WriteStyle::Never { + style::Style::new() + } else { + style::AnsiColor::BrightBlack.on_default() + }, + value: text, + } + } + #[cfg(not(feature = "color"))] + { + text + } + } + + fn write_header_value(&mut self, value: T) -> io::Result<()> + where + T: Display, + { + if !self.written_header_value { + self.written_header_value = true; + + let open_brace = self.subtle_style("["); + write!(self.buf, "{open_brace}{value}") + } else { + write!(self.buf, " {value}") + } + } + + fn write_level(&mut self, record: &Record<'_>) -> io::Result<()> { + if !self.format.level { + return Ok(()); + } + + let level = { + let level = record.level(); + #[cfg(feature = "color")] + { + StyledValue { + style: self.buf.default_level_style(level), + value: level, + } + } + #[cfg(not(feature = "color"))] + { + level + } + }; + + self.write_header_value(format_args!("{level:<5}")) + } + + fn write_timestamp(&mut self) -> io::Result<()> { + #[cfg(feature = "humantime")] + { + use self::TimestampPrecision::{Micros, Millis, Nanos, Seconds}; + let ts = match self.format.timestamp { + None => return Ok(()), + Some(Seconds) => self.buf.timestamp_seconds(), + Some(Millis) => self.buf.timestamp_millis(), + Some(Micros) => self.buf.timestamp_micros(), + Some(Nanos) => self.buf.timestamp_nanos(), + }; + + self.write_header_value(ts) + } + #[cfg(not(feature = "humantime"))] + { + // Trick the compiler to think we have used self.timestamp + // Workaround for "field is never used: `timestamp`" compiler nag. + let _ = self.format.timestamp; + Ok(()) + } + } + + fn write_module_path(&mut self, record: &Record<'_>) -> io::Result<()> { + if !self.format.module_path { + return Ok(()); + } + + if let Some(module_path) = record.module_path() { + self.write_header_value(module_path) + } else { + Ok(()) + } + } + + fn write_source_location(&mut self, record: &Record<'_>) -> io::Result<()> { + if !self.format.source_file { + return Ok(()); + } + + if let Some(file_path) = record.file() { + let line = self + .format + .source_line_number + .then(|| record.line()) + .flatten(); + match line { + Some(line) => self.write_header_value(format_args!("{file_path}:{line}")), + None => self.write_header_value(file_path), + } + } else { + Ok(()) + } + } + + fn write_target(&mut self, record: &Record<'_>) -> io::Result<()> { + if !self.format.target { + return Ok(()); + } + + match record.target() { + "" => Ok(()), + target => self.write_header_value(target), + } + } + + fn finish_header(&mut self) -> io::Result<()> { + if self.written_header_value { + let close_brace = self.subtle_style("]"); + write!(self.buf, "{close_brace} ") + } else { + Ok(()) + } + } + + fn write_args(&mut self, record: &Record<'_>) -> io::Result<()> { + match self.format.indent { + // Fast path for no indentation + None => write!(self.buf, "{}", record.args()), + + Some(indent_count) => { + // Create a wrapper around the buffer only if we have to actually indent the message + + struct IndentWrapper<'a, 'b> { + fmt: &'a mut ConfigurableFormatWriter<'b>, + indent_count: usize, + } + + impl Write for IndentWrapper<'_, '_> { + fn write(&mut self, buf: &[u8]) -> io::Result { + let mut first = true; + for chunk in buf.split(|&x| x == b'\n') { + if !first { + write!( + self.fmt.buf, + "{}{:width$}", + self.fmt.format.suffix, + "", + width = self.indent_count + )?; + } + self.fmt.buf.write_all(chunk)?; + first = false; + } + + Ok(buf.len()) + } + + fn flush(&mut self) -> io::Result<()> { + self.fmt.buf.flush() + } + } + + // The explicit scope here is just to make older versions of Rust happy + { + let mut wrapper = IndentWrapper { + fmt: self, + indent_count, + }; + write!(wrapper, "{}", record.args())?; + } + + Ok(()) + } + } + } + + #[cfg(feature = "kv")] + fn write_kv(&mut self, record: &Record<'_>) -> io::Result<()> { + let format = self + .format + .kv_format + .as_deref() + .unwrap_or(&default_kv_format); + format(self.buf, record.key_values()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use log::{Level, Record}; + + fn write_record(record: Record<'_>, fmt: ConfigurableFormatWriter<'_>) -> String { + let buf = fmt.buf.buf.clone(); + + fmt.write(&record).expect("failed to write record"); + + let buf = buf.borrow(); + String::from_utf8(buf.as_bytes().to_vec()).expect("failed to read record") + } + + fn write_target(target: &str, fmt: ConfigurableFormatWriter<'_>) -> String { + write_record( + Record::builder() + .args(format_args!("log\nmessage")) + .level(Level::Info) + .file(Some("test.rs")) + .line(Some(144)) + .module_path(Some("test::path")) + .target(target) + .build(), + fmt, + ) + } + + fn write(fmt: ConfigurableFormatWriter<'_>) -> String { + write_target("", fmt) + } + + fn formatter() -> Formatter { + let writer = writer::Builder::new() + .write_style(WriteStyle::Never) + .build(); + + Formatter::new(&writer) + } + + #[test] + fn format_with_header() { + let mut f = formatter(); + + let written = write(ConfigurableFormatWriter { + format: &ConfigurableFormat { + timestamp: None, + module_path: true, + target: false, + level: true, + source_file: false, + source_line_number: false, + #[cfg(feature = "kv")] + kv_format: Some(Box::new(hidden_kv_format)), + indent: None, + suffix: "\n", + }, + written_header_value: false, + buf: &mut f, + }); + + assert_eq!("[INFO test::path] log\nmessage\n", written); + } + + #[test] + fn format_no_header() { + let mut f = formatter(); + + let written = write(ConfigurableFormatWriter { + format: &ConfigurableFormat { + timestamp: None, + module_path: false, + target: false, + level: false, + source_file: false, + source_line_number: false, + #[cfg(feature = "kv")] + kv_format: Some(Box::new(hidden_kv_format)), + indent: None, + suffix: "\n", + }, + written_header_value: false, + buf: &mut f, + }); + + assert_eq!("log\nmessage\n", written); + } + + #[test] + fn format_indent_spaces() { + let mut f = formatter(); + + let written = write(ConfigurableFormatWriter { + format: &ConfigurableFormat { + timestamp: None, + module_path: true, + target: false, + level: true, + source_file: false, + source_line_number: false, + #[cfg(feature = "kv")] + kv_format: Some(Box::new(hidden_kv_format)), + indent: Some(4), + suffix: "\n", + }, + written_header_value: false, + buf: &mut f, + }); + + assert_eq!("[INFO test::path] log\n message\n", written); + } + + #[test] + fn format_indent_zero_spaces() { + let mut f = formatter(); + + let written = write(ConfigurableFormatWriter { + format: &ConfigurableFormat { + timestamp: None, + module_path: true, + target: false, + level: true, + source_file: false, + source_line_number: false, + #[cfg(feature = "kv")] + kv_format: Some(Box::new(hidden_kv_format)), + indent: Some(0), + suffix: "\n", + }, + written_header_value: false, + buf: &mut f, + }); + + assert_eq!("[INFO test::path] log\nmessage\n", written); + } + + #[test] + fn format_indent_spaces_no_header() { + let mut f = formatter(); + + let written = write(ConfigurableFormatWriter { + format: &ConfigurableFormat { + timestamp: None, + module_path: false, + target: false, + level: false, + source_file: false, + source_line_number: false, + #[cfg(feature = "kv")] + kv_format: Some(Box::new(hidden_kv_format)), + indent: Some(4), + suffix: "\n", + }, + written_header_value: false, + buf: &mut f, + }); + + assert_eq!("log\n message\n", written); + } + + #[test] + fn format_suffix() { + let mut f = formatter(); + + let written = write(ConfigurableFormatWriter { + format: &ConfigurableFormat { + timestamp: None, + module_path: false, + target: false, + level: false, + source_file: false, + source_line_number: false, + #[cfg(feature = "kv")] + kv_format: Some(Box::new(hidden_kv_format)), + indent: None, + suffix: "\n\n", + }, + written_header_value: false, + buf: &mut f, + }); + + assert_eq!("log\nmessage\n\n", written); + } + + #[test] + fn format_suffix_with_indent() { + let mut f = formatter(); + + let written = write(ConfigurableFormatWriter { + format: &ConfigurableFormat { + timestamp: None, + module_path: false, + target: false, + level: false, + source_file: false, + source_line_number: false, + #[cfg(feature = "kv")] + kv_format: Some(Box::new(hidden_kv_format)), + indent: Some(4), + suffix: "\n\n", + }, + written_header_value: false, + buf: &mut f, + }); + + assert_eq!("log\n\n message\n\n", written); + } + + #[test] + fn format_target() { + let mut f = formatter(); + + let written = write_target( + "target", + ConfigurableFormatWriter { + format: &ConfigurableFormat { + timestamp: None, + module_path: true, + target: true, + level: true, + source_file: false, + source_line_number: false, + #[cfg(feature = "kv")] + kv_format: Some(Box::new(hidden_kv_format)), + indent: None, + suffix: "\n", + }, + written_header_value: false, + buf: &mut f, + }, + ); + + assert_eq!("[INFO test::path target] log\nmessage\n", written); + } + + #[test] + fn format_empty_target() { + let mut f = formatter(); + + let written = write(ConfigurableFormatWriter { + format: &ConfigurableFormat { + timestamp: None, + module_path: true, + target: true, + level: true, + source_file: false, + source_line_number: false, + #[cfg(feature = "kv")] + kv_format: Some(Box::new(hidden_kv_format)), + indent: None, + suffix: "\n", + }, + written_header_value: false, + buf: &mut f, + }); + + assert_eq!("[INFO test::path] log\nmessage\n", written); + } + + #[test] + fn format_no_target() { + let mut f = formatter(); + + let written = write_target( + "target", + ConfigurableFormatWriter { + format: &ConfigurableFormat { + timestamp: None, + module_path: true, + target: false, + level: true, + source_file: false, + source_line_number: false, + #[cfg(feature = "kv")] + kv_format: Some(Box::new(hidden_kv_format)), + indent: None, + suffix: "\n", + }, + written_header_value: false, + buf: &mut f, + }, + ); + + assert_eq!("[INFO test::path] log\nmessage\n", written); + } + + #[test] + fn format_with_source_file_and_line_number() { + let mut f = formatter(); + + let written = write(ConfigurableFormatWriter { + format: &ConfigurableFormat { + timestamp: None, + module_path: false, + target: false, + level: true, + source_file: true, + source_line_number: true, + #[cfg(feature = "kv")] + kv_format: Some(Box::new(hidden_kv_format)), + indent: None, + suffix: "\n", + }, + written_header_value: false, + buf: &mut f, + }); + + assert_eq!("[INFO test.rs:144] log\nmessage\n", written); + } + + #[cfg(feature = "kv")] + #[test] + fn format_kv_default() { + let kvs = &[("a", 1u32), ("b", 2u32)][..]; + let mut f = formatter(); + let record = Record::builder() + .args(format_args!("log message")) + .level(Level::Info) + .module_path(Some("test::path")) + .key_values(&kvs) + .build(); + + let written = write_record( + record, + ConfigurableFormatWriter { + format: &ConfigurableFormat { + timestamp: None, + module_path: false, + target: false, + level: true, + source_file: false, + source_line_number: false, + kv_format: Some(Box::new(default_kv_format)), + indent: None, + suffix: "\n", + }, + written_header_value: false, + buf: &mut f, + }, + ); + + assert_eq!("[INFO ] log message a=1 b=2\n", written); + } + + #[cfg(feature = "kv")] + #[test] + fn format_kv_default_full() { + let kvs = &[("a", 1u32), ("b", 2u32)][..]; + let mut f = formatter(); + let record = Record::builder() + .args(format_args!("log\nmessage")) + .level(Level::Info) + .module_path(Some("test::path")) + .target("target") + .file(Some("test.rs")) + .line(Some(42)) + .key_values(&kvs) + .build(); + + let written = write_record( + record, + ConfigurableFormatWriter { + format: &ConfigurableFormat { + timestamp: None, + module_path: true, + target: true, + level: true, + source_file: true, + source_line_number: true, + kv_format: Some(Box::new(default_kv_format)), + indent: None, + suffix: "\n", + }, + written_header_value: false, + buf: &mut f, + }, + ); + + assert_eq!( + "[INFO test::path test.rs:42 target] log\nmessage a=1 b=2\n", + written + ); + } +} diff --git a/tools/vendor/env_logger/src/fmt/writer/buffer.rs b/tools/vendor/env_logger/src/fmt/writer/buffer.rs new file mode 100644 index 0000000000..c73b9581b9 --- /dev/null +++ b/tools/vendor/env_logger/src/fmt/writer/buffer.rs @@ -0,0 +1,175 @@ +use std::{io, sync::Mutex}; + +use crate::fmt::writer::WriteStyle; + +#[derive(Debug)] +pub(in crate::fmt::writer) struct BufferWriter { + target: WritableTarget, + write_style: WriteStyle, +} + +impl BufferWriter { + pub(in crate::fmt::writer) fn stderr(is_test: bool, write_style: WriteStyle) -> Self { + BufferWriter { + target: if is_test { + WritableTarget::PrintStderr + } else { + WritableTarget::WriteStderr + }, + write_style, + } + } + + pub(in crate::fmt::writer) fn stdout(is_test: bool, write_style: WriteStyle) -> Self { + BufferWriter { + target: if is_test { + WritableTarget::PrintStdout + } else { + WritableTarget::WriteStdout + }, + write_style, + } + } + + pub(in crate::fmt::writer) fn pipe( + pipe: Box>, + write_style: WriteStyle, + ) -> Self { + BufferWriter { + target: WritableTarget::Pipe(pipe), + write_style, + } + } + + pub(in crate::fmt::writer) fn write_style(&self) -> WriteStyle { + self.write_style + } + + pub(in crate::fmt::writer) fn buffer(&self) -> Buffer { + Buffer(Vec::new()) + } + + pub(in crate::fmt::writer) fn print(&self, buf: &Buffer) -> io::Result<()> { + #![allow(clippy::print_stdout)] // enabled for tests only + #![allow(clippy::print_stderr)] // enabled for tests only + + use std::io::Write as _; + + let buf = buf.as_bytes(); + match &self.target { + WritableTarget::WriteStdout => { + let stream = io::stdout(); + #[cfg(feature = "color")] + let stream = anstream::AutoStream::new(stream, self.write_style.into()); + let mut stream = stream.lock(); + stream.write_all(buf)?; + stream.flush()?; + } + WritableTarget::PrintStdout => { + #[cfg(feature = "color")] + let buf = adapt(buf, self.write_style)?; + #[cfg(feature = "color")] + let buf = &buf; + let buf = String::from_utf8_lossy(buf); + print!("{buf}"); + } + WritableTarget::WriteStderr => { + let stream = io::stderr(); + #[cfg(feature = "color")] + let stream = anstream::AutoStream::new(stream, self.write_style.into()); + let mut stream = stream.lock(); + stream.write_all(buf)?; + stream.flush()?; + } + WritableTarget::PrintStderr => { + #[cfg(feature = "color")] + let buf = adapt(buf, self.write_style)?; + #[cfg(feature = "color")] + let buf = &buf; + let buf = String::from_utf8_lossy(buf); + eprint!("{buf}"); + } + WritableTarget::Pipe(pipe) => { + #[cfg(feature = "color")] + let buf = adapt(buf, self.write_style)?; + #[cfg(feature = "color")] + let buf = &buf; + let mut stream = pipe.lock().expect("no panics while held"); + stream.write_all(buf)?; + stream.flush()?; + } + } + + Ok(()) + } +} + +#[cfg(feature = "color")] +fn adapt(buf: &[u8], write_style: WriteStyle) -> io::Result> { + use std::io::Write as _; + + let adapted = Vec::with_capacity(buf.len()); + let mut stream = anstream::AutoStream::new(adapted, write_style.into()); + stream.write_all(buf)?; + let adapted = stream.into_inner(); + Ok(adapted) +} + +pub(in crate::fmt) struct Buffer(Vec); + +impl Buffer { + pub(in crate::fmt) fn clear(&mut self) { + self.0.clear(); + } + + pub(in crate::fmt) fn write(&mut self, buf: &[u8]) -> io::Result { + self.0.extend(buf); + Ok(buf.len()) + } + + pub(in crate::fmt) fn flush(&mut self) -> io::Result<()> { + Ok(()) + } + + pub(in crate::fmt) fn as_bytes(&self) -> &[u8] { + &self.0 + } +} + +impl std::fmt::Debug for Buffer { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + String::from_utf8_lossy(self.as_bytes()).fmt(f) + } +} + +/// Log target, either `stdout`, `stderr` or a custom pipe. +/// +/// Same as `Target`, except the pipe is wrapped in a mutex for interior mutability. +pub(super) enum WritableTarget { + /// Logs will be written to standard output. + WriteStdout, + /// Logs will be printed to standard output. + PrintStdout, + /// Logs will be written to standard error. + WriteStderr, + /// Logs will be printed to standard error. + PrintStderr, + /// Logs will be sent to a custom pipe. + Pipe(Box>), +} + +impl std::fmt::Debug for WritableTarget { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Self::WriteStdout => "stdout", + Self::PrintStdout => "stdout", + Self::WriteStderr => "stderr", + Self::PrintStderr => "stderr", + Self::Pipe(_) => "pipe", + } + ) + } +} diff --git a/tools/vendor/env_logger/src/fmt/writer/mod.rs b/tools/vendor/env_logger/src/fmt/writer/mod.rs new file mode 100644 index 0000000000..bb68cb5617 --- /dev/null +++ b/tools/vendor/env_logger/src/fmt/writer/mod.rs @@ -0,0 +1,189 @@ +mod buffer; +mod target; + +use self::buffer::BufferWriter; +use std::{io, mem, sync::Mutex}; + +pub(super) use self::buffer::Buffer; + +pub use target::Target; + +/// Whether or not to print styles to the target. +#[allow(clippy::exhaustive_enums)] // By definition don't need more +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Default)] +pub enum WriteStyle { + /// Try to print styles, but don't force the issue. + #[default] + Auto, + /// Try very hard to print styles. + Always, + /// Never print styles. + Never, +} + +#[cfg(feature = "color")] +impl From for WriteStyle { + fn from(choice: anstream::ColorChoice) -> Self { + match choice { + anstream::ColorChoice::Auto => Self::Auto, + anstream::ColorChoice::Always => Self::Always, + anstream::ColorChoice::AlwaysAnsi => Self::Always, + anstream::ColorChoice::Never => Self::Never, + } + } +} + +#[cfg(feature = "color")] +impl From for anstream::ColorChoice { + fn from(choice: WriteStyle) -> Self { + match choice { + WriteStyle::Auto => anstream::ColorChoice::Auto, + WriteStyle::Always => anstream::ColorChoice::Always, + WriteStyle::Never => anstream::ColorChoice::Never, + } + } +} + +/// A terminal target with color awareness. +#[derive(Debug)] +pub(crate) struct Writer { + inner: BufferWriter, +} + +impl Writer { + pub(crate) fn write_style(&self) -> WriteStyle { + self.inner.write_style() + } + + pub(super) fn buffer(&self) -> Buffer { + self.inner.buffer() + } + + pub(super) fn print(&self, buf: &Buffer) -> io::Result<()> { + self.inner.print(buf) + } +} + +/// A builder for a terminal writer. +/// +/// The target and style choice can be configured before building. +#[derive(Debug)] +pub(crate) struct Builder { + target: Target, + write_style: WriteStyle, + is_test: bool, + built: bool, +} + +impl Builder { + /// Initialize the writer builder with defaults. + pub(crate) fn new() -> Self { + Builder { + target: Default::default(), + write_style: Default::default(), + is_test: false, + built: false, + } + } + + /// Set the target to write to. + pub(crate) fn target(&mut self, target: Target) -> &mut Self { + self.target = target; + self + } + + /// Parses a style choice string. + /// + /// See the [Disabling colors] section for more details. + /// + /// [Disabling colors]: ../index.html#disabling-colors + pub(crate) fn parse_write_style(&mut self, write_style: &str) -> &mut Self { + self.write_style(parse_write_style(write_style)) + } + + /// Whether or not to print style characters when writing. + pub(crate) fn write_style(&mut self, write_style: WriteStyle) -> &mut Self { + self.write_style = write_style; + self + } + + /// Whether or not to capture logs for `cargo test`. + #[allow(clippy::wrong_self_convention)] + pub(crate) fn is_test(&mut self, is_test: bool) -> &mut Self { + self.is_test = is_test; + self + } + + /// Build a terminal writer. + pub(crate) fn build(&mut self) -> Writer { + assert!(!self.built, "attempt to re-use consumed builder"); + self.built = true; + + let color_choice = self.write_style; + #[cfg(feature = "auto-color")] + let color_choice = if color_choice == WriteStyle::Auto { + match &self.target { + Target::Stdout => anstream::AutoStream::choice(&io::stdout()).into(), + Target::Stderr => anstream::AutoStream::choice(&io::stderr()).into(), + Target::Pipe(_) => color_choice, + } + } else { + color_choice + }; + let color_choice = if color_choice == WriteStyle::Auto { + WriteStyle::Never + } else { + color_choice + }; + + let writer = match mem::take(&mut self.target) { + Target::Stdout => BufferWriter::stdout(self.is_test, color_choice), + Target::Stderr => BufferWriter::stderr(self.is_test, color_choice), + Target::Pipe(pipe) => BufferWriter::pipe(Box::new(Mutex::new(pipe)), color_choice), + }; + + Writer { inner: writer } + } +} + +impl Default for Builder { + fn default() -> Self { + Builder::new() + } +} + +fn parse_write_style(spec: &str) -> WriteStyle { + match spec { + "auto" => WriteStyle::Auto, + "always" => WriteStyle::Always, + "never" => WriteStyle::Never, + _ => Default::default(), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_write_style_valid() { + let inputs = vec![ + ("auto", WriteStyle::Auto), + ("always", WriteStyle::Always), + ("never", WriteStyle::Never), + ]; + + for (input, expected) in inputs { + assert_eq!(expected, parse_write_style(input)); + } + } + + #[test] + fn parse_write_style_invalid() { + let inputs = vec!["", "true", "false", "NEVER!!"]; + + for input in inputs { + assert_eq!(WriteStyle::Auto, parse_write_style(input)); + } + } +} diff --git a/tools/vendor/env_logger/src/fmt/writer/target.rs b/tools/vendor/env_logger/src/fmt/writer/target.rs new file mode 100644 index 0000000000..a1220ffeee --- /dev/null +++ b/tools/vendor/env_logger/src/fmt/writer/target.rs @@ -0,0 +1,26 @@ +/// Log target, either `stdout`, `stderr` or a custom pipe. +#[non_exhaustive] +#[derive(Default)] +pub enum Target { + /// Logs will be sent to standard output. + Stdout, + /// Logs will be sent to standard error. + #[default] + Stderr, + /// Logs will be sent to a custom pipe. + Pipe(Box), +} + +impl std::fmt::Debug for Target { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Self::Stdout => "stdout", + Self::Stderr => "stderr", + Self::Pipe(_) => "pipe", + } + ) + } +} diff --git a/tools/vendor/env_logger/src/lib.rs b/tools/vendor/env_logger/src/lib.rs new file mode 100644 index 0000000000..e0afd1c2e3 --- /dev/null +++ b/tools/vendor/env_logger/src/lib.rs @@ -0,0 +1,272 @@ +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! A simple logger that can be configured via environment variables, for use +//! with the logging facade exposed by the [`log` crate][log-crate-url]. +//! +//! Despite having "env" in its name, **`env_logger`** can also be configured by +//! other means besides environment variables. See [the examples][gh-repo-examples] +//! in the source repository for more approaches. +//! +//! By default, `env_logger` writes logs to `stderr`, but can be configured to +//! instead write them to `stdout`. +//! +//! ## Example +//! +//! ``` +//! use log::{debug, error, log_enabled, info, Level}; +//! +//! env_logger::init(); +//! +//! debug!("this is a debug {}", "message"); +//! error!("this is printed by default"); +//! +//! if log_enabled!(Level::Info) { +//! let x = 3 * 4; // expensive computation +//! info!("the answer was: {}", x); +//! } +//! ``` +//! +//! Assumes the binary is `main`: +//! +//! ```console +//! $ RUST_LOG=error ./main +//! [2017-11-09T02:12:24Z ERROR main] this is printed by default +//! ``` +//! +//! ```console +//! $ RUST_LOG=info ./main +//! [2017-11-09T02:12:24Z ERROR main] this is printed by default +//! [2017-11-09T02:12:24Z INFO main] the answer was: 12 +//! ``` +//! +//! ```console +//! $ RUST_LOG=debug ./main +//! [2017-11-09T02:12:24Z DEBUG main] this is a debug message +//! [2017-11-09T02:12:24Z ERROR main] this is printed by default +//! [2017-11-09T02:12:24Z INFO main] the answer was: 12 +//! ``` +//! +//! You can also set the log level on a per module basis: +//! +//! ```console +//! $ RUST_LOG=main=info ./main +//! [2017-11-09T02:12:24Z ERROR main] this is printed by default +//! [2017-11-09T02:12:24Z INFO main] the answer was: 12 +//! ``` +//! +//! And enable all logging: +//! +//! ```console +//! $ RUST_LOG=main ./main +//! [2017-11-09T02:12:24Z DEBUG main] this is a debug message +//! [2017-11-09T02:12:24Z ERROR main] this is printed by default +//! [2017-11-09T02:12:24Z INFO main] the answer was: 12 +//! ``` +//! +//! If the binary name contains hyphens, you will need to replace +//! them with underscores: +//! +//! ```console +//! $ RUST_LOG=my_app ./my-app +//! [2017-11-09T02:12:24Z DEBUG my_app] this is a debug message +//! [2017-11-09T02:12:24Z ERROR my_app] this is printed by default +//! [2017-11-09T02:12:24Z INFO my_app] the answer was: 12 +//! ``` +//! +//! This is because Rust modules and crates cannot contain hyphens +//! in their name, although `cargo` continues to accept them. +//! +//! See the documentation for the [`log` crate][log-crate-url] for more +//! information about its API. +//! +//! ## Enabling logging +//! +//! **By default all logging is disabled except for the `error` level** +//! +//! The **`RUST_LOG`** environment variable controls logging with the syntax: +//! ```console +//! RUST_LOG=[target][=][level][,...] +//! ``` +//! Or in other words, its a comma-separated list of directives. +//! Directives can filter by **target**, by **level**, or both (using `=`). +//! +//! For example, +//! ```console +//! RUST_LOG=data=debug,hardware=debug +//! ``` +//! +//! **target** is typically the path of the module the message +//! in question originated from, though it can be overridden. +//! The path is rooted in the name of the crate it was compiled for, so if +//! your program is in a file called, for example, `hello.rs`, the path would +//! simply be `hello`. +//! +//! Furthermore, the log can be filtered using prefix-search based on the +//! specified log target. +//! +//! For example, `RUST_LOG=example` would match the following targets: +//! - `example` +//! - `example::test` +//! - `example::test::module::submodule` +//! - `examples::and_more_examples` +//! +//! When providing the crate name or a module path, explicitly specifying the +//! log level is optional. If omitted, all logging for the item will be +//! enabled. +//! +//! **level** is the maximum [`log::Level`][level-enum] to be shown and includes: +//! - `error` +//! - `warn` +//! - `info` +//! - `debug` +//! - `trace` +//! - `off` (pseudo level to disable all logging for the target) +//! +//! Logging level names are case-insensitive; e.g., +//! `debug`, `DEBUG`, and `dEbuG` all represent the same logging level. For +//! consistency, our convention is to use the lower case names. Where our docs +//! do use other forms, they do so in the context of specific examples, so you +//! won't be surprised if you see similar usage in the wild. +//! +//! Some examples of valid values of `RUST_LOG` are: +//! +//! - `RUST_LOG=hello` turns on all logging for the `hello` module +//! - `RUST_LOG=trace` turns on all logging for the application, regardless of its name +//! - `RUST_LOG=TRACE` turns on all logging for the application, regardless of its name (same as previous) +//! - `RUST_LOG=info` turns on all info logging +//! - `RUST_LOG=INFO` turns on all info logging (same as previous) +//! - `RUST_LOG=hello=debug` turns on debug logging for `hello` +//! - `RUST_LOG=hello=DEBUG` turns on debug logging for `hello` (same as previous) +//! - `RUST_LOG=hello,std::option` turns on `hello`, and std's option logging +//! - `RUST_LOG=error,hello=warn` turn on global error logging and also warn for `hello` +//! - `RUST_LOG=error,hello=off` turn on global error logging, but turn off logging for `hello` +//! - `RUST_LOG=off` turns off all logging for the application +//! - `RUST_LOG=OFF` turns off all logging for the application (same as previous) +//! +//! ## Filtering results +//! +//! A `RUST_LOG` directive may include a regex filter. The syntax is to append `/` +//! followed by a regex. Each message is checked against the regex, and is only +//! logged if it matches. Note that the matching is done after formatting the +//! log string but before adding any logging meta-data. There is a single filter +//! for all modules. +//! +//! Some examples: +//! +//! * `hello/foo` turns on all logging for the 'hello' module where the log +//! message includes 'foo'. +//! * `info/f.o` turns on all info logging where the log message includes 'foo', +//! 'f1o', 'fao', etc. +//! * `hello=debug/foo*foo` turns on debug logging for 'hello' where the log +//! message includes 'foofoo' or 'fofoo' or 'fooooooofoo', etc. +//! * `error,hello=warn/[0-9]scopes` turn on global error logging and also +//! warn for hello. In both cases the log message must include a single digit +//! number followed by 'scopes'. +//! +//! ## Capturing logs in tests +//! +//! Records logged during `cargo test` will not be captured by the test harness by default. +//! The [`Builder::is_test`] method can be used in unit tests to ensure logs will be captured: +//! +//! ``` +//! #[cfg(test)] +//! mod tests { +//! use log::info; +//! +//! fn init() { +//! let _ = env_logger::builder().is_test(true).try_init(); +//! } +//! +//! #[test] +//! fn it_works() { +//! init(); +//! +//! info!("This record will be captured by `cargo test`"); +//! +//! assert_eq!(2, 1 + 1); +//! } +//! } +//! ``` +//! +//! Enabling test capturing comes at the expense of color and other style support +//! and may have performance implications. +//! +//! ## Disabling colors +//! +//! Colors and other styles can be configured with the `RUST_LOG_STYLE` +//! environment variable. It accepts the following values: +//! +//! * `auto` (default) will attempt to print style characters, but don't force the issue. +//! If the console isn't available on Windows, or if TERM=dumb, for example, then don't print colors. +//! * `always` will always print style characters even if they aren't supported by the terminal. +//! This includes emitting ANSI colors on Windows if the console API is unavailable. +//! * `never` will never print style characters. +//! +//! ## Tweaking the default format +//! +//! Parts of the default format can be excluded from the log output using the [`Builder`]. +//! The following example excludes the timestamp from the log output: +//! +//! ``` +//! env_logger::builder() +//! .format_timestamp(None) +//! .init(); +//! ``` +//! +//! ### Stability of the default format +//! +//! The default format won't optimise for long-term stability, and explicitly makes no +//! guarantees about the stability of its output across major, minor or patch version +//! bumps during `0.x`. +//! +//! If you want to capture or interpret the output of `env_logger` programmatically +//! then you should use a custom format. +//! +//! ### Using a custom format +//! +//! Custom formats can be provided as closures to the [`Builder`]. +//! These closures take a [`Formatter`][crate::fmt::Formatter] and `log::Record` as arguments: +//! +//! ``` +//! use std::io::Write; +//! +//! env_logger::builder() +//! .format(|buf, record| { +//! writeln!(buf, "{}: {}", record.level(), record.args()) +//! }) +//! .init(); +//! ``` +//! +//! See the [`fmt`] module for more details about custom formats. +//! +//! ## Specifying defaults for environment variables +//! +//! `env_logger` can read configuration from environment variables. +//! If these variables aren't present, the default value to use can be tweaked with the [`Env`] type. +//! The following example defaults to log `warn` and above if the `RUST_LOG` environment variable +//! isn't set: +//! +//! ``` +//! use env_logger::Env; +//! +//! env_logger::Builder::from_env(Env::default().default_filter_or("warn")).init(); +//! ``` +//! +//! [gh-repo-examples]: https://github.com/rust-cli/env_logger/tree/main/examples +//! [level-enum]: https://docs.rs/log/latest/log/enum.Level.html +//! [log-crate-url]: https://docs.rs/log + +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![warn(clippy::print_stderr)] +#![warn(clippy::print_stdout)] + +mod logger; + +pub mod fmt; + +pub use self::fmt::{Target, TimestampPrecision, WriteStyle}; +pub use self::logger::*; diff --git a/tools/vendor/env_logger/src/logger.rs b/tools/vendor/env_logger/src/logger.rs new file mode 100644 index 0000000000..bd4d13b719 --- /dev/null +++ b/tools/vendor/env_logger/src/logger.rs @@ -0,0 +1,1058 @@ +use std::{borrow::Cow, cell::RefCell, env, io}; + +use log::{LevelFilter, Log, Metadata, Record, SetLoggerError}; + +use crate::fmt; +use crate::fmt::writer::{self, Writer}; +use crate::fmt::{FormatFn, Formatter}; + +/// The default name for the environment variable to read filters from. +pub const DEFAULT_FILTER_ENV: &str = "RUST_LOG"; + +/// The default name for the environment variable to read style preferences from. +pub const DEFAULT_WRITE_STYLE_ENV: &str = "RUST_LOG_STYLE"; + +/// `Builder` acts as builder for initializing a `Logger`. +/// +/// It can be used to customize the log format, change the environment variable used +/// to provide the logging directives and also set the default log level filter. +/// +/// # Examples +/// +/// ``` +/// # use std::io::Write; +/// use env_logger::Builder; +/// use log::{LevelFilter, error, info}; +/// +/// let mut builder = Builder::from_default_env(); +/// +/// builder +/// .format(|buf, record| writeln!(buf, "{} - {}", record.level(), record.args())) +/// .filter(None, LevelFilter::Info) +/// .init(); +/// +/// error!("error message"); +/// info!("info message"); +/// ``` +#[derive(Default)] +pub struct Builder { + filter: env_filter::Builder, + writer: writer::Builder, + format: fmt::Builder, + built: bool, +} + +impl Builder { + /// Initializes the log builder with defaults. + /// + /// **NOTE:** This method won't read from any environment variables. + /// Use the [`filter`] and [`write_style`] methods to configure the builder + /// or use [`from_env`] or [`from_default_env`] instead. + /// + /// # Examples + /// + /// Create a new builder and configure filters and style: + /// + /// ``` + /// use log::LevelFilter; + /// use env_logger::{Builder, WriteStyle}; + /// + /// let mut builder = Builder::new(); + /// + /// builder + /// .filter(None, LevelFilter::Info) + /// .write_style(WriteStyle::Always) + /// .init(); + /// ``` + /// + /// [`filter`]: #method.filter + /// [`write_style`]: #method.write_style + /// [`from_env`]: #method.from_env + /// [`from_default_env`]: #method.from_default_env + pub fn new() -> Builder { + Default::default() + } + + /// Initializes the log builder from the environment. + /// + /// The variables used to read configuration from can be tweaked before + /// passing in. + /// + /// # Examples + /// + /// Initialise a logger reading the log filter from an environment variable + /// called `MY_LOG`: + /// + /// ``` + /// use env_logger::Builder; + /// + /// let mut builder = Builder::from_env("MY_LOG"); + /// builder.init(); + /// ``` + /// + /// Initialise a logger using the `MY_LOG` variable for filtering and + /// `MY_LOG_STYLE` for whether or not to write styles: + /// + /// ``` + /// use env_logger::{Builder, Env}; + /// + /// let env = Env::new().filter("MY_LOG").write_style("MY_LOG_STYLE"); + /// + /// let mut builder = Builder::from_env(env); + /// builder.init(); + /// ``` + pub fn from_env<'a, E>(env: E) -> Self + where + E: Into>, + { + let mut builder = Builder::new(); + builder.parse_env(env); + builder + } + + /// Applies the configuration from the environment. + /// + /// This function allows a builder to be configured with default parameters, + /// to be then overridden by the environment. + /// + /// # Examples + /// + /// Initialise a logger with filter level `Off`, then override the log + /// filter from an environment variable called `MY_LOG`: + /// + /// ``` + /// use log::LevelFilter; + /// use env_logger::Builder; + /// + /// let mut builder = Builder::new(); + /// + /// builder.filter_level(LevelFilter::Off); + /// builder.parse_env("MY_LOG"); + /// builder.init(); + /// ``` + /// + /// Initialise a logger with filter level `Off`, then use the `MY_LOG` + /// variable to override filtering and `MY_LOG_STYLE` to override whether + /// or not to write styles: + /// + /// ``` + /// use log::LevelFilter; + /// use env_logger::{Builder, Env}; + /// + /// let env = Env::new().filter("MY_LOG").write_style("MY_LOG_STYLE"); + /// + /// let mut builder = Builder::new(); + /// builder.filter_level(LevelFilter::Off); + /// builder.parse_env(env); + /// builder.init(); + /// ``` + pub fn parse_env<'a, E>(&mut self, env: E) -> &mut Self + where + E: Into>, + { + let env = env.into(); + + if let Some(s) = env.get_filter() { + self.parse_filters(&s); + } + + if let Some(s) = env.get_write_style() { + self.parse_write_style(&s); + } + + self + } + + /// Initializes the log builder from the environment using default variable names. + /// + /// This method is a convenient way to call `from_env(Env::default())` without + /// having to use the `Env` type explicitly. The builder will use the + /// [default environment variables]. + /// + /// # Examples + /// + /// Initialise a logger using the default environment variables: + /// + /// ``` + /// use env_logger::Builder; + /// + /// let mut builder = Builder::from_default_env(); + /// builder.init(); + /// ``` + /// + /// [default environment variables]: struct.Env.html#default-environment-variables + pub fn from_default_env() -> Self { + Self::from_env(Env::default()) + } + + /// Applies the configuration from the environment using default variable names. + /// + /// This method is a convenient way to call `parse_env(Env::default())` without + /// having to use the `Env` type explicitly. The builder will use the + /// [default environment variables]. + /// + /// # Examples + /// + /// Initialise a logger with filter level `Off`, then configure it using the + /// default environment variables: + /// + /// ``` + /// use log::LevelFilter; + /// use env_logger::Builder; + /// + /// let mut builder = Builder::new(); + /// builder.filter_level(LevelFilter::Off); + /// builder.parse_default_env(); + /// builder.init(); + /// ``` + /// + /// [default environment variables]: struct.Env.html#default-environment-variables + pub fn parse_default_env(&mut self) -> &mut Self { + self.parse_env(Env::default()) + } + + /// Sets the format function for formatting the log output. + /// + /// This function is called on each record logged and should format the + /// log record and output it to the given [`Formatter`]. + /// + /// The format function is expected to output the string directly to the + /// `Formatter` so that implementations can use the [`std::fmt`] macros + /// to format and output without intermediate heap allocations. The default + /// `env_logger` formatter takes advantage of this. + /// + /// When the `color` feature is enabled, styling via ANSI escape codes is supported and the + /// output will automatically respect [`Builder::write_style`]. + /// + /// # Examples + /// + /// Use a custom format to write only the log message: + /// + /// ``` + /// use std::io::Write; + /// use env_logger::Builder; + /// + /// let mut builder = Builder::new(); + /// + /// builder.format(|buf, record| writeln!(buf, "{}", record.args())); + /// ``` + /// + /// [`Formatter`]: fmt/struct.Formatter.html + /// [`String`]: https://doc.rust-lang.org/stable/std/string/struct.String.html + /// [`std::fmt`]: https://doc.rust-lang.org/std/fmt/index.html + pub fn format(&mut self, format: F) -> &mut Self + where + F: Fn(&mut Formatter, &Record<'_>) -> io::Result<()> + Sync + Send + 'static, + { + self.format.custom_format = Some(Box::new(format)); + self + } + + /// Use the default format. + /// + /// This method will clear any custom format set on the builder. + pub fn default_format(&mut self) -> &mut Self { + self.format = Default::default(); + self + } + + /// Whether or not to write the level in the default format. + pub fn format_level(&mut self, write: bool) -> &mut Self { + self.format.default_format.level(write); + self + } + + /// Whether or not to write the source file path in the default format. + pub fn format_file(&mut self, write: bool) -> &mut Self { + self.format.default_format.file(write); + self + } + + /// Whether or not to write the source line number path in the default format. + /// + /// Only has effect if `format_file` is also enabled + pub fn format_line_number(&mut self, write: bool) -> &mut Self { + self.format.default_format.line_number(write); + self + } + + /// Whether or not to write the source path and line number + /// + /// Equivalent to calling both `format_file` and `format_line_number` + /// with `true` + pub fn format_source_path(&mut self, write: bool) -> &mut Self { + self.format_file(write).format_line_number(write); + self + } + + /// Whether or not to write the module path in the default format. + pub fn format_module_path(&mut self, write: bool) -> &mut Self { + self.format.default_format.module_path(write); + self + } + + /// Whether or not to write the target in the default format. + pub fn format_target(&mut self, write: bool) -> &mut Self { + self.format.default_format.target(write); + self + } + + /// Configures the amount of spaces to use to indent multiline log records. + /// A value of `None` disables any kind of indentation. + pub fn format_indent(&mut self, indent: Option) -> &mut Self { + self.format.default_format.indent(indent); + self + } + + /// Configures if timestamp should be included and in what precision. + pub fn format_timestamp(&mut self, timestamp: Option) -> &mut Self { + self.format.default_format.timestamp(timestamp); + self + } + + /// Configures the timestamp to use second precision. + pub fn format_timestamp_secs(&mut self) -> &mut Self { + self.format_timestamp(Some(fmt::TimestampPrecision::Seconds)) + } + + /// Configures the timestamp to use millisecond precision. + pub fn format_timestamp_millis(&mut self) -> &mut Self { + self.format_timestamp(Some(fmt::TimestampPrecision::Millis)) + } + + /// Configures the timestamp to use microsecond precision. + pub fn format_timestamp_micros(&mut self) -> &mut Self { + self.format_timestamp(Some(fmt::TimestampPrecision::Micros)) + } + + /// Configures the timestamp to use nanosecond precision. + pub fn format_timestamp_nanos(&mut self) -> &mut Self { + self.format_timestamp(Some(fmt::TimestampPrecision::Nanos)) + } + + /// Configures the end of line suffix. + pub fn format_suffix(&mut self, suffix: &'static str) -> &mut Self { + self.format.default_format.suffix(suffix); + self + } + + /// Set the format for structured key/value pairs in the log record + /// + /// With the default format, this function is called for each record and should format + /// the structured key-value pairs as returned by [`log::Record::key_values`]. + /// + /// The format function is expected to output the string directly to the `Formatter` so that + /// implementations can use the [`std::fmt`] macros, similar to the main format function. + /// + /// The default format uses a space to separate each key-value pair, with an "=" between + /// the key and value. + #[cfg(feature = "kv")] + pub fn format_key_values(&mut self, format: F) -> &mut Self + where + F: Fn(&mut Formatter, &dyn log::kv::Source) -> io::Result<()> + Sync + Send + 'static, + { + self.format.default_format.key_values(format); + self + } + + /// Adds a directive to the filter for a specific module. + /// + /// # Examples + /// + /// Only include messages for info and above for logs in `path::to::module`: + /// + /// ``` + /// use env_logger::Builder; + /// use log::LevelFilter; + /// + /// let mut builder = Builder::new(); + /// + /// builder.filter_module("path::to::module", LevelFilter::Info); + /// ``` + pub fn filter_module(&mut self, module: &str, level: LevelFilter) -> &mut Self { + self.filter.filter_module(module, level); + self + } + + /// Adds a directive to the filter for all modules. + /// + /// # Examples + /// + /// Only include messages for info and above for logs globally: + /// + /// ``` + /// use env_logger::Builder; + /// use log::LevelFilter; + /// + /// let mut builder = Builder::new(); + /// + /// builder.filter_level(LevelFilter::Info); + /// ``` + pub fn filter_level(&mut self, level: LevelFilter) -> &mut Self { + self.filter.filter_level(level); + self + } + + /// Adds filters to the logger. + /// + /// The given module (if any) will log at most the specified level provided. + /// If no module is provided then the filter will apply to all log messages. + /// + /// # Examples + /// + /// Only include messages for info and above for logs in `path::to::module`: + /// + /// ``` + /// use env_logger::Builder; + /// use log::LevelFilter; + /// + /// let mut builder = Builder::new(); + /// + /// builder.filter(Some("path::to::module"), LevelFilter::Info); + /// ``` + pub fn filter(&mut self, module: Option<&str>, level: LevelFilter) -> &mut Self { + self.filter.filter(module, level); + self + } + + /// Parses the directives string in the same form as the `RUST_LOG` + /// environment variable. + /// + /// See the module documentation for more details. + pub fn parse_filters(&mut self, filters: &str) -> &mut Self { + self.filter.parse(filters); + self + } + + /// Sets the target for the log output. + /// + /// Env logger can log to either stdout, stderr or a custom pipe. The default is stderr. + /// + /// The custom pipe can be used to send the log messages to a custom sink (for example a file). + /// Do note that direct writes to a file can become a bottleneck due to IO operation times. + /// + /// # Examples + /// + /// Write log message to `stdout`: + /// + /// ``` + /// use env_logger::{Builder, Target}; + /// + /// let mut builder = Builder::new(); + /// + /// builder.target(Target::Stdout); + /// ``` + pub fn target(&mut self, target: fmt::Target) -> &mut Self { + self.writer.target(target); + self + } + + /// Sets whether or not styles will be written. + /// + /// This can be useful in environments that don't support control characters + /// for setting colors. + /// + /// # Examples + /// + /// Never attempt to write styles: + /// + /// ``` + /// use env_logger::{Builder, WriteStyle}; + /// + /// let mut builder = Builder::new(); + /// + /// builder.write_style(WriteStyle::Never); + /// ``` + pub fn write_style(&mut self, write_style: fmt::WriteStyle) -> &mut Self { + self.writer.write_style(write_style); + self + } + + /// Parses whether or not to write styles in the same form as the `RUST_LOG_STYLE` + /// environment variable. + /// + /// See the module documentation for more details. + pub fn parse_write_style(&mut self, write_style: &str) -> &mut Self { + self.writer.parse_write_style(write_style); + self + } + + /// Sets whether or not the logger will be used in unit tests. + /// + /// If `is_test` is `true` then the logger will allow the testing framework to + /// capture log records rather than printing them to the terminal directly. + pub fn is_test(&mut self, is_test: bool) -> &mut Self { + self.writer.is_test(is_test); + self + } + + /// Initializes the global logger with the built env logger. + /// + /// This should be called early in the execution of a Rust program. Any log + /// events that occur before initialization will be ignored. + /// + /// # Errors + /// + /// This function will fail if it is called more than once, or if another + /// library has already initialized a global logger. + pub fn try_init(&mut self) -> Result<(), SetLoggerError> { + let logger = self.build(); + + let max_level = logger.filter(); + let r = log::set_boxed_logger(Box::new(logger)); + + if r.is_ok() { + log::set_max_level(max_level); + } + + r + } + + /// Initializes the global logger with the built env logger. + /// + /// This should be called early in the execution of a Rust program. Any log + /// events that occur before initialization will be ignored. + /// + /// # Panics + /// + /// This function will panic if it is called more than once, or if another + /// library has already initialized a global logger. + pub fn init(&mut self) { + self.try_init() + .expect("Builder::init should not be called after logger initialized"); + } + + /// Build an env logger. + /// + /// The returned logger implements the `Log` trait and can be installed manually + /// or nested within another logger. + pub fn build(&mut self) -> Logger { + assert!(!self.built, "attempt to re-use consumed builder"); + self.built = true; + + Logger { + writer: self.writer.build(), + filter: self.filter.build(), + format: self.format.build(), + } + } +} + +impl std::fmt::Debug for Builder { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if self.built { + f.debug_struct("Logger").field("built", &true).finish() + } else { + f.debug_struct("Logger") + .field("filter", &self.filter) + .field("writer", &self.writer) + .finish() + } + } +} + +/// The env logger. +/// +/// This struct implements the `Log` trait from the [`log` crate][log-crate-url], +/// which allows it to act as a logger. +/// +/// The [`init()`], [`try_init()`], [`Builder::init()`] and [`Builder::try_init()`] +/// methods will each construct a `Logger` and immediately initialize it as the +/// default global logger. +/// +/// If you'd instead need access to the constructed `Logger`, you can use +/// the associated [`Builder`] and install it with the +/// [`log` crate][log-crate-url] directly. +/// +/// [log-crate-url]: https://docs.rs/log +/// [`init()`]: fn.init.html +/// [`try_init()`]: fn.try_init.html +/// [`Builder::init()`]: struct.Builder.html#method.init +/// [`Builder::try_init()`]: struct.Builder.html#method.try_init +/// [`Builder`]: struct.Builder.html +pub struct Logger { + writer: Writer, + filter: env_filter::Filter, + format: FormatFn, +} + +impl Logger { + /// Creates the logger from the environment. + /// + /// The variables used to read configuration from can be tweaked before + /// passing in. + /// + /// # Examples + /// + /// Create a logger reading the log filter from an environment variable + /// called `MY_LOG`: + /// + /// ``` + /// use env_logger::Logger; + /// + /// let logger = Logger::from_env("MY_LOG"); + /// ``` + /// + /// Create a logger using the `MY_LOG` variable for filtering and + /// `MY_LOG_STYLE` for whether or not to write styles: + /// + /// ``` + /// use env_logger::{Logger, Env}; + /// + /// let env = Env::new().filter_or("MY_LOG", "info").write_style_or("MY_LOG_STYLE", "always"); + /// + /// let logger = Logger::from_env(env); + /// ``` + pub fn from_env<'a, E>(env: E) -> Self + where + E: Into>, + { + Builder::from_env(env).build() + } + + /// Creates the logger from the environment using default variable names. + /// + /// This method is a convenient way to call `from_env(Env::default())` without + /// having to use the `Env` type explicitly. The logger will use the + /// [default environment variables]. + /// + /// # Examples + /// + /// Creates a logger using the default environment variables: + /// + /// ``` + /// use env_logger::Logger; + /// + /// let logger = Logger::from_default_env(); + /// ``` + /// + /// [default environment variables]: struct.Env.html#default-environment-variables + pub fn from_default_env() -> Self { + Builder::from_default_env().build() + } + + /// Returns the maximum `LevelFilter` that this env logger instance is + /// configured to output. + pub fn filter(&self) -> LevelFilter { + self.filter.filter() + } + + /// Checks if this record matches the configured filter. + pub fn matches(&self, record: &Record<'_>) -> bool { + self.filter.matches(record) + } +} + +impl Log for Logger { + fn enabled(&self, metadata: &Metadata<'_>) -> bool { + self.filter.enabled(metadata) + } + + fn log(&self, record: &Record<'_>) { + if self.matches(record) { + // Log records are written to a thread-local buffer before being printed + // to the terminal. We clear these buffers afterwards, but they aren't shrunk + // so will always at least have capacity for the largest log record formatted + // on that thread. + // + // If multiple `Logger`s are used by the same threads then the thread-local + // formatter might have different color support. If this is the case the + // formatter and its buffer are discarded and recreated. + + thread_local! { + static FORMATTER: RefCell> = const { RefCell::new(None) }; + } + + let print = |formatter: &mut Formatter, record: &Record<'_>| { + let _ = self + .format + .format(formatter, record) + .and_then(|_| formatter.print(&self.writer)); + + // Always clear the buffer afterwards + formatter.clear(); + }; + + let printed = FORMATTER + .try_with(|tl_buf| { + if let Ok(mut tl_buf) = tl_buf.try_borrow_mut() { + // There are no active borrows of the buffer + if let Some(ref mut formatter) = *tl_buf { + // We have a previously set formatter + + // Check the buffer style. If it's different from the logger's + // style then drop the buffer and recreate it. + if formatter.write_style() != self.writer.write_style() { + *formatter = Formatter::new(&self.writer); + } + + print(formatter, record); + } else { + // We don't have a previously set formatter + let mut formatter = Formatter::new(&self.writer); + print(&mut formatter, record); + + *tl_buf = Some(formatter); + } + } else { + // There's already an active borrow of the buffer (due to re-entrancy) + print(&mut Formatter::new(&self.writer), record); + } + }) + .is_ok(); + + if !printed { + // The thread-local storage was not available (because its + // destructor has already run). Create a new single-use + // Formatter on the stack for this call. + print(&mut Formatter::new(&self.writer), record); + } + } + } + + fn flush(&self) {} +} + +impl std::fmt::Debug for Logger { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Logger") + .field("filter", &self.filter) + .finish() + } +} + +/// Set of environment variables to configure from. +/// +/// # Default environment variables +/// +/// By default, the `Env` will read the following environment variables: +/// +/// - `RUST_LOG`: the level filter +/// - `RUST_LOG_STYLE`: whether or not to print styles with records. +/// +/// These sources can be configured using the builder methods on `Env`. +#[derive(Debug)] +pub struct Env<'a> { + filter: Var<'a>, + write_style: Var<'a>, +} + +impl<'a> Env<'a> { + /// Get a default set of environment variables. + pub fn new() -> Self { + Self::default() + } + + /// Specify an environment variable to read the filter from. + pub fn filter(mut self, filter_env: E) -> Self + where + E: Into>, + { + self.filter = Var::new(filter_env); + + self + } + + /// Specify an environment variable to read the filter from. + /// + /// If the variable is not set, the default value will be used. + pub fn filter_or(mut self, filter_env: E, default: V) -> Self + where + E: Into>, + V: Into>, + { + self.filter = Var::new_with_default(filter_env, default); + + self + } + + /// Use the default environment variable to read the filter from. + /// + /// If the variable is not set, the default value will be used. + pub fn default_filter_or(mut self, default: V) -> Self + where + V: Into>, + { + self.filter = Var::new_with_default(DEFAULT_FILTER_ENV, default); + + self + } + + fn get_filter(&self) -> Option { + self.filter.get() + } + + /// Specify an environment variable to read the style from. + pub fn write_style(mut self, write_style_env: E) -> Self + where + E: Into>, + { + self.write_style = Var::new(write_style_env); + + self + } + + /// Specify an environment variable to read the style from. + /// + /// If the variable is not set, the default value will be used. + pub fn write_style_or(mut self, write_style_env: E, default: V) -> Self + where + E: Into>, + V: Into>, + { + self.write_style = Var::new_with_default(write_style_env, default); + + self + } + + /// Use the default environment variable to read the style from. + /// + /// If the variable is not set, the default value will be used. + pub fn default_write_style_or(mut self, default: V) -> Self + where + V: Into>, + { + self.write_style = Var::new_with_default(DEFAULT_WRITE_STYLE_ENV, default); + + self + } + + fn get_write_style(&self) -> Option { + self.write_style.get() + } +} + +impl<'a, T> From for Env<'a> +where + T: Into>, +{ + fn from(filter_env: T) -> Self { + Env::default().filter(filter_env.into()) + } +} + +impl Default for Env<'_> { + fn default() -> Self { + Env { + filter: Var::new(DEFAULT_FILTER_ENV), + write_style: Var::new(DEFAULT_WRITE_STYLE_ENV), + } + } +} + +#[derive(Debug)] +struct Var<'a> { + name: Cow<'a, str>, + default: Option>, +} + +impl<'a> Var<'a> { + fn new(name: E) -> Self + where + E: Into>, + { + Var { + name: name.into(), + default: None, + } + } + + fn new_with_default(name: E, default: V) -> Self + where + E: Into>, + V: Into>, + { + Var { + name: name.into(), + default: Some(default.into()), + } + } + + fn get(&self) -> Option { + env::var(&*self.name) + .ok() + .or_else(|| self.default.clone().map(|v| v.into_owned())) + } +} + +/// Attempts to initialize the global logger with an env logger. +/// +/// This should be called early in the execution of a Rust program. Any log +/// events that occur before initialization will be ignored. +/// +/// # Errors +/// +/// This function will fail if it is called more than once, or if another +/// library has already initialized a global logger. +pub fn try_init() -> Result<(), SetLoggerError> { + try_init_from_env(Env::default()) +} + +/// Initializes the global logger with an env logger. +/// +/// This should be called early in the execution of a Rust program. Any log +/// events that occur before initialization will be ignored. +/// +/// # Panics +/// +/// This function will panic if it is called more than once, or if another +/// library has already initialized a global logger. +pub fn init() { + try_init().expect("env_logger::init should not be called after logger initialized"); +} + +/// Attempts to initialize the global logger with an env logger from the given +/// environment variables. +/// +/// This should be called early in the execution of a Rust program. Any log +/// events that occur before initialization will be ignored. +/// +/// # Examples +/// +/// Initialise a logger using the `MY_LOG` environment variable for filters +/// and `MY_LOG_STYLE` for writing colors: +/// +/// ``` +/// use env_logger::{Builder, Env}; +/// +/// # fn run() -> Result<(), Box> { +/// let env = Env::new().filter("MY_LOG").write_style("MY_LOG_STYLE"); +/// +/// env_logger::try_init_from_env(env)?; +/// +/// Ok(()) +/// # } +/// # run().unwrap(); +/// ``` +/// +/// # Errors +/// +/// This function will fail if it is called more than once, or if another +/// library has already initialized a global logger. +pub fn try_init_from_env<'a, E>(env: E) -> Result<(), SetLoggerError> +where + E: Into>, +{ + let mut builder = Builder::from_env(env); + + builder.try_init() +} + +/// Initializes the global logger with an env logger from the given environment +/// variables. +/// +/// This should be called early in the execution of a Rust program. Any log +/// events that occur before initialization will be ignored. +/// +/// # Examples +/// +/// Initialise a logger using the `MY_LOG` environment variable for filters +/// and `MY_LOG_STYLE` for writing colors: +/// +/// ``` +/// use env_logger::{Builder, Env}; +/// +/// let env = Env::new().filter("MY_LOG").write_style("MY_LOG_STYLE"); +/// +/// env_logger::init_from_env(env); +/// ``` +/// +/// # Panics +/// +/// This function will panic if it is called more than once, or if another +/// library has already initialized a global logger. +pub fn init_from_env<'a, E>(env: E) +where + E: Into>, +{ + try_init_from_env(env) + .expect("env_logger::init_from_env should not be called after logger initialized"); +} + +/// Create a new builder with the default environment variables. +/// +/// The builder can be configured before being initialized. +/// This is a convenient way of calling [`Builder::from_default_env`]. +/// +/// [`Builder::from_default_env`]: struct.Builder.html#method.from_default_env +pub fn builder() -> Builder { + Builder::from_default_env() +} + +/// Create a builder from the given environment variables. +/// +/// The builder can be configured before being initialized. +#[deprecated( + since = "0.8.0", + note = "Prefer `env_logger::Builder::from_env()` instead." +)] +pub fn from_env<'a, E>(env: E) -> Builder +where + E: Into>, +{ + Builder::from_env(env) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn env_get_filter_reads_from_var_if_set() { + env::set_var("env_get_filter_reads_from_var_if_set", "from var"); + + let env = Env::new().filter_or("env_get_filter_reads_from_var_if_set", "from default"); + + assert_eq!(Some("from var".to_owned()), env.get_filter()); + } + + #[test] + fn env_get_filter_reads_from_default_if_var_not_set() { + env::remove_var("env_get_filter_reads_from_default_if_var_not_set"); + + let env = Env::new().filter_or( + "env_get_filter_reads_from_default_if_var_not_set", + "from default", + ); + + assert_eq!(Some("from default".to_owned()), env.get_filter()); + } + + #[test] + fn env_get_write_style_reads_from_var_if_set() { + env::set_var("env_get_write_style_reads_from_var_if_set", "from var"); + + let env = + Env::new().write_style_or("env_get_write_style_reads_from_var_if_set", "from default"); + + assert_eq!(Some("from var".to_owned()), env.get_write_style()); + } + + #[test] + fn env_get_write_style_reads_from_default_if_var_not_set() { + env::remove_var("env_get_write_style_reads_from_default_if_var_not_set"); + + let env = Env::new().write_style_or( + "env_get_write_style_reads_from_default_if_var_not_set", + "from default", + ); + + assert_eq!(Some("from default".to_owned()), env.get_write_style()); + } + + #[test] + fn builder_parse_env_overrides_existing_filters() { + env::set_var( + "builder_parse_default_env_overrides_existing_filters", + "debug", + ); + let env = Env::new().filter("builder_parse_default_env_overrides_existing_filters"); + + let mut builder = Builder::new(); + builder.filter_level(LevelFilter::Trace); + // Overrides global level to debug + builder.parse_env(env); + + assert_eq!(builder.filter.build().filter(), LevelFilter::Debug); + } +} diff --git a/tools/vendor/env_logger/tests/init-twice-retains-filter.rs b/tools/vendor/env_logger/tests/init-twice-retains-filter.rs new file mode 100644 index 0000000000..d66367ac4c --- /dev/null +++ b/tools/vendor/env_logger/tests/init-twice-retains-filter.rs @@ -0,0 +1,39 @@ +#![allow(clippy::unwrap_used)] + +use std::env; +use std::process; +use std::str; + +fn main() { + if env::var("YOU_ARE_TESTING_NOW").is_ok() { + // Init from the env (which should set the max level to `Debug`) + env_logger::init(); + + assert_eq!(log::LevelFilter::Debug, log::max_level()); + + // Init again using a different max level + // This shouldn't clobber the level that was previously set + env_logger::Builder::new() + .parse_filters("info") + .try_init() + .unwrap_err(); + + assert_eq!(log::LevelFilter::Debug, log::max_level()); + return; + } + + let exe = env::current_exe().unwrap(); + let out = process::Command::new(exe) + .env("YOU_ARE_TESTING_NOW", "1") + .env("RUST_LOG", "debug") + .output() + .unwrap_or_else(|e| panic!("Unable to start child process: {e}")); + if out.status.success() { + return; + } + + println!("test failed: {}", out.status); + println!("--- stdout\n{}", str::from_utf8(&out.stdout).unwrap()); + println!("--- stderr\n{}", str::from_utf8(&out.stderr).unwrap()); + process::exit(1); +} diff --git a/tools/vendor/env_logger/tests/log-in-log.rs b/tools/vendor/env_logger/tests/log-in-log.rs new file mode 100644 index 0000000000..93348cdf81 --- /dev/null +++ b/tools/vendor/env_logger/tests/log-in-log.rs @@ -0,0 +1,40 @@ +#![allow(clippy::unwrap_used)] + +#[macro_use] +extern crate log; + +use std::env; +use std::fmt; +use std::process; +use std::str; + +struct Foo; + +impl fmt::Display for Foo { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + info!("test"); + f.write_str("bar") + } +} + +fn main() { + env_logger::init(); + if env::var("YOU_ARE_TESTING_NOW").is_ok() { + return info!("{}", Foo); + } + + let exe = env::current_exe().unwrap(); + let out = process::Command::new(exe) + .env("YOU_ARE_TESTING_NOW", "1") + .env("RUST_LOG", "debug") + .output() + .unwrap_or_else(|e| panic!("Unable to start child process: {e}")); + if out.status.success() { + return; + } + + println!("test failed: {}", out.status); + println!("--- stdout\n{}", str::from_utf8(&out.stdout).unwrap()); + println!("--- stderr\n{}", str::from_utf8(&out.stderr).unwrap()); + process::exit(1); +} diff --git a/tools/vendor/env_logger/tests/log_tls_dtors.rs b/tools/vendor/env_logger/tests/log_tls_dtors.rs new file mode 100644 index 0000000000..6876c0791d --- /dev/null +++ b/tools/vendor/env_logger/tests/log_tls_dtors.rs @@ -0,0 +1,67 @@ +#![allow(clippy::unwrap_used)] + +#[macro_use] +extern crate log; + +use std::env; +use std::process; +use std::str; +use std::thread; + +struct DropMe; + +impl Drop for DropMe { + fn drop(&mut self) { + debug!("Dropping now"); + } +} + +fn run() { + // Use multiple thread local values to increase the chance that our TLS + // value will get destroyed after the FORMATTER key in the library + thread_local! { + static DROP_ME_0: DropMe = const { DropMe }; + static DROP_ME_1: DropMe = const { DropMe }; + static DROP_ME_2: DropMe = const { DropMe }; + static DROP_ME_3: DropMe = const { DropMe }; + static DROP_ME_4: DropMe = const { DropMe }; + static DROP_ME_5: DropMe = const { DropMe }; + static DROP_ME_6: DropMe = const { DropMe }; + static DROP_ME_7: DropMe = const { DropMe }; + static DROP_ME_8: DropMe = const { DropMe }; + static DROP_ME_9: DropMe = const { DropMe }; + } + DROP_ME_0.with(|_| {}); + DROP_ME_1.with(|_| {}); + DROP_ME_2.with(|_| {}); + DROP_ME_3.with(|_| {}); + DROP_ME_4.with(|_| {}); + DROP_ME_5.with(|_| {}); + DROP_ME_6.with(|_| {}); + DROP_ME_7.with(|_| {}); + DROP_ME_8.with(|_| {}); + DROP_ME_9.with(|_| {}); +} + +fn main() { + env_logger::init(); + if env::var("YOU_ARE_TESTING_NOW").is_ok() { + // Run on a separate thread because TLS values on the main thread + // won't have their destructors run if pthread is used. + // https://doc.rust-lang.org/std/thread/struct.LocalKey.html#platform-specific-behavior + thread::spawn(run).join().unwrap(); + } else { + let exe = env::current_exe().unwrap(); + let out = process::Command::new(exe) + .env("YOU_ARE_TESTING_NOW", "1") + .env("RUST_LOG", "debug") + .output() + .unwrap_or_else(|e| panic!("Unable to start child process: {e}")); + if !out.status.success() { + println!("test failed: {}", out.status); + println!("--- stdout\n{}", str::from_utf8(&out.stdout).unwrap()); + println!("--- stderr\n{}", str::from_utf8(&out.stderr).unwrap()); + process::exit(1); + } + } +} diff --git a/tools/vendor/env_logger/tests/regexp_filter.rs b/tools/vendor/env_logger/tests/regexp_filter.rs new file mode 100644 index 0000000000..25ceca0553 --- /dev/null +++ b/tools/vendor/env_logger/tests/regexp_filter.rs @@ -0,0 +1,55 @@ +#![allow(clippy::unwrap_used)] + +#[macro_use] +extern crate log; + +use std::env; +use std::process; +use std::str; + +fn main() { + if env::var("LOG_REGEXP_TEST").ok() == Some(String::from("1")) { + child_main(); + } else { + parent_main(); + } +} + +fn child_main() { + env_logger::init(); + info!("XYZ Message"); +} + +fn run_child(rust_log: String) -> bool { + let exe = env::current_exe().unwrap(); + let out = process::Command::new(exe) + .env("LOG_REGEXP_TEST", "1") + .env("RUST_LOG", rust_log) + .output() + .unwrap_or_else(|e| panic!("Unable to start child process: {e}")); + str::from_utf8(out.stderr.as_ref()) + .unwrap() + .contains("XYZ Message") +} + +fn assert_message_printed(rust_log: &str) { + if !run_child(rust_log.to_owned()) { + panic!("RUST_LOG={rust_log} should allow the test log message") + } +} + +fn assert_message_not_printed(rust_log: &str) { + if run_child(rust_log.to_owned()) { + panic!("RUST_LOG={rust_log} should not allow the test log message") + } +} + +fn parent_main() { + // test normal log severity levels + assert_message_printed("info"); + assert_message_not_printed("warn"); + + // test of regular expression filters + assert_message_printed("info/XYZ"); + assert_message_not_printed("info/XXX"); +} diff --git a/tools/vendor/jiff-static/.cargo-checksum.json b/tools/vendor/jiff-static/.cargo-checksum.json new file mode 100644 index 0000000000..73a8465091 --- /dev/null +++ b/tools/vendor/jiff-static/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{".cargo_vcs_info.json":"b41ec3b3003d359a85555d50ffe41145be7ced266322a1ca4c77808baf208ec1","COPYING":"01c266bced4a434da0051174d6bee16a4c82cf634e2679b6155d40d75012390f","Cargo.lock":"387b64c94c68292723db48d57593999069f1d1d1279fbd3f3455aa1b7ffb724b","Cargo.toml":"904e44a7a1a384803a2a96d21ab9c3cda320a06db5fde7d37c4e908250028bc1","Cargo.toml.orig":"da563dfbadc1a41c8720ab2821de0f8c518a9d6a681bbf8b453957f7a55c0f82","LICENSE-MIT":"0f96a83840e146e43c0ec96a22ec1f392e0680e6c1226e6f3ba87e0740af850f","README.md":"1703b78df7b46a4d02c3992d5cf6aeb6d6dd4686322db5d73a1a228291cb913c","UNLICENSE":"7e12e5df4bae12cb21581ba157ced20e1986a0508dd10d0e8a4ab9a4cf94e85c","src/lib.rs":"19e845c2c7e3b8acf846b05d39e46146dd54ce8a334e213b47244004a989c32e","src/shared/crc32/mod.rs":"d15141848dad2ac189bf1bd1dbccd629039015f7bbe101f071ac39973a575af0","src/shared/crc32/table.rs":"ce74ebe69462392b239143edf58c17ae29a7c93e43c34c84cf1a24401fd1bd1c","src/shared/error/itime.rs":"9165704bfd4416f70d663f6ae078bb06bd9b11b65efd401a2d32ec63a2107b13","src/shared/error/mod.rs":"a2f4bd19776970920abb540b6da3d3c0dc0422bacc49a0665326956c174a042f","src/shared/mod.rs":"15cdae67df68f1ffdfb505bacffc27dd907964b482925b1eb5d23dc629e970e8","src/shared/posix.rs":"2cbc54a651c1eed1028c5a052dd13d2c2e2d3127379667be615c7a2785ec5efe","src/shared/tzif.rs":"a59435d0ab55687533f16f5c2da8a5d04555df438524222a7faf2df6ce486d23","src/shared/util/array_str.rs":"9735a5384687620b087280824631cc9b5f29eee96aec350dbf2038d89c17598f","src/shared/util/error.rs":"6f1a13eef24512dda28a3127c498ae6e53fac887670f03d4ce4982f6c80e04af","src/shared/util/escape.rs":"2041bce3fbb6628aa2ff8a858b2cc3adf80696b500b049547e5e39e79a759ef7","src/shared/util/itime.rs":"21ce513c9a4956907092162e9f271626cb364ade9886f33349044e651e84de70","src/shared/util/mod.rs":"ac49711b4995fcd64f8fc41bfb0d3d3fde96740eb5ae92bf7356e8ca51e2877a","src/shared/util/utf8.rs":"a0fad1d925ece6687da97e9308aed25e28980f2e6ae8775e186c6cb32a3217db"},"package":"e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78"} \ No newline at end of file diff --git a/tools/vendor/jiff-static/.cargo_vcs_info.json b/tools/vendor/jiff-static/.cargo_vcs_info.json new file mode 100644 index 0000000000..6cb8bc2f42 --- /dev/null +++ b/tools/vendor/jiff-static/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "6508ba163bbb6f029c4f79663915ee8b135946b8" + }, + "path_in_vcs": "crates/jiff-static" +} \ No newline at end of file diff --git a/tools/vendor/jiff-static/COPYING b/tools/vendor/jiff-static/COPYING new file mode 100644 index 0000000000..bb9c20a094 --- /dev/null +++ b/tools/vendor/jiff-static/COPYING @@ -0,0 +1,3 @@ +This project is dual-licensed under the Unlicense and MIT licenses. + +You may use this code under the terms of either license. diff --git a/tools/vendor/jiff-static/Cargo.lock b/tools/vendor/jiff-static/Cargo.lock new file mode 100644 index 0000000000..0d4149b306 --- /dev/null +++ b/tools/vendor/jiff-static/Cargo.lock @@ -0,0 +1,54 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "jiff-static" +version = "0.2.18" +dependencies = [ + "jiff-tzdb", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68971ebff725b9e2ca27a601c5eb38a4c5d64422c4cbab0c535f248087eda5c2" + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "2.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" diff --git a/tools/vendor/jiff-static/Cargo.toml b/tools/vendor/jiff-static/Cargo.toml new file mode 100644 index 0000000000..d223384066 --- /dev/null +++ b/tools/vendor/jiff-static/Cargo.toml @@ -0,0 +1,68 @@ +# 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-static" +version = "0.2.18" +authors = ["Andrew Gallant "] +build = false +include = [ + "/src/**/*.rs", + "COPYING", + "LICENSE-MIT", + "UNLICENSE", +] +autolib = false +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = "Create static TimeZone values for Jiff (useful in core-only environments)." +homepage = "https://github.com/BurntSushi/jiff/tree/master/crates/jiff-static" +documentation = "https://docs.rs/jiff-tzdb" +readme = "README.md" +keywords = [ + "date", + "time", + "static", + "zone", + "iana", +] +categories = ["date-and-time"] +license = "Unlicense OR MIT" +repository = "https://github.com/BurntSushi/jiff" + +[features] +default = [] +perf-inline = [] +tz-fat = [] +tzdb = ["dep:jiff-tzdb"] + +[lib] +name = "jiff_static" +path = "src/lib.rs" +bench = false +proc-macro = true + +[dependencies.jiff-tzdb] +version = "0.1.4" +optional = true + +[dependencies.proc-macro2] +version = "1.0.93" + +[dependencies.quote] +version = "1.0.38" + +[dependencies.syn] +version = "2.0.98" diff --git a/tools/vendor/jiff-static/Cargo.toml.orig b/tools/vendor/jiff-static/Cargo.toml.orig new file mode 100644 index 0000000000..749c62b760 --- /dev/null +++ b/tools/vendor/jiff-static/Cargo.toml.orig @@ -0,0 +1,42 @@ +[package] +name = "jiff-static" +version = "0.2.18" #:version +authors = ["Andrew Gallant "] +license = "Unlicense OR MIT" +homepage = "https://github.com/BurntSushi/jiff/tree/master/crates/jiff-static" +repository = "https://github.com/BurntSushi/jiff" +documentation = "https://docs.rs/jiff-tzdb" +description = "Create static TimeZone values for Jiff (useful in core-only environments)." +categories = ["date-and-time"] +keywords = ["date", "time", "static", "zone", "iana"] +workspace = "../.." +edition = "2021" +rust-version = "1.70" +include = ["/src/**/*.rs", "COPYING", "LICENSE-MIT", "UNLICENSE"] + +[lib] +name = "jiff_static" +bench = false +proc-macro = true + +[features] +default = [] +# This forces the jiff-tzdb crate to be included and makes the `get` proc macro +# available (which pulls from the bundled tzdb). +tzdb = ["dep:jiff-tzdb"] +# This fattens up the TZif data to contain more explicit transitions. This may +# improve the performance of time zone lookups. +# +# See the `tz-fat` feature in this repository's root `Cargo.toml` for more +# context. +tz-fat = [] +# Equivalent to the eponymous feature in `jiff` proper. Except it isn't +# enabled by default here, since we don't really care about that level of +# perf at compile time. +perf-inline = [] + +[dependencies] +jiff-tzdb = { version = "0.1.4", path = "../jiff-tzdb", optional = true } +proc-macro2 = "1.0.93" +quote = "1.0.38" +syn = "2.0.98" diff --git a/tools/vendor/jiff-static/LICENSE-MIT b/tools/vendor/jiff-static/LICENSE-MIT new file mode 100644 index 0000000000..3b0a5dc09c --- /dev/null +++ b/tools/vendor/jiff-static/LICENSE-MIT @@ -0,0 +1,21 @@ +The 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. diff --git a/tools/vendor/jiff-static/README.md b/tools/vendor/jiff-static/README.md new file mode 100644 index 0000000000..103ad6df3a --- /dev/null +++ b/tools/vendor/jiff-static/README.md @@ -0,0 +1,19 @@ +jiff-static +=========== +This is an optional dependency of `jiff` that embeds time zone data into your +binary via procedural macros. It unlocks the use case of creating a `TimeZone` +value in core-only environments without dynamic memory allocation. + +Users should generally not depend on this directly, but instead use it +through Jiff. Namely, all of the procedural macros defined in this crate +are re-exported through Jiff's public API. For example, one can enable the +`static` or `static-tz` crate features in `jiff` to get `jiff::tz::get!` and +`jiff::tz::include!`. + +**WARNING**: The `src/shared` directory in this crate is copied from the +`../src/shared` directory. This copy is managed by `jiff-cli generate shared`. +See the comments in the code for why this is done. + +### Documentation + +https://docs.rs/jiff-static diff --git a/tools/vendor/jiff-static/UNLICENSE b/tools/vendor/jiff-static/UNLICENSE new file mode 100644 index 0000000000..68a49daad8 --- /dev/null +++ b/tools/vendor/jiff-static/UNLICENSE @@ -0,0 +1,24 @@ +This 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 diff --git a/tools/vendor/jiff-static/src/lib.rs b/tools/vendor/jiff-static/src/lib.rs new file mode 100644 index 0000000000..99d11b7356 --- /dev/null +++ b/tools/vendor/jiff-static/src/lib.rs @@ -0,0 +1,462 @@ +/*! +This crate provides macros for defining `static` data structures for Jiff. + +The macros in this crate are re-exported in the [`jiff::tz`] sub-module. +Users should _not_ depend on this crate directly or import from it. Instead, +enable the `static` or `static-tz` features of Jiff and use the re-exports in +`jiff::tz`. + +At present, the macros in this crate are limited to creating `TimeZone` +in a `const` context. This works by reading TZif data (e.g., from +`/usr/share/zoneinfo/America/New_York` or from [`jiff-tzdb`]) at compile +time and generating Rust source code that builds a `TimeZone`. + +# Documentation + +The macros defined in this crate are documented on their corresponding +re-exports in Jiff: + +* `get` is documented at [`jiff::tz::get`]. +* `include` is documented at [`jiff::tz::include`]. + +# Compatibility + +The APIs required to build a `TimeZone` in a `const` context are exposed by +Jiff but not part of Jiff's public API for the purposes of semver (and do not +appear in `rustdoc`). The only guarantee provided by `jiff` and `jiff-static` +is that there is exactly one version of `jiff` that `jiff-static` works with. +Conventionally, this is indicated by the exact same version string. That is, +`jiff-static 0.2.2` is only guaranteed to work with `jiff 0.2.2`. + +This compatibility constraint is managed by Jiff, so that you should never +need to worry about it. In particular, users should never directly depend on +this crate. Everything should be managed through the `jiff` crate. + +[`jiff-tzdb`]: https://docs.rs/jiff-tzdb +[`jiff::tz`]: https://docs.rs/jiff/0.2/jiff/tz/index.html +[`jiff::tz::get`]: https://docs.rs/jiff/0.2/jiff/tz/macro.get.html +[`jiff::tz::include`]: https://docs.rs/jiff/0.2/jiff/tz/macro.include.html +*/ + +extern crate alloc; +extern crate proc_macro; + +use proc_macro::TokenStream; +use quote::quote; + +use self::shared::{ + util::array_str::Abbreviation, PosixDay, PosixDayTime, PosixDst, + PosixOffset, PosixRule, PosixTime, PosixTimeZone, TzifDateTime, TzifFixed, + TzifIndicator, TzifLocalTimeType, TzifOwned, TzifTransitionInfo, + TzifTransitionKind, TzifTransitionsOwned, +}; + +/// A bundle of code copied from `src/shared`. +/// +/// The main thing we use in here is the parsing routine for TZif data and +/// shared data types for representing TZif data. +/// +/// We also squash dead code warnings. This is somewhat precarious since +/// ideally we wouldn't compile what we don't need. But in practice, it's +/// annoying to get rid of everything we don't need in this context, and it +/// should be pretty small anyway. +#[allow(dead_code)] +mod shared; + +// Public API docs are in Jiff. +#[proc_macro] +pub fn include(input: TokenStream) -> TokenStream { + let input = syn::parse_macro_input!(input as Include); + proc_macro::TokenStream::from(input.quote()) +} + +// Public API docs are in Jiff. +#[cfg(feature = "tzdb")] +#[proc_macro] +pub fn get(input: TokenStream) -> TokenStream { + let input = syn::parse_macro_input!(input as Get); + proc_macro::TokenStream::from(input.quote()) +} + +/// The entry point for the `include!` macro. +#[derive(Debug)] +struct Include { + tzif: TzifOwned, +} + +impl Include { + fn from_path_only(path: &str) -> Result { + const NEEDLE: &str = "zoneinfo/"; + + let Some(zoneinfo) = path.rfind(NEEDLE) else { + return Err(format!( + "could not extract IANA time zone identifier from \ + file path `{path}` \ + (could not find `zoneinfo` in path), \ + please provide IANA time zone identifier as second \ + parameter", + )); + }; + let idstart = zoneinfo.saturating_add(NEEDLE.len()); + let id = &path[idstart..]; + Include::from_path_with_id(id, path) + } + + fn from_path_with_id(id: &str, path: &str) -> Result { + let id = id.to_string(); + let data = std::fs::read(path) + .map_err(|e| format!("failed to read {path}: {e}"))?; + let tzif = TzifOwned::parse(Some(id.clone()), &data).map_err(|e| { + format!("failed to parse TZif data from {path}: {e}") + })?; + Ok(Include { tzif }) + } + + fn quote(&self) -> proc_macro2::TokenStream { + self.tzif.quote() + } +} + +impl syn::parse::Parse for Include { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let lit1 = input.parse::()?.value(); + if !input.lookahead1().peek(syn::Token![,]) { + return Ok( + Include::from_path_only(&lit1).map_err(|e| input.error(e))? + ); + } + input.parse::()?; + if input.is_empty() { + return Ok( + Include::from_path_only(&lit1).map_err(|e| input.error(e))? + ); + } + let lit2 = input.parse::()?.value(); + // Permit optional trailing comma. + if input.lookahead1().peek(syn::Token![,]) { + input.parse::()?; + } + Ok(Include::from_path_with_id(&lit2, &lit1) + .map_err(|e| input.error(e))?) + } +} + +/// The entry point for the `get!` macro. +#[cfg(feature = "tzdb")] +#[derive(Debug)] +struct Get { + tzif: TzifOwned, +} + +#[cfg(feature = "tzdb")] +impl Get { + fn from_id(id: &str) -> Result { + let (id, data) = jiff_tzdb::get(id).ok_or_else(|| { + format!("could not find time zone `{id}` in bundled tzdb") + })?; + let id = id.to_string(); + let tzif = TzifOwned::parse(Some(id.clone()), &data).map_err(|e| { + format!("failed to parse TZif data from bundled `{id}`: {e}") + })?; + Ok(Get { tzif }) + } + + fn quote(&self) -> proc_macro2::TokenStream { + self.tzif.quote() + } +} + +#[cfg(feature = "tzdb")] +impl syn::parse::Parse for Get { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let lit1 = input.parse::()?.value(); + if input.lookahead1().peek(syn::Token![,]) { + input.parse::()?; + } + Ok(Get::from_id(&lit1).map_err(|e| input.error(e))?) + } +} + +// Everything below at this point is quasi-quoting the `shared` data type +// values into `static` data structures as Rust source code. + +impl TzifOwned { + fn quote(&self) -> proc_macro2::TokenStream { + let TzifOwned { ref fixed, ref types, ref transitions } = *self; + let fixed = fixed.quote(); + let types = types.iter().map(TzifLocalTimeType::quote); + let transitions = transitions.quote(); + quote! { + { + static TZ: jiff::tz::TimeZone = + jiff::tz::TimeZone::__internal_from_tzif( + &jiff::shared::TzifStatic { + fixed: #fixed, + types: &[#(#types),*], + transitions: #transitions, + }.into_jiff() + ); + // SAFETY: Since we are guaranteed that the `TimeZone` is + // constructed above as a static TZif time zone, it follows + // that it is safe to memcpy's its internal representation. + // + // NOTE: We arrange things this way so that `jiff::tz::get!` + // can be used "by value" in most contexts. Basically, we + // "pin" the time zone to a static so that it has a guaranteed + // static lifetime. Otherwise, since `TimeZone` has a `Drop` + // impl, it's easy to run afoul of this and have it be dropped + // earlier than you like. Since this particular variant of + // `TimeZone` can always be memcpy'd internally, we just do + // this dance here to save the user from having to write out + // their own `static`. + // + // NOTE: It would be nice if we could make this `copy` routine + // safe, or at least panic if it's misused. But to do that, you + // need to know the time zone variant. And to know the time + // zone variant, you need to "look" at the tag in the pointer. + // And looking at the address of a pointer in a `const` context + // is precarious. + unsafe { TZ.copy() } + } + } + } +} + +impl TzifFixed { + fn quote(&self) -> proc_macro2::TokenStream { + let TzifFixed { + ref name, + version, + checksum, + ref designations, + ref posix_tz, + } = *self; + let name = name.as_ref().unwrap(); + let posix_tz = posix_tz + .as_ref() + .map(|tz| { + let tz = tz.quote(); + quote!(Some(#tz)) + }) + .unwrap_or_else(|| quote!(None)); + quote! { + jiff::shared::TzifFixed { + name: Some(#name), + version: #version, + checksum: #checksum, + designations: #designations, + posix_tz: #posix_tz, + } + } + } +} + +impl TzifTransitionsOwned { + fn quote(&self) -> proc_macro2::TokenStream { + let TzifTransitionsOwned { + ref timestamps, + ref civil_starts, + ref civil_ends, + ref infos, + } = *self; + let civil_starts: Vec<_> = + civil_starts.iter().map(TzifDateTime::quote).collect(); + let civil_ends: Vec<_> = + civil_ends.iter().map(TzifDateTime::quote).collect(); + let infos: Vec<_> = + infos.iter().map(TzifTransitionInfo::quote).collect(); + quote! { + jiff::shared::TzifTransitions { + timestamps: &[#(#timestamps),*], + civil_starts: &[#(#civil_starts),*], + civil_ends: &[#(#civil_ends),*], + infos: &[#(#infos),*], + } + } + } +} + +impl TzifLocalTimeType { + fn quote(&self) -> proc_macro2::TokenStream { + let TzifLocalTimeType { + offset, + is_dst, + ref designation, + ref indicator, + } = *self; + let desig_start = designation.0; + let desig_end = designation.1; + let indicator = indicator.quote(); + quote! { + jiff::shared::TzifLocalTimeType { + offset: #offset, + is_dst: #is_dst, + designation: (#desig_start, #desig_end), + indicator: #indicator, + } + } + } +} + +impl TzifIndicator { + fn quote(&self) -> proc_macro2::TokenStream { + match *self { + TzifIndicator::LocalWall => quote! { + jiff::shared::TzifIndicator::LocalWall + }, + TzifIndicator::LocalStandard => quote! { + jiff::shared::TzifIndicator::LocalStandard + }, + TzifIndicator::UTStandard => quote! { + jiff::shared::TzifIndicator::UTStandard + }, + } + } +} + +impl TzifTransitionInfo { + fn quote(&self) -> proc_macro2::TokenStream { + let TzifTransitionInfo { type_index, kind } = *self; + let kind = kind.quote(); + quote! { + jiff::shared::TzifTransitionInfo { + type_index: #type_index, + kind: #kind, + } + } + } +} + +impl TzifTransitionKind { + fn quote(&self) -> proc_macro2::TokenStream { + match *self { + TzifTransitionKind::Unambiguous => quote! { + jiff::shared::TzifTransitionKind::Unambiguous + }, + TzifTransitionKind::Gap => quote! { + jiff::shared::TzifTransitionKind::Gap + }, + TzifTransitionKind::Fold => quote! { + jiff::shared::TzifTransitionKind::Fold + }, + } + } +} + +impl TzifDateTime { + fn quote(&self) -> proc_macro2::TokenStream { + let year = self.year(); + let month = self.month(); + let day = self.day(); + let hour = self.hour(); + let minute = self.minute(); + let second = self.second(); + quote! { + jiff::shared::TzifDateTime::new( + #year, + #month, + #day, + #hour, + #minute, + #second, + ) + } + } +} + +impl PosixTimeZone { + fn quote(&self) -> proc_macro2::TokenStream { + let PosixTimeZone { ref std_abbrev, ref std_offset, ref dst } = *self; + let std_abbrev = std_abbrev.as_str(); + let std_offset = std_offset.quote(); + let dst = dst + .as_ref() + .map(|dst| { + let dst = dst.quote(); + quote!(Some(#dst)) + }) + .unwrap_or_else(|| quote!(None)); + quote! { + jiff::shared::PosixTimeZone { + std_abbrev: #std_abbrev, + std_offset: #std_offset, + dst: #dst, + } + } + } +} + +impl PosixDst { + fn quote(&self) -> proc_macro2::TokenStream { + let PosixDst { ref abbrev, ref offset, ref rule } = *self; + let abbrev = abbrev.as_str(); + let offset = offset.quote(); + let rule = rule.quote(); + quote! { + jiff::shared::PosixDst { + abbrev: #abbrev, + offset: #offset, + rule: #rule, + } + } + } +} + +impl PosixRule { + fn quote(&self) -> proc_macro2::TokenStream { + let start = self.start.quote(); + let end = self.end.quote(); + quote! { + jiff::shared::PosixRule { start: #start, end: #end } + } + } +} + +impl PosixDayTime { + fn quote(&self) -> proc_macro2::TokenStream { + let PosixDayTime { ref date, ref time } = *self; + let date = date.quote(); + let time = time.quote(); + quote! { + jiff::shared::PosixDayTime { date: #date, time: #time } + } + } +} + +impl PosixDay { + fn quote(&self) -> proc_macro2::TokenStream { + match *self { + PosixDay::JulianOne(day) => quote! { + jiff::shared::PosixDay::JulianOne(#day) + }, + PosixDay::JulianZero(day) => quote! { + jiff::shared::PosixDay::JulianZero(#day) + }, + PosixDay::WeekdayOfMonth { month, week, weekday } => quote! { + jiff::shared::PosixDay::WeekdayOfMonth { + month: #month, + week: #week, + weekday: #weekday, + } + }, + } + } +} + +impl PosixTime { + fn quote(&self) -> proc_macro2::TokenStream { + let PosixTime { second } = *self; + quote! { + jiff::shared::PosixTime { second: #second } + } + } +} + +impl PosixOffset { + fn quote(&self) -> proc_macro2::TokenStream { + let PosixOffset { second } = *self; + quote! { + jiff::shared::PosixOffset { second: #second } + } + } +} diff --git a/tools/vendor/jiff-static/src/shared/crc32/mod.rs b/tools/vendor/jiff-static/src/shared/crc32/mod.rs new file mode 100644 index 0000000000..3d63946f5a --- /dev/null +++ b/tools/vendor/jiff-static/src/shared/crc32/mod.rs @@ -0,0 +1,48 @@ +// auto-generated by: jiff-cli generate shared + +use self::table::{TABLE, TABLE16}; + +mod table; + +/// Returns the "masked" CRC32 checksum of the slice using the Castagnoli +/// polynomial. +/// +/// This "masked" checksum is the same one used by the Snappy frame format. +/// Masking is supposed to make the checksum robust with respect to data that +/// contains the checksum itself. +pub(crate) fn sum(buf: &[u8]) -> u32 { + let sum = slice16(0, buf); + (sum.wrapping_shr(15) | sum.wrapping_shl(17)).wrapping_add(0xA282EAD8) +} + +/// Returns the CRC32 checksum of `buf` using the Castagnoli polynomial. +/// +/// This computes the checksum by looking at 16 bytes from the given slice +/// per iteration. +fn slice16(prev: u32, mut buf: &[u8]) -> u32 { + let mut crc: u32 = !prev; + while buf.len() >= 16 { + crc ^= u32::from_le_bytes(buf[..4].try_into().unwrap()); + crc = TABLE16[0][usize::from(buf[15])] + ^ TABLE16[1][usize::from(buf[14])] + ^ TABLE16[2][usize::from(buf[13])] + ^ TABLE16[3][usize::from(buf[12])] + ^ TABLE16[4][usize::from(buf[11])] + ^ TABLE16[5][usize::from(buf[10])] + ^ TABLE16[6][usize::from(buf[9])] + ^ TABLE16[7][usize::from(buf[8])] + ^ TABLE16[8][usize::from(buf[7])] + ^ TABLE16[9][usize::from(buf[6])] + ^ TABLE16[10][usize::from(buf[5])] + ^ TABLE16[11][usize::from(buf[4])] + ^ TABLE16[12][usize::from((crc >> 24) as u8)] + ^ TABLE16[13][usize::from((crc >> 16) as u8)] + ^ TABLE16[14][usize::from((crc >> 8) as u8)] + ^ TABLE16[15][usize::from((crc) as u8)]; + buf = &buf[16..]; + } + for &b in buf { + crc = TABLE[usize::from((crc as u8) ^ b)] ^ (crc >> 8); + } + !crc +} diff --git a/tools/vendor/jiff-static/src/shared/crc32/table.rs b/tools/vendor/jiff-static/src/shared/crc32/table.rs new file mode 100644 index 0000000000..35e9553302 --- /dev/null +++ b/tools/vendor/jiff-static/src/shared/crc32/table.rs @@ -0,0 +1,798 @@ +// auto-generated by: jiff-cli generate shared + +// auto-generated by: jiff-cli generate crc32 + +pub(super) const TABLE: [u32; 256] = [ + 0, 4067132163, 3778769143, 324072436, 3348797215, 904991772, 648144872, + 3570033899, 2329499855, 2024987596, 1809983544, 2575936315, 1296289744, + 3207089363, 2893594407, 1578318884, 274646895, 3795141740, 4049975192, + 51262619, 3619967088, 632279923, 922689671, 3298075524, 2592579488, + 1760304291, 2075979607, 2312596564, 1562183871, 2943781820, 3156637768, + 1313733451, 549293790, 3537243613, 3246849577, 871202090, 3878099393, + 357341890, 102525238, 4101499445, 2858735121, 1477399826, 1264559846, + 3107202533, 1845379342, 2677391885, 2361733625, 2125378298, 820201905, + 3263744690, 3520608582, 598981189, 4151959214, 85089709, 373468761, + 3827903834, 3124367742, 1213305469, 1526817161, 2842354314, 2107672161, + 2412447074, 2627466902, 1861252501, 1098587580, 3004210879, 2688576843, + 1378610760, 2262928035, 1955203488, 1742404180, 2511436119, 3416409459, + 969524848, 714683780, 3639785095, 205050476, 4266873199, 3976438427, + 526918040, 1361435347, 2739821008, 2954799652, 1114974503, 2529119692, + 1691668175, 2005155131, 2247081528, 3690758684, 697762079, 986182379, + 3366744552, 476452099, 3993867776, 4250756596, 255256311, 1640403810, + 2477592673, 2164122517, 1922457750, 2791048317, 1412925310, 1197962378, + 3037525897, 3944729517, 427051182, 170179418, 4165941337, 746937522, + 3740196785, 3451792453, 1070968646, 1905808397, 2213795598, 2426610938, + 1657317369, 3053634322, 1147748369, 1463399397, 2773627110, 4215344322, + 153784257, 444234805, 3893493558, 1021025245, 3467647198, 3722505002, + 797665321, 2197175160, 1889384571, 1674398607, 2443626636, 1164749927, + 3070701412, 2757221520, 1446797203, 137323447, 4198817972, 3910406976, + 461344835, 3484808360, 1037989803, 781091935, 3705997148, 2460548119, + 1623424788, 1939049696, 2180517859, 1429367560, 2807687179, 3020495871, + 1180866812, 410100952, 3927582683, 4182430767, 186734380, 3756733383, + 763408580, 1053836080, 3434856499, 2722870694, 1344288421, 1131464017, + 2971354706, 1708204729, 2545590714, 2229949006, 1988219213, 680717673, + 3673779818, 3383336350, 1002577565, 4010310262, 493091189, 238226049, + 4233660802, 2987750089, 1082061258, 1395524158, 2705686845, 1972364758, + 2279892693, 2494862625, 1725896226, 952904198, 3399985413, 3656866545, + 731699698, 4283874585, 222117402, 510512622, 3959836397, 3280807620, + 837199303, 582374963, 3504198960, 68661723, 4135334616, 3844915500, + 390545967, 1230274059, 3141532936, 2825850620, 1510247935, 2395924756, + 2091215383, 1878366691, 2644384480, 3553878443, 565732008, 854102364, + 3229815391, 340358836, 3861050807, 4117890627, 119113024, 1493875044, + 2875275879, 3090270611, 1247431312, 2660249211, 1828433272, 2141937292, + 2378227087, 3811616794, 291187481, 34330861, 4032846830, 615137029, + 3603020806, 3314634738, 939183345, 1776939221, 2609017814, 2295496738, + 2058945313, 2926798794, 1545135305, 1330124605, 3173225534, 4084100981, + 17165430, 307568514, 3762199681, 888469610, 3332340585, 3587147933, + 665062302, 2042050490, 2346497209, 2559330125, 1793573966, 3190661285, + 1279665062, 1595330642, 2910671697, +]; + +pub(super) const TABLE16: [[u32; 256]; 16] = [ + [ + 0, 4067132163, 3778769143, 324072436, 3348797215, 904991772, + 648144872, 3570033899, 2329499855, 2024987596, 1809983544, 2575936315, + 1296289744, 3207089363, 2893594407, 1578318884, 274646895, 3795141740, + 4049975192, 51262619, 3619967088, 632279923, 922689671, 3298075524, + 2592579488, 1760304291, 2075979607, 2312596564, 1562183871, + 2943781820, 3156637768, 1313733451, 549293790, 3537243613, 3246849577, + 871202090, 3878099393, 357341890, 102525238, 4101499445, 2858735121, + 1477399826, 1264559846, 3107202533, 1845379342, 2677391885, + 2361733625, 2125378298, 820201905, 3263744690, 3520608582, 598981189, + 4151959214, 85089709, 373468761, 3827903834, 3124367742, 1213305469, + 1526817161, 2842354314, 2107672161, 2412447074, 2627466902, + 1861252501, 1098587580, 3004210879, 2688576843, 1378610760, + 2262928035, 1955203488, 1742404180, 2511436119, 3416409459, 969524848, + 714683780, 3639785095, 205050476, 4266873199, 3976438427, 526918040, + 1361435347, 2739821008, 2954799652, 1114974503, 2529119692, + 1691668175, 2005155131, 2247081528, 3690758684, 697762079, 986182379, + 3366744552, 476452099, 3993867776, 4250756596, 255256311, 1640403810, + 2477592673, 2164122517, 1922457750, 2791048317, 1412925310, + 1197962378, 3037525897, 3944729517, 427051182, 170179418, 4165941337, + 746937522, 3740196785, 3451792453, 1070968646, 1905808397, 2213795598, + 2426610938, 1657317369, 3053634322, 1147748369, 1463399397, + 2773627110, 4215344322, 153784257, 444234805, 3893493558, 1021025245, + 3467647198, 3722505002, 797665321, 2197175160, 1889384571, 1674398607, + 2443626636, 1164749927, 3070701412, 2757221520, 1446797203, 137323447, + 4198817972, 3910406976, 461344835, 3484808360, 1037989803, 781091935, + 3705997148, 2460548119, 1623424788, 1939049696, 2180517859, + 1429367560, 2807687179, 3020495871, 1180866812, 410100952, 3927582683, + 4182430767, 186734380, 3756733383, 763408580, 1053836080, 3434856499, + 2722870694, 1344288421, 1131464017, 2971354706, 1708204729, + 2545590714, 2229949006, 1988219213, 680717673, 3673779818, 3383336350, + 1002577565, 4010310262, 493091189, 238226049, 4233660802, 2987750089, + 1082061258, 1395524158, 2705686845, 1972364758, 2279892693, + 2494862625, 1725896226, 952904198, 3399985413, 3656866545, 731699698, + 4283874585, 222117402, 510512622, 3959836397, 3280807620, 837199303, + 582374963, 3504198960, 68661723, 4135334616, 3844915500, 390545967, + 1230274059, 3141532936, 2825850620, 1510247935, 2395924756, + 2091215383, 1878366691, 2644384480, 3553878443, 565732008, 854102364, + 3229815391, 340358836, 3861050807, 4117890627, 119113024, 1493875044, + 2875275879, 3090270611, 1247431312, 2660249211, 1828433272, + 2141937292, 2378227087, 3811616794, 291187481, 34330861, 4032846830, + 615137029, 3603020806, 3314634738, 939183345, 1776939221, 2609017814, + 2295496738, 2058945313, 2926798794, 1545135305, 1330124605, + 3173225534, 4084100981, 17165430, 307568514, 3762199681, 888469610, + 3332340585, 3587147933, 665062302, 2042050490, 2346497209, 2559330125, + 1793573966, 3190661285, 1279665062, 1595330642, 2910671697, + ], + [ + 0, 329422967, 658845934, 887597209, 1317691868, 1562966443, + 1775194418, 2054015301, 2635383736, 2394315727, 3125932886, + 2851302177, 3550388836, 3225172499, 4108030602, 3883469565, + 1069937025, 744974838, 411091311, 186800408, 1901039709, 1659701290, + 1443537075, 1168652484, 2731618873, 2977147470, 2241069783, + 2520160928, 3965408229, 4294560658, 3407766283, 3636263804, + 2139874050, 1814657909, 1489949676, 1265388443, 822182622, 581114537, + 373600816, 98970183, 3802079418, 4047354061, 3319402580, 3598223395, + 2887074150, 3216496913, 2337304968, 2566056447, 1078858371, + 1408010996, 1728782957, 1957280282, 247755615, 493284136, 696337329, + 975428550, 3713716539, 3472378188, 4196393429, 3921508770, 2479927527, + 2154965136, 3029696521, 2805405822, 4279748100, 3971309171, + 3629315818, 3421531805, 2979899352, 2722054063, 2530776886, + 2239369025, 1644365244, 1906417099, 1162229074, 1457827109, 747201632, + 1059847191, 197940366, 409914617, 3235002245, 3547377650, 3885434731, + 4097154844, 2388153945, 2650459694, 2837276343, 3133144768, + 1573319741, 1315204170, 2055455955, 1763794084, 323786209, 15601046, + 873047311, 665533816, 2157716742, 2470362481, 2816021992, 3027996063, + 3457565914, 3719617709, 3914560564, 4210158659, 495511230, 237665993, + 986568272, 695160359, 1392674658, 1084235541, 1950857100, 1743073275, + 3210335367, 2902150384, 2552030313, 2344516638, 4057183579, + 3799067948, 3600188853, 3308527042, 575477567, 837783368, 84420561, + 380288934, 1825011427, 2137386644, 1266828813, 1478549114, 4223924985, + 3898696334, 3699821079, 3475264096, 3041499941, 2800419666, + 2450303947, 2175677372, 1725380929, 1970643254, 1100089775, + 1378914776, 677206173, 1006616810, 253257843, 482013188, 3288730488, + 3617886991, 3812834198, 4041319393, 2324458148, 2569990867, + 2915654218, 3194733117, 1494403264, 1253068983, 2119694382, + 1844797529, 395880732, 70922603, 819829234, 595526021, 2219317755, + 2548728204, 2735548693, 2964304226, 3401742375, 3647004752, + 3985066185, 4263891134, 425515587, 184435252, 1041885869, 767259354, + 1473690527, 1148462056, 1888717681, 1664160518, 3146639482, + 2821681165, 2630408340, 2406105315, 4110911910, 3869577681, + 3527588168, 3252691263, 647572418, 893105077, 31202092, 310281051, + 1746094622, 2075251305, 1331067632, 1559552647, 81018109, 393651338, + 596708371, 808686692, 1247698209, 1509737814, 1830514127, 2126116280, + 2579562309, 2321704754, 3196440491, 2905036764, 3611991705, + 3303540462, 4027559543, 3819779584, 991022460, 682841355, 475331986, + 267806181, 1973136544, 1715025111, 1390320718, 1098646585, 2785349316, + 3047659187, 2168471082, 2464327261, 3901714200, 4214093679, + 3486146550, 3697854337, 2069880831, 1761429384, 1545269009, + 1337489254, 903200291, 645342804, 311463629, 20059834, 3863682119, + 4125721648, 3238931625, 3534533854, 2831252891, 3143886316, + 2407812469, 2619790594, 1150955134, 1463334409, 1675566736, + 1887274727, 168841122, 431151061, 760577868, 1056433979, 3650022854, + 3391911345, 4274773288, 3983099231, 2533657626, 2225476717, + 2957098228, 2749572227, + ], + [ + 0, 2772537982, 1332695565, 3928932467, 2665391130, 1000289892, + 3518101015, 1961911401, 944848581, 2635115707, 2000579784, 3531603638, + 2794429151, 63834273, 3923822802, 1285642924, 1889697162, 3588485108, + 1070411655, 2592914937, 4001159568, 1262308334, 2702412701, 72489443, + 1223902031, 3987919153, 127668546, 2732426044, 3593332565, 1936487723, + 2571285848, 1006839590, 3779394324, 1141205354, 2922096921, 191511399, + 2140823310, 3671838064, 821366019, 2511642493, 3642082769, 2085902255, + 2524616668, 859506082, 1204511179, 3800757173, 144978886, 2917507512, + 2447804062, 883365088, 3733574803, 2076722925, 255337092, 2860101882, + 1079472265, 3843482359, 2847389787, 217459237, 3872975446, 1134131240, + 929635393, 2452131391, 2013679180, 3712474162, 3345318105, 1646531239, + 2282410708, 759906474, 1505436867, 4244289213, 383022798, 3012945072, + 4281646620, 1517628514, 2958814225, 354057839, 1642732038, 3299575928, + 780486667, 2344934005, 3083337043, 310800173, 4171804510, 1575566624, + 689527113, 2354629431, 1719012164, 3275200826, 2409022358, 718754280, + 3237581211, 1706558437, 289957772, 3020551666, 1579627905, 4217808895, + 639728589, 2204166579, 1766730176, 3423583166, 3103776727, 499010985, + 4153445850, 1389436836, 510674184, 3140605814, 1360992005, 4099835259, + 2158944530, 636449644, 3485578015, 1786782049, 1451427399, 4089615417, + 434918474, 3165505076, 3361579613, 1830563875, 2268262480, 577987118, + 1859270786, 3415452412, 566061711, 2231171313, 4027358360, 1431113446, + 3210989205, 438459627, 2334619459, 778495293, 3293062478, 1628026672, + 368694105, 2964865319, 1519812948, 4292285226, 3010873734, 372759544, + 4229503883, 1498974709, 766045596, 2297004002, 1657257873, 3347459567, + 4219800265, 1589942455, 3035257028, 296471226, 1700507347, 3222944941, + 708115678, 2406837920, 3285464076, 1721083506, 2361091585, 704312447, + 1560973334, 4165665384, 308658715, 3072610405, 1784908887, 3475119657, + 621600346, 2152549412, 4106037325, 1375517235, 3151133248, 513009726, + 1379054226, 4151517420, 492691615, 3088872161, 3438024328, 1772979446, + 2206418053, 650303227, 448917981, 3212862371, 1437508560, 4042207662, + 2216646087, 559859641, 3413116874, 1848743348, 579915544, 2278645094, + 1845468437, 3367898987, 3159255810, 420477308, 4079040783, 1449175921, + 1279457178, 3909314020, 53323159, 2792110057, 3533460352, 2011021822, + 2649948557, 951227379, 1947453791, 3511835425, 998021970, 2654800172, + 3939331397, 1334640443, 2778873672, 14921014, 1021348368, 2577471598, + 1938806813, 3603843683, 2721984010, 125811828, 3981540359, 1209069177, + 78755029, 2716870315, 1272899288, 4003427494, 2590970063, 1060012721, + 3573564098, 1883361468, 2902854798, 138911472, 3798556291, 1193856253, + 869836948, 2526624490, 2092432025, 3656804583, 2505519691, 806789173, + 3661127750, 2138698296, 193566289, 2932343855, 1155974236, 3785840162, + 3718541572, 2028331898, 2462786313, 931836279, 1132123422, 3862644576, + 202737427, 2840860013, 3858059201, 1085595071, 2862226892, 266047410, + 2066475995, 3731519909, 876919254, 2433035176, + ], + [ + 0, 3712330424, 3211207553, 1646430521, 2065838579, 2791807819, + 3292861042, 419477706, 4131677158, 721537374, 1227047015, 2489772767, + 2372293141, 1344534701, 838955412, 4014267180, 3915690301, 874584965, + 1443074748, 2336634884, 2454094030, 1325607542, 757179215, 4033087991, + 522244827, 3261429859, 2689069402, 2097306594, 1677910824, 3108456848, + 3680878761, 102787601, 3609531531, 174112307, 1749169930, 3037175218, + 2886149496, 1900187584, 325060345, 3458575425, 560035693, 4230274517, + 2651215084, 1128529492, 1514358430, 2265377830, 3844367647, 945934247, + 1044489654, 3808694030, 2166785591, 1550003343, 1164153925, + 2552643325, 4194613188, 658578812, 3355821648, 356543720, 2002970065, + 2854702953, 3005740963, 1851940123, 205575202, 3506798234, 2879807463, + 1994650975, 348224614, 3380926174, 3498339860, 230802604, 1877167509, + 2997282605, 1575107585, 2158466745, 3800375168, 1069593912, 650120690, + 4219840330, 2577870451, 1155695819, 1120071386, 2676442210, + 4255501659, 551577571, 971038505, 3836048785, 2257058984, 1539462672, + 3028716860, 1774397316, 199339709, 3601073157, 3483679951, 316741239, + 1891868494, 2911254006, 2088979308, 2714165716, 3286526189, 513917525, + 128023199, 3672428583, 3100006686, 1703146406, 2328307850, 1468170802, + 899681035, 3907363251, 4058323321, 748729281, 1317157624, 2479329344, + 2515008081, 1218597097, 713087440, 4156912488, 4005940130, 864051482, + 1369630755, 2363966107, 1671666103, 3202757391, 3703880246, 25235598, + 411150404, 3317957372, 2816904133, 2057511293, 1386268991, 2414175111, + 3989301950, 813842438, 696449228, 4106703476, 2531646285, 1268806133, + 2766449369, 2040594529, 461605208, 3334874080, 3754335018, 42152338, + 1621211307, 3185840659, 3150215170, 1719784122, 77814659, 3655790907, + 3236317681, 497279817, 2139187824, 2730803400, 1300241380, 2428875100, + 4075239525, 799183581, 916597271, 3957817519, 2311391638, 1417716526, + 2240142772, 1489008396, 987954741, 3886503053, 4272417863, 602031871, + 1103155142, 2625987966, 1942077010, 2927891690, 3433471443, 300103531, + 149131169, 3584435481, 3078925344, 1791035032, 1826712713, 2980365873, + 3548794632, 247719344, 398679418, 3397842882, 2829352699, 1977734211, + 2594508655, 1205904855, 633482478, 4169631318, 3783736988, 1019384868, + 1591745821, 2208675749, 4177958616, 608386144, 1180808537, 2602835937, + 2183440171, 1600195987, 1027835050, 3758501394, 256046398, 3523698566, + 2955269823, 1835039751, 1952498893, 2837802613, 3406292812, 373444084, + 274868197, 3441921373, 2936341604, 1916841692, 1799362070, 3053829294, + 3559339415, 157458223, 3861267459, 996404923, 1497458562, 2214907194, + 2634315248, 1078058824, 576935537, 4280745161, 774079059, 4083558635, + 2437194194, 1275136874, 1426174880, 2286164248, 3932590113, 925055641, + 3630686645, 86133517, 1728102964, 3125110924, 2739261510, 2113960702, + 472052679, 3244775807, 3343332206, 436378070, 2015367407, 2774907479, + 3160736413, 1629530149, 50471196, 3729230756, 822300808, 3964074544, + 2388947721, 1394727345, 1243701627, 2539965379, 4115022586, 671344706, + ], + [ + 0, 940666796, 1881333592, 1211347188, 3762667184, 3629437212, + 2422694376, 2826309188, 3311864721, 4252394557, 3041252553, + 2371140453, 623031585, 489937549, 1426090617, 1829832149, 2401395155, + 3073576575, 4278238859, 3339833639, 1869078371, 1467396303, 524615739, + 659845015, 1246063170, 1918109166, 979875098, 41343670, 2852181234, + 2450635614, 3659664298, 3795018758, 464041303, 599382779, 1804233231, + 1402668451, 4226616295, 3288097867, 2345653439, 3017718547, + 3738156742, 3873362282, 2934792606, 2533101106, 1049231478, 110850010, + 1319690030, 1991880834, 2492126340, 2895887144, 3836218332, + 3703137392, 1959750196, 1289618840, 82687340, 1023204032, 1374543637, + 1778167993, 567214157, 434008033, 2980602277, 2310606345, 3247095549, + 4187738449, 928082606, 255853826, 1198765558, 2137184858, 3608466462, + 4010130354, 2805336902, 2670159082, 4063650111, 3391557267, + 2182397543, 3120943563, 309583759, 711110691, 1649475799, 1514172283, + 3094547325, 2153931985, 3360805925, 4030774153, 1479980493, + 1613224545, 672426645, 268764473, 2098462956, 1157984064, 221700020, + 891793432, 2639380060, 2772488688, 3983761668, 3579973288, 754573305, + 350793813, 1557856417, 1690988301, 3434897737, 4104982245, 3164519953, + 2224017853, 3919500392, 3515857860, 2579237680, 2712495260, 165374680, + 835323252, 2046408064, 1105779244, 2749087274, 2613760390, 3556335986, + 3957853918, 1134428314, 2072997686, 868016066, 195932270, 1723643323, + 1588451863, 379480803, 781124943, 2264468235, 3202901159, 4141601875, + 3469392895, 1856165212, 1454619376, 511707652, 647062952, 2397531116, + 3069576256, 4274369716, 3335838488, 2881871565, 2480189793, + 3689349525, 3824578105, 1266704509, 1938886609, 1000521509, 62115977, + 3783308431, 3650214691, 2443340759, 2847081595, 29690431, 970220947, + 1911018855, 1240906443, 619167518, 485937330, 1422221382, 1825837034, + 3298951598, 4239617538, 3028344566, 2358358362, 1963614219, + 1293619111, 86556499, 1027199231, 2505039547, 2908664087, 3849126371, + 3715919439, 2959960986, 2289828918, 3226449090, 4166966126, + 1344853290, 1748613766, 537528946, 404448734, 4196925912, 3258543732, + 2315968128, 2988159276, 443400040, 578605252, 1783586864, 1381896092, + 1062144585, 123626981, 1332598033, 2004662973, 3742020857, 3877362517, + 2938661793, 2537096205, 1509146610, 1642254430, 701587626, 297799430, + 3115712834, 2175233774, 3381976602, 4052070838, 2626991203, + 2760235983, 3971377979, 3567715479, 2094074579, 1153459583, 217306507, + 887274023, 3604078113, 4005605773, 2800943481, 2665639637, 915693713, + 243601213, 1186381769, 2124927077, 330749360, 732412444, 1670646504, + 1535468868, 4092816128, 3420587180, 2211558488, 3149978612, + 1113262757, 2051695881, 846845437, 174635601, 2719921173, 2584730553, + 3527174989, 3928818913, 2268856628, 3207425688, 4145995372, + 3473912256, 1736032132, 1600704552, 391864540, 793382768, 3447286646, + 4117234906, 3176903726, 2236275586, 758961606, 355318378, 1562249886, + 1695507762, 136208615, 806293323, 2017247167, 1076744211, 3898334807, + 3494556155, 2558066959, 2691198627, + ], + [ + 0, 4012927769, 3683426499, 884788186, 3002414967, 1573215342, + 1769576372, 2252995757, 1611012127, 2402710278, 3146430684, + 1421530053, 3539152744, 1036207217, 159354795, 3863995570, 3222024254, + 792484647, 461410557, 4105239524, 1928922953, 2647223376, 2843060106, + 1178979475, 2685020193, 1329218360, 2072414434, 2495013883, 318709590, + 4258231375, 3379806101, 641979532, 2247366285, 1791262100, 1584969294, + 2974342487, 922821114, 3627109091, 3968696633, 62777888, 3857845906, + 180512139, 1048489553, 3511600456, 1460091365, 3090633468, 2357958950, + 1673261631, 1173890739, 2865253802, 2658436720, 1900342633, + 4144828868, 406682333, 746696967, 3283212830, 637419180, 3402519989, + 4268924527, 289600886, 2534083035, 2017157826, 1283959064, 2746728961, + 235166699, 3778294002, 3582524200, 985174065, 3169938588, 1405159301, + 1736297567, 2286790470, 1845642228, 2167548141, 3046040375, + 1522436142, 3707204739, 868687770, 125555776, 3897278297, 3456658389, + 557318348, 361024278, 4206141455, 2096979106, 2479699899, 2809265249, + 1212258168, 2920182730, 1094588627, 1971507977, 2595403792, 486229181, + 4090179492, 3346523262, 675778407, 2347781478, 1690314367, 1350364581, + 3209463484, 956660241, 3593801992, 3800685266, 230273483, 3958789497, + 80100960, 813364666, 3746209443, 1493393934, 3056797975, 2190459597, + 1841277396, 1274838360, 2764838465, 2423315867, 2134947458, + 4178135599, 372842806, 579201772, 3451224565, 737830215, 3301576286, + 4034315652, 524725917, 2567918128, 1983854889, 1115943667, 2914228714, + 470333398, 4080590031, 3347322645, 682916876, 2935849121, 1104014264, + 1970348130, 2587970427, 2081337289, 2470364368, 2810318602, + 1219650579, 3472595134, 567014311, 360134781, 4198978404, 3691284456, + 859106545, 126363435, 3904392242, 1861333151, 2176965510, 3044872284, + 1515027269, 3154288631, 1395848430, 1737375540, 2294174765, 251111552, + 3787965337, 3581610051, 978019162, 2583470427, 1993132610, 1114636696, + 2906679937, 722048556, 3292134709, 4035262191, 531979766, 4193958212, + 382390877, 578165127, 3443946142, 1259310643, 2755650858, 2424516336, + 2142455273, 1508938085, 3066100348, 2189177254, 1833720511, + 3943015954, 70634763, 814286545, 3753471432, 972458362, 3603358307, + 3799656889, 222970528, 2332278285, 1681118484, 1351556814, 3216995799, + 302836797, 4248602404, 3380628734, 649078759, 2700729162, 1338617939, + 2071296905, 2487554192, 1913320482, 2637864763, 2844153057, + 1186349048, 3237987157, 802138188, 460546966, 4098033807, 3523271683, + 1026602778, 160201920, 3871086553, 1626729332, 2412085357, 3145288631, + 1414078638, 2986787868, 1563864837, 1770677471, 2260340678, 15987563, + 4022573170, 3682554792, 877607089, 2549676720, 2026410409, 1282693747, + 2739155306, 621661639, 3393038046, 4269894916, 296814109, 4160676527, + 416188854, 745685612, 3275893109, 1158403544, 2856042177, 2659677467, + 1907826178, 1475660430, 3099894167, 2356701773, 1665663316, + 3842113017, 171022048, 1049451834, 3518838307, 938660497, 3636640136, + 3967709778, 55449931, 2231887334, 1782025983, 1586185509, 2981834300, + ], + [ + 0, 1745038536, 3490077072, 3087365464, 2782971345, 3454265625, + 1978047553, 501592201, 1311636819, 640602523, 2653660355, 4129851403, + 3956095106, 2211320906, 1003184402, 1405636058, 2623273638, + 4099462766, 1281205046, 610177022, 968572791, 1371018175, 3921503975, + 2176731695, 3530950645, 3128240957, 40918629, 1785950893, 2006368804, + 529919724, 2811272116, 3482564476, 1029407677, 1431875445, 3982350893, + 2237593317, 2562410092, 4038617764, 1220354044, 549335860, 1937145582, + 460673574, 2742036350, 3413314486, 3600202559, 3197474807, 110158511, + 1855180391, 2701162779, 3372438995, 1896226955, 419761219, 81837258, + 1826852866, 3571901786, 3169175954, 4012737608, 2267981952, + 1059839448, 1462300944, 1254965657, 583953745, 2597001225, 4073206977, + 2058815354, 313797554, 2863750890, 3266474530, 3747070635, 3075796579, + 257006395, 1733474291, 882571817, 1553585889, 3835470777, 2359267185, + 2440708088, 4185461552, 1098671720, 696208032, 3874291164, 2398081300, + 921347148, 1592363140, 1124874253, 722408645, 2466931101, 4211690837, + 2831220879, 3233950791, 2026330399, 281310679, 220317022, 1696786838, + 3710360782, 3039080454, 1206682823, 804235279, 2548751703, 4293521823, + 3792453910, 2316266974, 839522438, 1510552654, 163674516, 1640125788, + 3653705732, 2982415564, 2887892037, 3290599565, 2082989525, 337955101, + 3686235745, 3014939305, 196159473, 1672612665, 2119678896, 374642552, + 2924601888, 3327315688, 2509931314, 4254707706, 1167907490, 765458026, + 813319907, 1484352043, 3766230899, 2290037691, 4117630708, 2641175100, + 627595108, 1298889644, 1351535397, 948824045, 2156434101, 3901472381, + 3141792679, 3544244079, 1799727671, 54953727, 514012790, 1990204094, + 3466948582, 2795914030, 1765143634, 20371610, 3107171778, 3509616906, + 3436523907, 2765495627, 483616787, 1959806171, 655886593, 1327179209, + 4145959057, 2669509721, 2197343440, 3942375448, 1392416064, 989706632, + 3358952777, 2687934849, 406050009, 1882257425, 1842694296, 97936464, + 3184726280, 3587194304, 2249748506, 3994770642, 1444817290, + 1042089282, 603502027, 1274779907, 4093570139, 2617098387, 1416525807, + 1013799719, 2221420159, 3966436023, 4052660798, 2576195318, 562621358, + 1233897318, 440634044, 1916839540, 3393573676, 2722562020, 3215150957, + 3617612709, 1873090301, 128334389, 2413365646, 3889833286, 1608470558, + 937196758, 708430943, 1111154839, 4198471119, 2453453063, 3254056157, + 2851592213, 301116749, 2045870469, 1679044876, 202841540, 3021105308, + 3692119124, 327349032, 2072109024, 3280251576, 2877785712, 3059889913, + 3730905649, 1717858153, 241648545, 1571753595, 900473523, 2376685547, + 3853155107, 4165979050, 2420959074, 675910202, 1078640370, 2994899507, + 3665929979, 1652872099, 176684907, 392318946, 2137088810, 3345225330, + 2942778042, 4239357792, 2494323624, 749285104, 1151992376, 1498395313, + 827104889, 2303322913, 3779774441, 786002069, 1188715613, 4276037893, + 2531001805, 2335814980, 3812268428, 1530916052, 859619356, 1626639814, + 150446350, 2968704086, 3639736478, 3306440727, 2903991519, 353505671, + 2098281807, + ], + [ + 0, 1228700967, 2457401934, 3678701417, 555582061, 1747058506, + 3009771555, 4200137988, 1111164122, 185039357, 3494117012, 2575270835, + 1663469239, 706411408, 4049501433, 3093430750, 2222328244, 3444208787, + 370078714, 1597148893, 2775288793, 3965187838, 924021143, 2117012656, + 3326938478, 2406576201, 1412822816, 487164423, 3880816387, 2926375460, + 1965585741, 1007945834, 218129817, 1144789182, 2675482583, 3594838768, + 740157428, 1696701139, 3194297786, 4149829789, 1329291587, 101129316, + 3712195341, 2491409962, 1848042286, 656055817, 4234025312, 3043124295, + 2306239533, 3226079498, 453940835, 1379068740, 2825645632, 3780612967, + 974328846, 1932486953, 3410847991, 2188449232, 1496683193, 269086622, + 3931171482, 2741802941, 2015891668, 823422451, 436259634, 1396487701, + 2289578364, 3242478683, 991775071, 1914778744, 2842014481, 3763981878, + 1480314856, 285717199, 3393402278, 2206156929, 2032553349, 807022754, + 3948853195, 2724383468, 2658583174, 3612000161, 202258632, 1160922607, + 3211477227, 4132912588, 756267685, 1680852866, 3696084572, 2507258747, + 1312111634, 118047029, 4249895985, 3026991382, 1864941183, 638894936, + 385920683, 1581044620, 2239255781, 3427019202, 907881670, 2132890081, + 2758137480, 3982076847, 1429973617, 470275926, 3343077439, 2390699288, + 1948657692, 1025135931, 3864973906, 2942480245, 2474026783, + 3662338616, 17718609, 1211244662, 2993366386, 4216805461, 538173244, + 1764729371, 3511526341, 2557599458, 1127569803, 168371372, 4031783336, + 3110886543, 1646844902, 722773697, 872519268, 2101209923, 2792975402, + 4014280973, 354161673, 1545627950, 2271538759, 3461911392, 1983550142, + 1057419161, 3829557488, 2910721495, 1462178003, 505114100, 3311397533, + 2355337146, 2960629712, 4182500087, 571434398, 1798510777, 2439650749, + 3629539482, 51570675, 1244568276, 4065106698, 3144738349, 1614045508, + 688397411, 3545307495, 2590860352, 1093264169, 135634446, 956429309, + 1883082458, 2876836275, 3796202644, 404517264, 1361054903, 2321845214, + 3277387513, 2067461927, 839289344, 3913420137, 2692640846, 1512535370, + 320538733, 3361705732, 2170810915, 3178756681, 4098590574, 789512199, + 1714650400, 2624223268, 3579184387, 236094058, 1194262349, 4283235987, + 3060827060, 1832125661, 604535290, 3729882366, 2540503513, 1277789872, + 85326743, 771841366, 1732059249, 3162089240, 4114995775, 253550395, + 1176543772, 2640586101, 3562559570, 1815763340, 621159595, 4265780162, + 3078545125, 1294457825, 68921030, 3747553711, 2523094152, 2859947234, + 3813353925, 940551852, 1899221899, 2339034767, 3260459944, 420621505, + 1345212902, 3897315384, 2708483359, 2050271862, 856217425, 3377582677, + 2154671986, 1529423899, 303387964, 587282639, 1782400488, 2977546881, + 4165320614, 35437218, 1260439429, 2422489324, 3646438859, 1631206421, + 671498546, 4081239643, 3128867708, 1076346488, 152814431, 3529458742, + 2606971153, 2809606523, 3997912156, 890227509, 2083763730, 2255139606, + 3478572593, 336742744, 1563309183, 3846976929, 2893039750, 1999949807, + 1040757448, 3293689804, 2372782827, 1445547394, 521482405, + ], + [ + 0, 4097758792, 3985758817, 430902313, 3738157619, 720442491, + 861804626, 3345010202, 3094606487, 1280124127, 1440884982, 2715614910, + 1723609252, 2458052332, 2335042245, 2131967117, 1963693023, + 2167752087, 2560248254, 1822728182, 2881769964, 1610254244, + 1180011405, 2993380805, 3447218504, 960935680, 552188713, 3570888033, + 330802043, 3884545331, 4263934234, 169389906, 3927386046, 506065398, + 126287327, 4088933271, 886629773, 3236312005, 3645456364, 762833316, + 1382339881, 2790916961, 3220508488, 1271650560, 2360022810, + 2023080274, 1631243643, 2500074291, 2669458529, 1797415465, + 1921871360, 2259925064, 1104377426, 3052249114, 2890050611, + 1484552827, 661604086, 3545338046, 3405683863, 1052789471, 4188046533, + 228479629, 338779812, 3759114476, 3519166861, 637333445, 1012130796, + 3362600356, 252574654, 4214435318, 3801883103, 379778967, 1773259546, + 2643402066, 2216694139, 1881065267, 3078490409, 1128324961, + 1525666632, 2932933888, 2764679762, 1358396442, 1230532659, + 3177621115, 2047240289, 2386083369, 2543301120, 1672045640, 481974469, + 3901001357, 4046160548, 85284076, 3262487286, 910904510, 803487895, + 3688535775, 1003822643, 3488335995, 3594830930, 578425370, 3843742720, + 287574600, 143328865, 4239773737, 2208754852, 2006465260, 1849112261, + 2584338573, 1567174295, 2841114847, 2969105654, 1153835710, + 1323208172, 3135265700, 2739885965, 1467056581, 2417053663, + 1680841111, 2105578942, 2310947830, 4138565499, 43231539, 456959258, + 4009915218, 677559624, 3697044224, 3321063209, 835563873, 2791835115, + 1381421987, 1274666890, 3217492418, 2024261592, 2358841744, + 2503351737, 1627966449, 505149308, 3928301876, 4085919005, 129301333, + 3235132751, 887808775, 759557934, 3648731494, 3546519092, 660422780, + 1056066645, 3402406429, 229397511, 4187128399, 3762130534, 335763502, + 1796236451, 2670637803, 2256649922, 1925146762, 3051333264, + 1105293528, 1481538801, 2893064889, 1283400277, 3091330077, + 2716792884, 1439706748, 2461065318, 1720596014, 2132883975, + 2334125135, 4094480578, 3278474, 429722275, 3986939115, 717427441, + 3741172921, 3344091280, 862723800, 963948938, 3444205506, 3571805163, + 551271843, 3887821753, 327525873, 170568152, 4262756240, 2164736797, + 1966708053, 1821809020, 2561167156, 1606975790, 2885048166, + 2992200527, 1181191431, 2007645286, 2207574574, 2587616775, + 1845833807, 2842033749, 1566255133, 1156850740, 2966090364, + 3487158001, 1005000889, 575149200, 3598107352, 286657730, 3844659850, + 4236760739, 146342123, 44150713, 4137646577, 4012930520, 453944208, + 3698224522, 676379586, 838842347, 3317784995, 3134348590, 1324125030, + 1464043343, 2742898951, 1679662877, 2418231637, 2307671420, + 2108855092, 2646416344, 1770245520, 1881981369, 2215778289, + 1131600363, 3075215267, 2934113162, 1524487618, 634317135, 3522182919, + 3361682222, 1013048678, 4211157884, 255851828, 378597661, 3803064149, + 3904276487, 478699087, 86463078, 4044981294, 913918516, 3259473020, + 3689451605, 802571805, 1355119248, 2767957208, 3176440049, 1231713977, + 2383067299, 2050256619, 1671127746, 2544219274, + ], + [ + 0, 3411442597, 2470478267, 1477900830, 594376071, 3896184354, + 2955801660, 2071695257, 1188752142, 2374799531, 3583666869, 516690192, + 1706532489, 2934039852, 4143390514, 1033987223, 2377504284, + 1189326265, 519395239, 3584240642, 2933433243, 1703860286, 1033380384, + 4140718469, 3413064978, 3753655, 1479523497, 2474231564, 3892463765, + 592720688, 2067974446, 2954146443, 512219849, 3587285356, 2378652530, + 1183916247, 1038790478, 4139570411, 2930388725, 1711035728, + 1482502599, 2466990690, 3407720572, 4967385, 2066760768, 2959491045, + 3899704827, 589741662, 2469530837, 1482979184, 7507310, 3408197323, + 2959046994, 2064188151, 589297897, 3897131852, 3588810715, 515808382, + 1185441376, 2382241221, 4135948892, 1037298169, 1707414503, + 2928896066, 1024439698, 4133080631, 2924426281, 1696157580, 509788181, + 3577002928, 2367832494, 1182022155, 2077580956, 2961384761, + 3902136103, 600024194, 1488464667, 2481868990, 3422071456, 11456773, + 2965005198, 2079070251, 603644469, 3903625616, 2480346633, 1484877228, + 9934770, 3418483735, 4133521536, 1027011365, 1696598331, 2926998174, + 3574463751, 509314722, 1179483324, 2367358745, 596143963, 3906868478, + 2965958368, 2073990469, 15014620, 3417530745, 2477103975, 1492377794, + 1699906645, 2919563248, 4128376302, 1027898955, 1178595794, + 2372504183, 3581898857, 506006476, 2923280711, 1701561058, 1031616764, + 4130030425, 2370882752, 1174845285, 504384891, 3578148574, 3907473993, + 598813164, 2074596338, 2968627287, 3414829006, 14441579, 1489675893, + 2476531152, 2048879396, 2974355585, 3915377311, 571052346, 1500651171, + 2451858694, 3392315160, 23389373, 1019576362, 4153670543, 2944729489, + 1691580980, 531166637, 3573452296, 2364044310, 1203638195, 4155161912, + 1023198877, 1693072515, 2948351782, 3569862847, 529642266, 1200048388, + 2362520225, 2976929334, 2049322387, 573626253, 3915820072, 2451383217, + 1498109972, 22913546, 3389774255, 1687728621, 2949565000, 4158140502, + 1015958515, 1207288938, 2359541711, 3568649681, 534986356, 574773987, + 3910410566, 2969754456, 2052366589, 19869540, 3396949185, 2456792799, + 1496962426, 3912067057, 578493524, 2054022730, 2973474287, 3393196662, + 18246099, 1493210061, 2455169128, 2952236287, 1688336218, 1018629444, + 4158748385, 2358966648, 1204585181, 534411459, 3565945702, 1192287926, + 2353440019, 3562037005, 520496296, 1685957425, 2938884244, 4147980938, + 1013666095, 30029240, 3399241245, 2458563587, 1507643302, 581386303, + 3924900762, 2984755588, 2058467873, 3399813290, 32731919, 1508215057, + 2461266612, 3922230573, 580781704, 2055797910, 2984150835, 2357191588, + 1193908225, 524247583, 3563657658, 2937230883, 1682238854, 1012012952, + 4144262205, 1503069311, 2462154714, 3403122116, 25296481, 2063233528, + 2980842077, 3921342531, 585927654, 525201265, 3558577364, 2349690570, + 1197151599, 1008769782, 4151763283, 2942311245, 1681285352, + 3559051875, 527739334, 1197626328, 2352228477, 4149192676, 1008327745, + 1678714463, 2941869562, 2465741165, 1504592584, 28883158, 3404645235, + 2979351786, 2059614031, 584437073, 3917723380, + ], + [ + 0, 2540828609, 722442611, 3162402482, 1444885222, 3245262119, + 2098244501, 3932249172, 2889770444, 995070477, 2268200127, 272632702, + 4196489002, 1834000619, 3509505625, 1180645784, 1569766761, + 3403762344, 1990140954, 3790525403, 193957775, 2633922638, 545265404, + 3086082365, 4054767781, 1725902692, 3668001238, 1305523735, + 2813454915, 817896834, 2361291568, 466584817, 3139533522, 743476499, + 2418992033, 123671648, 3980281908, 2052046837, 3325153607, 1363158662, + 387915550, 2154752223, 1007715949, 2875290028, 1090530808, 3597785657, + 1779414155, 4252910410, 3870410683, 1908420730, 3451805384, + 1523558665, 2964256093, 668926620, 2611047470, 214997999, 1250927223, + 3724432822, 1635793668, 4143041733, 479236241, 2346805072, 933169634, + 2700017187, 1940816725, 3839849620, 1486952998, 3486576103, 632402355, + 2998945394, 247343296, 2580537089, 3750815385, 1222709592, 4104093674, + 1676576811, 2307904639, 519971774, 2726317324, 905034445, 775831100, + 3109014013, 87140175, 2453688462, 2015431898, 4015061787, 1395561897, + 3294585448, 2181061616, 359771185, 2836382339, 1048458562, 3558828310, + 1131323095, 4279300197, 1751189412, 3364878727, 1610485318, + 3816841460, 1961989941, 2660289377, 165756064, 3047117330, 586065363, + 1689361483, 4089473930, 1337853240, 3637506809, 850308781, 2782878060, + 429995998, 2396045343, 2501854446, 40809263, 3188776349, 694233692, + 3271587336, 1416724937, 3893358459, 2138970298, 958472482, 2924533475, + 305051729, 2237616016, 1866339268, 4165985285, 1144097463, 3544218998, + 3881633450, 1881993579, 3427966937, 1529047064, 2973905996, 640926605, + 2588781887, 222059262, 1264804710, 3692205223, 1617754645, 4145876436, + 494686592, 2316150337, 913557747, 2701279026, 3134045123, 767314946, + 2445419184, 112448881, 3973220645, 2074312420, 3353153622, 1353508759, + 385080847, 2172791246, 1039943548, 2861412541, 1089268969, 3617397544, + 1810068890, 4237460059, 1551662200, 3406662585, 2004083979, + 3758232266, 174280350, 2635250015, 560781293, 3055362092, 4030863796, + 1731456629, 3679289543, 1279031046, 2791123794, 825023635, 2371007009, + 438519264, 32293137, 2526885584, 719542370, 3180507043, 1475605495, + 3229746230, 2096917124, 3951926597, 2916263133, 983782172, 2262646190, + 296536687, 4224554555, 1824285178, 3502378824, 1202976905, 2498987519, + 58880574, 3220970636, 680389453, 3270293273, 1436369112, 3923979882, + 2123553195, 953016371, 2948339698, 331512128, 2226359937, 1859310293, + 4188218644, 1172130726, 3534535783, 3378722966, 1578291031, + 3798770149, 1964856868, 2675706480, 135134641, 3027473155, 587359426, + 1700617562, 4063013531, 1314047017, 3642962920, 859991996, 2754844797, + 407762639, 2403074318, 802357037, 3097692396, 81618526, 2477560223, + 2043530699, 4005313034, 1388467384, 3316884345, 2213321441, 345861408, + 2833449874, 1066595411, 3589515271, 1115840454, 4277940596, + 1770899125, 1916944964, 3845371269, 1498274615, 3460050166, 610103458, + 3006039907, 257092049, 2552438288, 3732678536, 1225642057, 4118003451, + 1644316986, 2288194926, 521331375, 2741799965, 874347484, + ], + [ + 0, 829543472, 1659086944, 1402109008, 3318173888, 4105602288, + 2804218016, 2522164368, 2388842353, 3205694273, 3967909649, + 3723537185, 1269139377, 2060735361, 692465617, 406322145, 422172691, + 676858915, 2076864627, 1253811267, 3706620115, 3986644195, 3188531379, + 2407330947, 2538278754, 2788875090, 4121470722, 3302585138, + 1384931234, 1677560722, 812644290, 18752498, 844345382, 52585494, + 1353717830, 1640090742, 4153729254, 3336910038, 2507622534, + 2751896758, 3157354327, 2369827687, 3738357559, 4020443911, + 2046179223, 1216865191, 454402039, 711216071, 729442357, 436976645, + 1234813013, 2028475493, 4005378293, 3754749125, 2355007637, + 3173991589, 2769862468, 2489936756, 3355121444, 4136289044, + 1625288580, 1370373044, 37504996, 860722132, 1688690764, 1440134268, + 105170988, 926227484, 2707435660, 2417091772, 3280181484, 4075965660, + 3938826045, 3686032141, 2284199773, 3109538669, 788719613, 510866381, + 1306611613, 2089851821, 2106505311, 1291807855, 527241279, 773637135, + 3091851423, 2302164143, 3668590847, 3957036239, 4092358446, + 3265116958, 2433730382, 2692617086, 908804078, 123399134, 1422432142, + 1706640318, 1458884714, 1736770650, 873953290, 90614842, 2469626026, + 2722256026, 4056950986, 3231841530, 3633710875, 3924284203, + 3128274811, 2332326731, 491870171, 740328427, 2142437307, 1321413515, + 1339885689, 2125257801, 759078937, 474969129, 2316982457, 3144387721, + 3908694233, 3649578217, 3250577160, 4040035128, 2740746088, + 2452464472, 75009992, 889805816, 1721444264, 1475015576, 3377381528, + 4164883624, 2880268536, 2598157512, 210341976, 1039680616, 1852454968, + 1595665416, 1194072041, 1985856473, 634371977, 348023737, 2196457257, + 3013251865, 3758681929, 3514383225, 3496417419, 3776367803, + 2995040491, 2213897435, 362825803, 617716859, 2000937003, 1177695259, + 1577439226, 1869880266, 1021732762, 228045738, 2613223226, 2863876874, + 4179703642, 3360744298, 4213010622, 3396117646, 2583615710, + 2827947246, 1054482558, 262927438, 1547274270, 1833458734, 1971300303, + 1141797887, 396103599, 653122463, 2964911887, 2177442623, 3529203567, + 3811216223, 3795101869, 3544546461, 2161574093, 2980500733, 670300269, + 377629789, 1158696973, 1952547901, 1817608156, 1562881004, 246798268, + 1069810572, 2844864284, 2564881196, 3413280636, 4194521932, + 2917769428, 2627237092, 3473541300, 4269530244, 1747906580, + 1499407396, 181229684, 1002212420, 596342693, 318415765, 1097392069, + 1880689653, 3863750501, 3611161429, 2226097925, 3051248437, + 3032512711, 2243013879, 3592671399, 3880912023, 1896294407, + 1081539639, 333742183, 580211799, 983740342, 198409094, 1480656854, + 1764807654, 4284874614, 3457428294, 2642827030, 2901902118, + 2679771378, 2932589762, 4250515602, 3425201314, 1518157874, + 1795986434, 949938258, 166673506, 299419523, 547951539, 1933275107, + 1112194003, 3558840131, 3849208691, 3069984547, 2274224915, + 2257832161, 3085049041, 3832569985, 3573658801, 1129617441, + 1915046929, 565653569, 281470065, 150019984, 964742048, 1779611632, + 1533240256, 3442888528, 4232551264, 2950031152, 2661561088, + ], + [ + 0, 819083365, 1638166730, 1366706351, 3276333460, 4087011825, + 2733412702, 2453580091, 2206053849, 3014626748, 3805922579, + 3524001142, 1077236813, 1894214696, 563160199, 289610978, 51846467, + 868558118, 1655926153, 1382110700, 3227516119, 4035822770, 2717617181, + 2435429496, 2154473626, 2964885759, 3788429392, 3508330549, + 1126320398, 1945137515, 579221956, 307495329, 103692934, 922485475, + 1737116236, 1465414185, 3311852306, 4122272631, 2764221400, + 2484114365, 2236845919, 3045177146, 3841458069, 3559245808, + 1176202955, 1992906414, 666836481, 393029220, 87631813, 904601504, + 1688033039, 1414492010, 3329345105, 4137942580, 2815800987, + 2533854974, 2252640796, 3063327353, 3890275030, 3610434227, + 1158443912, 1977502701, 614990658, 343554855, 207385868, 1015958889, + 1844970950, 1563049379, 3474232472, 4291210493, 2930828370, + 2657279031, 2401353941, 3220437168, 4001738783, 3730278522, + 1281958209, 2092636452, 768430475, 488597998, 256600143, 1067012138, + 1861163141, 1581064416, 3422783963, 4241600958, 2913466641, + 2641740148, 2352405910, 3169117683, 3985812828, 3711997241, + 1333672962, 2141979751, 786058440, 503870637, 175263626, 983594991, + 1809203008, 1526990629, 3376066078, 4192769659, 2828984020, + 2555176625, 2299525715, 3118318134, 3903556249, 3631854332, + 1246174151, 2056594338, 736324365, 456217448, 157635273, 968321708, + 1757487619, 1477646950, 3391992669, 4211051320, 2877932439, + 2606496754, 2316887824, 3133857653, 3955005402, 3681464255, + 1229981316, 2038578913, 687109710, 405163563, 414771736, 678089341, + 2031917778, 1238278839, 3689941900, 3944887273, 3126098758, + 2324054819, 2613403585, 2870437796, 4200673035, 3400734574, + 1485684309, 1751090736, 959041183, 167507706, 464516955, 729665342, + 2047575953, 1255784436, 3639023311, 3895799466, 3108201989, + 2308005472, 2563916418, 2818603751, 4185272904, 3382970925, + 1536860950, 1799920499, 977195996, 183299001, 513200286, 776268027, + 2134024276, 1340119089, 3722326282, 3976989039, 3162128832, + 2359851429, 2649449799, 2906217762, 4233041293, 3432852968, + 1587774675, 1852947638, 1057485849, 265669756, 495046109, 760477112, + 2082848023, 1291289970, 3737726025, 3994752044, 3211615363, + 2411685094, 2667345924, 2922266721, 4283959502, 3481940139, + 1572116880, 1835442677, 1007741274, 214094143, 350527252, 607561585, + 1967189982, 1167251387, 3618406016, 3883812581, 3053981258, + 2262447663, 2543397581, 2806715048, 4131215879, 3337577058, + 1423035225, 1677980476, 896908179, 94864374, 401834583, 656521778, + 1985475229, 1183173368, 3569050563, 3832109990, 3038712585, + 2244815724, 2492348302, 2757496811, 4113188676, 3321397025, + 1472648730, 1729425023, 912434896, 112238261, 315270546, 572038647, + 1936643416, 1136454973, 3514975238, 3780148323, 2955293900, + 2163477673, 2444693579, 2707761198, 4027801729, 3233896676, + 1392505311, 1647167930, 861634837, 59357552, 299743441, 554664116, + 1887029275, 1085010046, 3533003077, 3796328736, 3006343567, + 2212696554, 2459962632, 2725393773, 4077157826, 3285599655, + 1374219420, 1631245561, 810327126, 10396723, + ], + [ + 0, 1409766726, 2819533452, 4228513738, 1441866729, 32929455, + 4261382501, 2851658787, 2883733458, 4293202580, 65858910, 1475065880, + 4262683707, 2853450109, 1444794039, 35298289, 1378416981, 103787539, + 4196815833, 2921399967, 131717820, 1407094778, 2950131760, 4224722294, + 4190813831, 2915953601, 1371804683, 96682317, 2889588078, 4164732968, + 70596578, 1345479332, 2756833962, 4032210924, 207575078, 1482165600, + 4053848387, 2779218949, 1504607183, 229191305, 263435640, 1538580542, + 2814189556, 4089072306, 1514313361, 239453143, 4065082397, 2789960027, + 4135128063, 2726190777, 1584898419, 175174709, 2743609366, 4153376080, + 193364634, 1602344924, 1570458669, 161225067, 4120241825, 2710746087, + 141193156, 1550662274, 2690958664, 4100165646, 1297060773, 424199907, + 3846256937, 2974182511, 415150156, 1287251210, 2964331200, 3837218694, + 3870151799, 2997518641, 1319337723, 446966717, 3009214366, 3881542360, + 458382610, 1330972756, 526871280, 1264598966, 3077161084, 3815675194, + 1251363097, 512826463, 3801670549, 3063920339, 3028626722, 3766646884, + 478906286, 1217188584, 3782479563, 3044236173, 1232774215, 494792961, + 3911082255, 3172545609, 1091619715, 353869509, 3169796838, 3907524512, + 350349418, 1088863532, 1123821277, 385577883, 3941764177, 3203782935, + 386729268, 1124749426, 3204689848, 3942972158, 3140917338, 4013018396, + 322450134, 1195337616, 4006067123, 3133206261, 1187582271, 315507833, + 282386312, 1154714318, 3101324548, 3973914690, 1160117345, 287484199, + 3979040493, 3106669483, 2594121546, 3466097164, 848399814, 1721161856, + 3480093859, 2607370725, 1734389295, 862452585, 830300312, 1702507998, + 2574502420, 3446972242, 1686913905, 814421559, 3431131645, 2558901435, + 3367493151, 2628815705, 1622766739, 884875733, 2638675446, 3376523440, + 893933434, 1632567868, 1666554317, 928173195, 3411751745, 2673632775, + 916765220, 1654910818, 2661945512, 3400353262, 1053742560, 1791590566, + 2529197932, 3267832362, 1799353865, 1060676431, 3274792069, + 2536901059, 2502726194, 3240871796, 1025652926, 1764060664, + 3235754459, 2497373341, 1758665559, 1020546577, 1827024053, 954300915, + 3303573049, 2431636351, 957812572, 1829788186, 2434377168, 3307139222, + 3338955623, 2466463265, 1862975979, 990745773, 2465548430, 3337756104, + 989585922, 1862055748, 3620779247, 2211963305, 2145263203, 735660837, + 2183239430, 3592864320, 707739018, 2116577484, 2083713853, 674604667, + 3560724913, 2151353591, 700698836, 2110031250, 2177727064, 3586797342, + 2247642554, 3523156220, 771155766, 2045882992, 3490278995, 2215525141, + 2013775071, 738234777, 773458536, 2048745262, 2249498852, 3524523426, + 2079009153, 804027591, 3555033869, 2279790155, 1937851973, 663098115, + 3683642569, 2408102287, 644900268, 1920413930, 2390675232, 3665402470, + 3630367127, 2355385553, 1886235419, 610991709, 2375164542, 3650451256, + 631015666, 1906040244, 564772624, 1974397526, 2309428636, 3718267098, + 1951964409, 543148479, 3696637557, 2287035187, 2320234690, 3729567108, + 574968398, 1984038664, 3753564971, 2344455789, 2008314279, 598942945, + ], + [ + 0, 1737424129, 3474848258, 2828207875, 2614592245, 4233723892, + 1422555383, 860128758, 843281179, 1439534618, 4250831129, 2597352472, + 2845110766, 3457813743, 1720257516, 17299181, 1686562358, 50862903, + 2879069236, 3423985973, 4283524291, 2564790722, 810327745, 1472357312, + 1455789357, 827025452, 2581098287, 4267086382, 3440515032, 2862410457, + 34598362, 1702957275, 3373124716, 2927833453, 101725806, 1637796719, + 1390038681, 894743448, 2647110811, 4199106970, 4216242039, 2629845622, + 877872501, 1407039604, 1620655490, 118997123, 2944714624, 3356113537, + 2911578714, 3389380443, 1654050904, 85470553, 912042159, 1372738990, + 4181807789, 2664411052, 2680707393, 4165379136, 1356178243, 928734786, + 69196724, 1670457013, 3405914550, 2894912695, 2549607977, 4034466600, + 1491735595, 1063577898, 203451612, 1806602717, 3275593438, 2763221983, + 2780077362, 3258605619, 1789486896, 220699185, 1046683591, 1508762310, + 4051625413, 2532317380, 4084271135, 2499802398, 1013772829, + 1541541660, 1755745002, 254310379, 2814079208, 3224735209, 3241310980, + 2797372933, 237994246, 1772190727, 1525021169, 1030423792, 2516059123, + 4067884786, 1593451077, 963960644, 2447890503, 4134085958, 3308101808, + 2728615345, 170941106, 1841211315, 1824084318, 188197983, 2745477980, + 3291108957, 4151235499, 2430611114, 947071401, 1610470568, 981255283, + 1576155506, 4116790897, 2465186672, 2712356486, 3324361607, + 1857469572, 154681733, 138393448, 1873889897, 3340914026, 2695671915, + 2481468829, 4100376732, 1559613343, 997929630, 704883363, 1301108642, + 3843969185, 2190519712, 2983471190, 3596277079, 2127155796, 424095573, + 406903224, 2144216249, 3613205434, 2966675131, 2207734605, 3826886220, + 1284153679, 721706062, 1317358741, 688632212, 2174269079, 3860220822, + 3578973792, 3000775521, 441398370, 2109852003, 2093367182, 457753231, + 3017524620, 3562354829, 3876699515, 2157920378, 671893369, 1333967480, + 3809371855, 2223021006, 739482829, 1268605388, 2027545658, 525801787, + 3083083320, 3494568761, 3511490004, 3066292437, 508620758, 2044596951, + 1251645217, 756312608, 2240245027, 3792277538, 2273870073, 3758521848, + 1217755899, 790333434, 475988492, 2077359885, 3544381454, 3033269519, + 3050042338, 3527741155, 2060847584, 492369121, 773615895, 1234340886, + 3774974741, 2257548820, 3186902154, 3665475979, 1927921288, 359089033, + 639878783, 1101871998, 3913170045, 2393948540, 2411149201, 3896101520, + 1084935571, 656683154, 341882212, 1944995941, 3682422630, 3170087527, + 3648168636, 3204210621, 376395966, 1910613439, 1118117961, 623631688, + 2377701963, 3929417546, 3945910695, 2361339046, 606874533, 1134745252, + 1894142802, 392736339, 3220941136, 3631567953, 1962510566, 326596071, + 3152311012, 3697971173, 4012771859, 2292250386, 540274705, 1203571984, + 1186659325, 557057788, 2309423615, 3995729150, 3714939144, 3135472649, + 309363466, 1979612683, 276786896, 2012320721, 3747779794, 3102501331, + 2343103525, 3961917732, 1152718375, 591129382, 574365131, 1169350858, + 3978422217, 2326731464, 3119226686, 3731186239, 1995859260, 293115965, + ], + [ + 0, 4060876286, 3790892301, 335044851, 3322195179, 872980757, + 670089702, 3590114328, 2313498407, 2078876377, 1745961514, 2585612244, + 1340179404, 3186462258, 2920672961, 1545200447, 371599551, 3827967297, + 4157752754, 98453580, 3491923028, 573475242, 836168025, 3285902503, + 2680358808, 1842285158, 2117560981, 2352703339, 1506257779, + 2882250381, 3090400894, 1245694848, 743199102, 3728759936, 3451403379, + 1068773773, 3930652053, 407171179, 196907160, 4189101414, 2779348569, + 1470460839, 1146950484, 3058769578, 1672336050, 2443304780, + 2186919871, 1884664385, 980056513, 3362157631, 3684570316, 697440562, + 4235121962, 241359060, 496678951, 4019631577, 3012515558, 1099127576, + 1383807979, 2692167189, 1972107789, 2273834995, 2491389696, + 1718852350, 1486398204, 2861870850, 3110916081, 1264632335, + 2661027351, 1821377513, 2137547546, 2372169444, 3514666459, 594640933, + 814342358, 3263556904, 393814320, 3849661646, 4136455229, 75579843, + 1321110083, 3165816765, 2940921678, 1564928688, 2293900968, + 2058758998, 1766738853, 2604811867, 3344672100, 894937242, 649054313, + 3567502743, 23005583, 4082304113, 3769328770, 312961404, 1960113026, + 2262367868, 2501942927, 1730974577, 3000000361, 1088180887, + 1394881124, 2703769498, 4247903397, 255709531, 482718120, 4006198358, + 993357902, 3375988144, 3670089027, 684527805, 1660083005, 2432620227, + 2198255152, 1896528846, 2767615958, 1459255848, 1157765851, + 3071153957, 3944215578, 421263844, 182688023, 4176450793, 756242673, + 3743372559, 3437704700, 1055602690, 2972796408, 1128089606, + 1355098357, 2731091211, 2000021779, 2235163885, 2529264670, + 1691191776, 953445087, 3403179809, 3642755026, 724306476, 4275095092, + 215796682, 522496825, 3978864327, 2803330375, 1427857593, 1189281866, + 3035565492, 1628684716, 2468334674, 2162666657, 1928044895, 787628640, + 3707654046, 3473289069, 1024074387, 3908497035, 452649845, 151159686, + 4212035192, 2642220166, 1869683064, 2089384331, 2391110773, + 1534703725, 2843063699, 3129857376, 1216469150, 345521057, 3868472927, + 4117517996, 123755346, 3533477706, 546347700, 862517831, 3244619705, + 2338012217, 2035757511, 1789874484, 2560842954, 1298108626, + 3209927980, 2896952799, 1588064289, 46011166, 4038205152, 3813309971, + 289829869, 3300573173, 917942795, 625922808, 3611483910, 3920226052, + 463858426, 140347913, 4199647223, 799886319, 3718333969, 3461949154, + 1012214556, 1615644707, 2453718493, 2176361774, 1941219536, + 2789762248, 1413769526, 1203505605, 3048211515, 4287614907, 226738757, + 511419062, 3967266632, 965436240, 3414650542, 3632205405, 712180643, + 1986715804, 2221337954, 2543750545, 1704099951, 2960018551, + 1113735561, 1369055610, 2744528004, 3320166010, 938064772, 605150071, + 3592279689, 65084049, 4058847087, 3793057692, 270105186, 1275107677, + 3188495523, 2918511696, 1610152366, 2315531702, 2013804616, + 1810913467, 2583450949, 3552812741, 567251771, 842527688, 3225157174, + 365376046, 3888857040, 4097007395, 104813277, 1512485346, 2821372956, + 3151158511, 1239339281, 2619481353, 1848512759, 2111205380, + 2413460986, + ], +]; diff --git a/tools/vendor/jiff-static/src/shared/error/itime.rs b/tools/vendor/jiff-static/src/shared/error/itime.rs new file mode 100644 index 0000000000..05f56a6ef2 --- /dev/null +++ b/tools/vendor/jiff-static/src/shared/error/itime.rs @@ -0,0 +1,100 @@ +// auto-generated by: jiff-cli generate shared + +use crate::shared::{ + error, + util::itime::{days_in_month, days_in_year, IEpochDay}, +}; + +// N.B. Every variant in this error type is a range error. +#[derive(Clone, Debug, Eq, PartialEq)] +pub(crate) enum Error { + DateInvalidDayOfYear { year: i16 }, + DateInvalidDayOfYearNoLeap, + DateInvalidDays { year: i16, month: i8 }, + DateTimeSeconds, + // TODO: I believe this can never happen. + DayOfYear, + EpochDayDays, + EpochDayI32, + NthWeekdayOfMonth, + Tomorrow, + YearNext, + YearPrevious, + Yesterday, +} + +impl From for error::Error { + #[cold] + #[inline(never)] + fn from(err: Error) -> error::Error { + error::ErrorKind::Time(err).into() + } +} + +// impl error::IntoError for Error { +// fn into_error(self) -> error::Error { +// self.into() +// } +// } + +impl core::fmt::Display for Error { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + use self::Error::*; + + match *self { + DateInvalidDayOfYear { year } => write!( + f, + "number of days for `{year:04}` is invalid, \ + must be in range `1..={max_day}`", + max_day = days_in_year(year), + ), + DateInvalidDayOfYearNoLeap => f.write_str( + "number of days is invalid, must be in range `1..=365`", + ), + DateInvalidDays { year, month } => write!( + f, + "number of days for `{year:04}-{month:02}` is invalid, \ + must be in range `1..={max_day}`", + max_day = days_in_month(year, month), + ), + DateTimeSeconds => { + f.write_str("adding seconds to datetime overflowed") + } + DayOfYear => f.write_str("day of year is invalid"), + EpochDayDays => write!( + f, + "adding to epoch day resulted in a value outside \ + the allowed range of `{min}..={max}`", + min = IEpochDay::MIN.epoch_day, + max = IEpochDay::MAX.epoch_day, + ), + EpochDayI32 => f.write_str( + "adding to epoch day overflowed 32-bit signed integer", + ), + NthWeekdayOfMonth => f.write_str( + "invalid nth weekday of month, \ + must be non-zero and in range `-5..=5`", + ), + Tomorrow => f.write_str( + "returning tomorrow for `9999-12-31` is not \ + possible because it is greater than Jiff's supported + maximum date", + ), + YearNext => f.write_str( + "creating a date for a year following `9999` is \ + not possible because it is greater than Jiff's supported \ + maximum date", + ), + YearPrevious => f.write_str( + "creating a date for a year preceding `-9999` is \ + not possible because it is less than Jiff's supported \ + minimum date", + ), + Yesterday => f.write_str( + "returning yesterday for `-9999-01-01` is not \ + possible because it is less than Jiff's supported + minimum date", + ), + } + } +} diff --git a/tools/vendor/jiff-static/src/shared/error/mod.rs b/tools/vendor/jiff-static/src/shared/error/mod.rs new file mode 100644 index 0000000000..921da75548 --- /dev/null +++ b/tools/vendor/jiff-static/src/shared/error/mod.rs @@ -0,0 +1,57 @@ +// auto-generated by: jiff-cli generate shared + +pub(crate) mod itime; + +/// An error scoped to Jiff's `shared` module. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Error { + kind: ErrorKind, +} + +impl core::fmt::Display for Error { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + self.kind.fmt(f) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +enum ErrorKind { + Time(self::itime::Error), +} + +impl From for Error { + fn from(kind: ErrorKind) -> Error { + Error { kind } + } +} + +impl core::fmt::Display for ErrorKind { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + match *self { + ErrorKind::Time(ref err) => err.fmt(f), + } + } +} + +/* +/// A slim error that occurs when an input value is out of bounds. +#[derive(Clone, Debug)] +struct SlimRangeError { + what: &'static str, +} + +impl SlimRangeError { + fn new(what: &'static str) -> SlimRangeError { + SlimRangeError { what } + } +} + +impl std::error::Error for SlimRangeError {} + +impl core::fmt::Display for SlimRangeError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + let SlimRangeError { what } = *self; + write!(f, "parameter '{what}' is not in the required range") + } +} +*/ diff --git a/tools/vendor/jiff-static/src/shared/mod.rs b/tools/vendor/jiff-static/src/shared/mod.rs new file mode 100644 index 0000000000..c87564cacf --- /dev/null +++ b/tools/vendor/jiff-static/src/shared/mod.rs @@ -0,0 +1,500 @@ +// auto-generated by: jiff-cli generate shared + +/*! +Defines data types shared between `jiff` and `jiff-static`. + +While this module exposes types that can be imported outside of `jiff` itself, +there are *no* semver guarantees provided. That is, this module is _not_ part +of Jiff's public API. The only guarantee of compatibility that is provided +is that `jiff-static x.y.z` works with one and only one version of Jiff, +corresponding to `jiff x.y.z` (i.e., the same version number). + +# Design + +This module is really accomplishing two different things at the same time. + +Firstly, it is a way to provide types that can be used to construct a static +`TimeZone`. The proc macros in `jiff-static` generate code using these +types (and a few routines). + +Secondly, it provides a way to parse TZif data without `jiff-static` +depending on `jiff` via a Cargo dependency. This actually requires copying +the code in this module (which is why it is kinda sectioned off from the rest +of jiff) into the `jiff-static` crate. This can be done automatically with +`jiff-cli`: + +```text +jiff-cli generate shared +``` + +The copying of code is pretty unfortunate, because it means both crates have to +compile it. However, the alternatives aren't great either. + +One alternative is to have `jiff-static` explicitly depend on `jiff` in its +`Cargo.toml`. Then Jiff could expose the parsing routines, as it does here, +and `jiff-static` could use them directly. Unfortunately, this means that +`jiff` cannot depend on `jiff-static`. And that in turn means that `jiff` +cannot re-export the macros. Users will need to explicitly depend on and use +`jiff-static`. Moreover, this could result in some potential surprises +since `jiff-static` will need to have an `=x.y.z` dependency on Jiff for +compatibility reasons. That in turn means that the version of Jiff actually +used is not determine by the user's `jiff = "x.y.z"` line, but rather by the +user's `jiff-static = "x'.y'.z'"` line. This is overall annoying and not a +good user experience. Plus, it inverts the typical relationship between crates +and their proc macros (e.g., `serde` and `serde_derive`) and thus could result +in other unanticipated surprises. + +Another obvious alternative is to split this code out into a separate crate +that both `jiff` and `jiff-static` depend on. However, the API exposed in +this module does not provide a coherent user experience. It would either need a +ton of work to turn it into a coherent user experience or it would need to be +published as a `jiff-internal-use-only` crate that I find to be very annoying +and confusing. Moreover, a separate crate introduces a new semver boundary +beneath Jiff. I've found these sorts of things to overall increase maintenance +burden (see ripgrep and regex for cases where I did this). + +I overall decided that the least bad choice was to copy a little code (under +2,000 source lines of code at present I believe). Since the copy is managed +automatically via `jiff-cli generate shared`, we remove the downside of the +code getting out of sync. The only downside is extra compile time. Since I +generally only expect `jiff-static` to be used in niche circumstances, I +prefer this trade-off over the other choices. + +More context on how I arrived at this design can be found here: + + +# Particulars + +When this code is copied to `jiff-static`, the following transformations are +done: + +* A header is added to indicate that the copied file is auto-generated. +* All `#[cfg(feature = "alloc")]` annotations are removed. The `jiff-static` + proc macro always runs in a context where the standard library is available. +* Any code between `// only-jiff-start` and `// only-jiff-end` comments is + removed. Nesting isn't supported. + +Otherwise, this module is specifically organized in a way that doesn't rely on +any other part of Jiff. The one exception are routines to convert from these +exposed types to other internal types inside of Jiff. This is necessary for +building a static `TimeZone`. But these conversion routines are removed when +this module is copied to `jiff-static`. +*/ + +/// An alias for TZif data whose backing storage has a `'static` lifetime. + +/// An alias for TZif data whose backing storage is on the heap. +pub type TzifOwned = Tzif< + alloc::string::String, + self::util::array_str::Abbreviation, + alloc::vec::Vec, + alloc::vec::Vec, + alloc::vec::Vec, + alloc::vec::Vec, + alloc::vec::Vec, +>; + +/// An alias for TZif transition data whose backing storage is on the heap. +pub type TzifTransitionsOwned = TzifTransitions< + alloc::vec::Vec, + alloc::vec::Vec, + alloc::vec::Vec, + alloc::vec::Vec, +>; + +#[derive(Clone, Debug)] +pub struct Tzif { + pub fixed: TzifFixed, + pub types: TYPES, + pub transitions: TzifTransitions, +} + +#[derive(Clone, Debug)] +pub struct TzifFixed { + pub name: Option, + /// An ASCII byte corresponding to the version number. So, 0x50 is '2'. + /// + /// This is unused. It's only used in `test` compilation for emitting + /// diagnostic data about TZif files. If we really need to use this, we + /// should probably just convert it to an actual integer. + pub version: u8, + pub checksum: u32, + pub designations: STR, + pub posix_tz: Option>, +} + +#[derive(Clone, Copy, Debug)] +pub struct TzifLocalTimeType { + pub offset: i32, + pub is_dst: bool, + pub designation: (u8, u8), // inclusive..exclusive + pub indicator: TzifIndicator, +} + +/// This enum corresponds to the possible indicator values for standard/wall +/// and UT/local. +/// +/// Note that UT+Wall is not allowed. +/// +/// I honestly have no earthly clue what they mean. I've read the section about +/// them in RFC 8536 several times and I can't make sense of it. I've even +/// looked at data files that have these set and still can't make sense of +/// them. I've even looked at what other datetime libraries do with these, and +/// they all seem to just ignore them. Like, WTF. I've spent the last couple +/// months of my life steeped in time, and I just cannot figure this out. Am I +/// just dumb? +/// +/// Anyway, we parse them, but otherwise ignore them because that's what all +/// the cool kids do. +/// +/// The default is `LocalWall`, which also occurs when no indicators are +/// present. +/// +/// I tried again and still don't get it. Here's a dump for `Pacific/Honolulu`: +/// +/// ```text +/// $ ./scripts/jiff-debug tzif /usr/share/zoneinfo/Pacific/Honolulu +/// TIME ZONE NAME +/// /usr/share/zoneinfo/Pacific/Honolulu +/// LOCAL TIME TYPES +/// 000: offset=-10:31:26, is_dst=false, designation=LMT, indicator=local/wall +/// 001: offset=-10:30, is_dst=false, designation=HST, indicator=local/wall +/// 002: offset=-09:30, is_dst=true, designation=HDT, indicator=local/wall +/// 003: offset=-09:30, is_dst=true, designation=HWT, indicator=local/wall +/// 004: offset=-09:30, is_dst=true, designation=HPT, indicator=ut/std +/// 005: offset=-10, is_dst=false, designation=HST, indicator=local/wall +/// TRANSITIONS +/// 0000: -9999-01-02T01:59:59 :: -377705023201 :: type=0, -10:31:26, is_dst=false, LMT, local/wall +/// 0001: 1896-01-13T22:31:26 :: -2334101314 :: type=1, -10:30, is_dst=false, HST, local/wall +/// 0002: 1933-04-30T12:30:00 :: -1157283000 :: type=2, -09:30, is_dst=true, HDT, local/wall +/// 0003: 1933-05-21T21:30:00 :: -1155436200 :: type=1, -10:30, is_dst=false, HST, local/wall +/// 0004: 1942-02-09T12:30:00 :: -880198200 :: type=3, -09:30, is_dst=true, HWT, local/wall +/// 0005: 1945-08-14T23:00:00 :: -769395600 :: type=4, -09:30, is_dst=true, HPT, ut/std +/// 0006: 1945-09-30T11:30:00 :: -765376200 :: type=1, -10:30, is_dst=false, HST, local/wall +/// 0007: 1947-06-08T12:30:00 :: -712150200 :: type=5, -10, is_dst=false, HST, local/wall +/// POSIX TIME ZONE STRING +/// HST10 +/// ``` +/// +/// See how type 004 has a ut/std indicator? What the fuck does that mean? +/// All transitions are defined in terms of UTC. I confirmed this with `zdump`: +/// +/// ```text +/// $ zdump -v Pacific/Honolulu | rg 1945 +/// Pacific/Honolulu Tue Aug 14 22:59:59 1945 UT = Tue Aug 14 13:29:59 1945 HWT isdst=1 gmtoff=-34200 +/// Pacific/Honolulu Tue Aug 14 23:00:00 1945 UT = Tue Aug 14 13:30:00 1945 HPT isdst=1 gmtoff=-34200 +/// Pacific/Honolulu Sun Sep 30 11:29:59 1945 UT = Sun Sep 30 01:59:59 1945 HPT isdst=1 gmtoff=-34200 +/// Pacific/Honolulu Sun Sep 30 11:30:00 1945 UT = Sun Sep 30 01:00:00 1945 HST isdst=0 gmtoff=-37800 +/// ``` +/// +/// The times match up. All of them. The indicators don't seem to make a +/// difference. I'm clearly missing something. +#[derive(Clone, Copy, Debug)] +pub enum TzifIndicator { + LocalWall, + LocalStandard, + UTStandard, +} + +/// The set of transitions in TZif data, laid out in column orientation. +/// +/// The column orientation is used to make TZ lookups faster. Specifically, +/// for finding an offset for a timestamp, we do a binary search on +/// `timestamps`. For finding an offset for a local datetime, we do a binary +/// search on `civil_starts`. By making these two distinct sequences with +/// nothing else in them, we make them as small as possible and thus improve +/// cache locality. +/// +/// All sequences in this type are in correspondence with one another. They +/// are all guaranteed to have the same length. +#[derive(Clone, Debug)] +pub struct TzifTransitions { + /// The timestamp at which this transition begins. + pub timestamps: TIMESTAMPS, + /// The wall clock time for when a transition begins. + pub civil_starts: STARTS, + /// The wall clock time for when a transition ends. + /// + /// This is only non-zero when the transition kind is a gap or a fold. + pub civil_ends: ENDS, + /// Any other relevant data about a transition, such as its local type + /// index and the transition kind. + pub infos: INFOS, +} + +/// TZif transition info beyond the timestamp and civil datetime. +/// +/// For example, this contains a transition's "local type index," which in +/// turn gives access to the offset (among other metadata) for that transition. +#[derive(Clone, Copy, Debug)] +pub struct TzifTransitionInfo { + /// The index into the sequence of local time type records. This is what + /// provides the correct offset (from UTC) that is active beginning at + /// this transition. + pub type_index: u8, + /// The boundary condition for quickly determining if a given wall clock + /// time is ambiguous (i.e., falls in a gap or a fold). + pub kind: TzifTransitionKind, +} + +/// The kind of a transition. +/// +/// This is used when trying to determine the offset for a local datetime. It +/// indicates how the corresponding civil datetimes in `civil_starts` and +/// `civil_ends` should be interpreted. That is, there are three possible +/// cases: +/// +/// 1. The offset of this transition is equivalent to the offset of the +/// previous transition. That means there are no ambiguous civil datetimes +/// between the transitions. This can occur, e.g., when the time zone +/// abbreviation changes. +/// 2. The offset of the transition is greater than the offset of the previous +/// transition. That means there is a "gap" in local time between the +/// transitions. This typically corresponds to entering daylight saving time. +/// It is usually, but not always, 1 hour. +/// 3. The offset of the transition is less than the offset of the previous +/// transition. That means there is a "fold" in local time where time is +/// repeated. This typically corresponds to leaving daylight saving time. It +/// is usually, but not always, 1 hour. +/// +/// # More explanation +/// +/// This, when combined with `civil_starts` and `civil_ends` in +/// `TzifTransitions`, explicitly represents ambiguous wall clock times that +/// occur at the boundaries of transitions. +/// +/// The start of the wall clock time is always the earlier possible wall clock +/// time that could occur with this transition's corresponding offset. For a +/// gap, it's the previous transition's offset. For a fold, it's the current +/// transition's offset. +/// +/// For example, DST for `America/New_York` began on `2024-03-10T07:00:00+00`. +/// The offset prior to this instant in time is `-05`, corresponding +/// to standard time (EST). Thus, in wall clock time, DST began at +/// `2024-03-10T02:00:00`. And since this is a DST transition that jumps ahead +/// an hour, the start of DST also corresponds to the start of a gap. That is, +/// the times `02:00:00` through `02:59:59` never appear on a clock for this +/// hour. The question is thus: which offset should we apply to `02:00:00`? +/// We could apply the offset from the earlier transition `-05` and get +/// `2024-03-10T01:00:00-05` (that's `2024-03-10T06:00:00+00`), or we could +/// apply the offset from the later transition `-04` and get +/// `2024-03-10T03:00:00-04` (that's `2024-03-10T07:00:00+00`). +/// +/// So in the above, we would have a `Gap` variant where `start` (inclusive) is +/// `2024-03-10T02:00:00` and `end` (exclusive) is `2024-03-10T03:00:00`. +/// +/// The fold case is the same idea, but where the same time is repeated. +/// For example, in `America/New_York`, standard time began on +/// `2024-11-03T06:00:00+00`. The offset prior to this instant in time +/// is `-04`, corresponding to DST (EDT). Thus, in wall clock time, DST +/// ended at `2024-11-03T02:00:00`. However, since this is a fold, the +/// actual set of ambiguous times begins at `2024-11-03T01:00:00` and +/// ends at `2024-11-03T01:59:59.999999999`. That is, the wall clock time +/// `2024-11-03T02:00:00` is unambiguous. +/// +/// So in the fold case above, we would have a `Fold` variant where +/// `start` (inclusive) is `2024-11-03T01:00:00` and `end` (exclusive) is +/// `2024-11-03T02:00:00`. +/// +/// Since this gets bundled in with the sorted sequence of transitions, we'll +/// use the "start" time in all three cases as our target of binary search. +/// Once we land on a transition, we'll know our given wall clock time is +/// greater than or equal to its start wall clock time. At that point, to +/// determine if there is ambiguity, we merely need to determine if the given +/// wall clock time is less than the corresponding `end` time. If it is, then +/// it falls in a gap or fold. Otherwise, it's unambiguous. +/// +/// Note that we could compute these datetime values while searching for the +/// correct transition, but there's a fair bit of math involved in going +/// between timestamps (which is what TZif gives us) and calendar datetimes +/// (which is what we're given as input). It is also necessary that we offset +/// the timestamp given in TZif at some point, since it is in UTC and the +/// datetime given is in wall clock time. So I decided it would be worth +/// pre-computing what we need in terms of what the input is. This way, we +/// don't need to do any conversions, or indeed, any arithmetic at all, for +/// time zone lookups. We *could* store these as transitions, but then the +/// input datetime would need to be converted to a timestamp before searching +/// the transitions. +#[derive(Clone, Copy, Debug)] +pub enum TzifTransitionKind { + /// This transition cannot possibly lead to an unambiguous offset because + /// its offset is equivalent to the offset of the previous transition. + /// + /// Has an entry in `civil_starts`, but corresponding entry in `civil_ends` + /// is always zeroes (i.e., meaningless). + Unambiguous, + /// This occurs when this transition's offset is strictly greater than the + /// previous transition's offset. This effectively results in a "gap" of + /// time equal to the difference in the offsets between the two + /// transitions. + /// + /// Has an entry in `civil_starts` for when the gap starts (inclusive) in + /// local time. Also has an entry in `civil_ends` for when the fold ends + /// (exclusive) in local time. + Gap, + /// This occurs when this transition's offset is strictly less than the + /// previous transition's offset. This results in a "fold" of time where + /// the two transitions have an overlap where it is ambiguous which one + /// applies given a wall clock time. In effect, a span of time equal to the + /// difference in the offsets is repeated. + /// + /// Has an entry in `civil_starts` for when the fold starts (inclusive) in + /// local time. Also has an entry in `civil_ends` for when the fold ends + /// (exclusive) in local time. + Fold, +} + +/// The representation we use to represent a civil datetime. +/// +/// We don't use `shared::util::itime::IDateTime` here because we specifically +/// do not need to represent fractional seconds. This lets us easily represent +/// what we need in 8 bytes instead of the 12 bytes used by `IDateTime`. +/// +/// Moreover, we pack the fields into a single `i64` to make comparisons +/// extremely cheap. This is especially useful since we do a binary search on +/// `&[TzifDateTime]` when doing a TZ lookup for a civil datetime. +#[derive(Clone, Copy, Eq, Hash, PartialEq, PartialOrd, Ord)] +pub struct TzifDateTime { + bits: i64, +} + +impl TzifDateTime { + pub const ZERO: TzifDateTime = TzifDateTime::new(0, 0, 0, 0, 0, 0); + const MIN: TzifDateTime = TzifDateTime::new(-9999, 1, 1, 0, 0, 0); + + pub const fn new( + year: i16, + month: i8, + day: i8, + hour: i8, + minute: i8, + second: i8, + ) -> TzifDateTime { + let mut bits = (year as u64) << 48; + bits |= (month as u64) << 40; + bits |= (day as u64) << 32; + bits |= (hour as u64) << 24; + bits |= (minute as u64) << 16; + bits |= (second as u64) << 8; + // The least significant 8 bits remain 0. + TzifDateTime { bits: bits as i64 } + } + + pub const fn year(self) -> i16 { + (self.bits as u64 >> 48) as u16 as i16 + } + + pub const fn month(self) -> i8 { + (self.bits as u64 >> 40) as u8 as i8 + } + + pub const fn day(self) -> i8 { + (self.bits as u64 >> 32) as u8 as i8 + } + + pub const fn hour(self) -> i8 { + (self.bits as u64 >> 24) as u8 as i8 + } + + pub const fn minute(self) -> i8 { + (self.bits as u64 >> 16) as u8 as i8 + } + + pub const fn second(self) -> i8 { + (self.bits as u64 >> 8) as u8 as i8 + } +} + +impl core::fmt::Debug for TzifDateTime { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + if !f.alternate() { + f.debug_struct("TzifDateTime").field("bits", &self.bits).finish() + } else { + f.debug_tuple("TzifDateTime") + .field(&format_args!( + "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}", + self.year(), + self.month(), + self.day(), + self.hour(), + self.minute(), + self.second(), + )) + .finish() + } + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct PosixTimeZone { + pub std_abbrev: ABBREV, + pub std_offset: PosixOffset, + pub dst: Option>, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct PosixDst { + pub abbrev: ABBREV, + pub offset: PosixOffset, + pub rule: PosixRule, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct PosixRule { + pub start: PosixDayTime, + pub end: PosixDayTime, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct PosixDayTime { + pub date: PosixDay, + pub time: PosixTime, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum PosixDay { + /// Julian day in a year, no counting for leap days. + /// + /// Valid range is `1..=365`. + JulianOne(i16), + /// Julian day in a year, counting for leap days. + /// + /// Valid range is `0..=365`. + JulianZero(i16), + /// The nth weekday of a month. + WeekdayOfMonth { + /// The month. + /// + /// Valid range is: `1..=12`. + month: i8, + /// The week. + /// + /// Valid range is `1..=5`. + /// + /// One interesting thing to note here (or my interpretation anyway), + /// is that a week of `4` means the "4th weekday in a month" where as + /// a week of `5` means the "last weekday in a month, even if it's the + /// 4th weekday." + week: i8, + /// The weekday. + /// + /// Valid range is `0..=6`, with `0` corresponding to Sunday. + weekday: i8, + }, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct PosixTime { + pub second: i32, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct PosixOffset { + pub second: i32, +} + +// Does not require `alloc`, but is only used when `alloc` is enabled. +pub(crate) mod crc32; +pub(crate) mod posix; +pub(crate) mod tzif; +pub(crate) mod util; diff --git a/tools/vendor/jiff-static/src/shared/posix.rs b/tools/vendor/jiff-static/src/shared/posix.rs new file mode 100644 index 0000000000..62971715c2 --- /dev/null +++ b/tools/vendor/jiff-static/src/shared/posix.rs @@ -0,0 +1,3421 @@ +// auto-generated by: jiff-cli generate shared + +use core::fmt::Debug; + +use super::{ + util::{ + array_str::Abbreviation, + itime::{ + IAmbiguousOffset, IDate, IDateTime, IOffset, ITime, ITimeSecond, + ITimestamp, IWeekday, + }, + }, + PosixDay, PosixDayTime, PosixDst, PosixOffset, PosixRule, PosixTime, + PosixTimeZone, +}; + +impl PosixTimeZone { + /// Parse a POSIX `TZ` environment variable, assuming it's a rule and not + /// an implementation defined value, from the given bytes. + pub fn parse( + bytes: &[u8], + ) -> Result, PosixTimeZoneError> { + // We enable the IANA v3+ extensions here. (Namely, that the time + // specification hour value has the range `-167..=167` instead of + // `0..=24`.) Requiring strict POSIX rules doesn't seem necessary + // since the extension is a strict superset. Plus, GNU tooling + // seems to accept the extension. + let parser = Parser { ianav3plus: true, ..Parser::new(bytes) }; + parser.parse() + } +} + +impl + Debug> PosixTimeZone { + /// Returns the appropriate time zone offset to use for the given + /// timestamp. + /// + /// If you need information like whether the offset is in DST or not, or + /// the time zone abbreviation, then use `PosixTimeZone::to_offset_info`. + /// But that API may be more expensive to use, so only use it if you need + /// the additional data. + pub(crate) fn to_offset(&self, timestamp: ITimestamp) -> IOffset { + let std_offset = self.std_offset.to_ioffset(); + if self.dst.is_none() { + return std_offset; + } + + let dt = timestamp.to_datetime(IOffset::UTC); + self.dst_info_utc(dt.date.year) + .filter(|dst_info| dst_info.in_dst(dt)) + .map(|dst_info| dst_info.offset().to_ioffset()) + .unwrap_or_else(|| std_offset) + } + + /// Returns the appropriate time zone offset to use for the given + /// timestamp. + /// + /// This also includes whether the offset returned should be considered + /// to be "DST" or not, along with the time zone abbreviation (e.g., EST + /// for standard time in New York, and EDT for DST in New York). + pub(crate) fn to_offset_info( + &self, + timestamp: ITimestamp, + ) -> (IOffset, &'_ str, bool) { + let std_offset = self.std_offset.to_ioffset(); + if self.dst.is_none() { + return (std_offset, self.std_abbrev.as_ref(), false); + } + + let dt = timestamp.to_datetime(IOffset::UTC); + self.dst_info_utc(dt.date.year) + .filter(|dst_info| dst_info.in_dst(dt)) + .map(|dst_info| { + ( + dst_info.offset().to_ioffset(), + dst_info.dst.abbrev.as_ref(), + true, + ) + }) + .unwrap_or_else(|| (std_offset, self.std_abbrev.as_ref(), false)) + } + + /// Returns a possibly ambiguous timestamp for the given civil datetime. + /// + /// The given datetime should correspond to the "wall" clock time of what + /// humans use to tell time for this time zone. + /// + /// Note that "ambiguous timestamp" is represented by the possible + /// selection of offsets that could be applied to the given datetime. In + /// general, it is only ambiguous around transitions to-and-from DST. The + /// ambiguity can arise as a "fold" (when a particular wall clock time is + /// repeated) or as a "gap" (when a particular wall clock time is skipped + /// entirely). + pub(crate) fn to_ambiguous_kind(&self, dt: IDateTime) -> IAmbiguousOffset { + let year = dt.date.year; + let std_offset = self.std_offset.to_ioffset(); + let Some(dst_info) = self.dst_info_wall(year) else { + return IAmbiguousOffset::Unambiguous { offset: std_offset }; + }; + let dst_offset = dst_info.offset().to_ioffset(); + let diff = dst_offset.second - std_offset.second; + // When the difference between DST and standard is positive, that means + // STD->DST results in a gap while DST->STD results in a fold. However, + // when the difference is negative, that means STD->DST results in a + // fold while DST->STD results in a gap. The former is by far the most + // common. The latter is a bit weird, but real cases do exist. For + // example, Dublin has DST in winter (UTC+01) and STD in the summer + // (UTC+00). + // + // When the difference is zero, then we have a weird POSIX time zone + // where a DST transition rule was specified, but was set to explicitly + // be the same as STD. In this case, there can be no ambiguity. (The + // zero case is strictly redundant. Both the diff < 0 and diff > 0 + // cases handle the zero case correctly. But we write it out for + // clarity.) + if diff == 0 { + debug_assert_eq!(std_offset, dst_offset); + IAmbiguousOffset::Unambiguous { offset: std_offset } + } else if diff.is_negative() { + // For DST transitions that always move behind one hour, ambiguous + // timestamps only occur when the given civil datetime falls in the + // standard time range. + if dst_info.in_dst(dt) { + IAmbiguousOffset::Unambiguous { offset: dst_offset } + } else { + let fold_start = dst_info.start.saturating_add_seconds(diff); + let gap_end = + dst_info.end.saturating_add_seconds(diff.saturating_neg()); + if fold_start <= dt && dt < dst_info.start { + IAmbiguousOffset::Fold { + before: std_offset, + after: dst_offset, + } + } else if dst_info.end <= dt && dt < gap_end { + IAmbiguousOffset::Gap { + before: dst_offset, + after: std_offset, + } + } else { + IAmbiguousOffset::Unambiguous { offset: std_offset } + } + } + } else { + // For DST transitions that always move ahead one hour, ambiguous + // timestamps only occur when the given civil datetime falls in the + // DST range. + if !dst_info.in_dst(dt) { + IAmbiguousOffset::Unambiguous { offset: std_offset } + } else { + // PERF: I wonder if it makes sense to pre-compute these? + // Probably not, because we have to do it based on year of + // datetime given. But if we ever add a "caching" layer for + // POSIX time zones, then it might be worth adding these to it. + let gap_end = dst_info.start.saturating_add_seconds(diff); + let fold_start = + dst_info.end.saturating_add_seconds(diff.saturating_neg()); + if dst_info.start <= dt && dt < gap_end { + IAmbiguousOffset::Gap { + before: std_offset, + after: dst_offset, + } + } else if fold_start <= dt && dt < dst_info.end { + IAmbiguousOffset::Fold { + before: dst_offset, + after: std_offset, + } + } else { + IAmbiguousOffset::Unambiguous { offset: dst_offset } + } + } + } + } + + /// Returns the timestamp of the most recent time zone transition prior + /// to the timestamp given. If one doesn't exist, `None` is returned. + pub(crate) fn previous_transition( + &self, + timestamp: ITimestamp, + ) -> Option<(ITimestamp, IOffset, &'_ str, bool)> { + let dt = timestamp.to_datetime(IOffset::UTC); + let dst_info = self.dst_info_utc(dt.date.year)?; + let (earlier, later) = dst_info.ordered(); + let (prev, dst_info) = if dt > later { + (later, dst_info) + } else if dt > earlier { + (earlier, dst_info) + } else { + let prev_year = dt.date.prev_year().ok()?; + let dst_info = self.dst_info_utc(prev_year)?; + let (_, later) = dst_info.ordered(); + (later, dst_info) + }; + + let timestamp = prev.to_timestamp_checked(IOffset::UTC)?; + let dt = timestamp.to_datetime(IOffset::UTC); + let (offset, abbrev, dst) = if dst_info.in_dst(dt) { + (dst_info.offset(), dst_info.dst.abbrev.as_ref(), true) + } else { + (&self.std_offset, self.std_abbrev.as_ref(), false) + }; + Some((timestamp, offset.to_ioffset(), abbrev, dst)) + } + + /// Returns the timestamp of the soonest time zone transition after the + /// timestamp given. If one doesn't exist, `None` is returned. + pub(crate) fn next_transition( + &self, + timestamp: ITimestamp, + ) -> Option<(ITimestamp, IOffset, &'_ str, bool)> { + let dt = timestamp.to_datetime(IOffset::UTC); + let dst_info = self.dst_info_utc(dt.date.year)?; + let (earlier, later) = dst_info.ordered(); + let (next, dst_info) = if dt < earlier { + (earlier, dst_info) + } else if dt < later { + (later, dst_info) + } else { + let next_year = dt.date.next_year().ok()?; + let dst_info = self.dst_info_utc(next_year)?; + let (earlier, _) = dst_info.ordered(); + (earlier, dst_info) + }; + + let timestamp = next.to_timestamp_checked(IOffset::UTC)?; + let dt = timestamp.to_datetime(IOffset::UTC); + let (offset, abbrev, dst) = if dst_info.in_dst(dt) { + (dst_info.offset(), dst_info.dst.abbrev.as_ref(), true) + } else { + (&self.std_offset, self.std_abbrev.as_ref(), false) + }; + Some((timestamp, offset.to_ioffset(), abbrev, dst)) + } + + /// Returns the range in which DST occurs. + /// + /// The civil datetimes returned are in UTC. This is useful for determining + /// whether a timestamp is in DST or not. + fn dst_info_utc(&self, year: i16) -> Option> { + let dst = self.dst.as_ref()?; + // DST time starts with respect to standard time, so offset it by the + // standard offset. + let start = + dst.rule.start.to_datetime(year, self.std_offset.to_ioffset()); + // DST time ends with respect to DST time, so offset it by the DST + // offset. + let mut end = dst.rule.end.to_datetime(year, dst.offset.to_ioffset()); + // This is a whacky special case when DST is permanent, but the math + // using to calculate the start/end datetimes ends up leaving a gap + // for standard time to appear. In which case, it's possible for a + // timestamp at the end of a calendar year to get standard time when + // it really should be DST. + // + // We detect this case by re-interpreting the end of the boundary using + // the standard offset. If we get a datetime that is in a different + // year, then it follows that standard time is actually impossible to + // occur. + // + // These weird POSIX time zones can occur as the TZ strings in + // a TZif file compiled using rearguard semantics. For example, + // `Africa/Casablanca` has: + // + // XXX-2<+01>-1,0/0,J365/23 + // + // Notice here that DST is actually one hour *behind* (it is usually + // one hour *ahead*) _and_ it ends at 23:00:00 on the last day of the + // year. But if it ends at 23:00, then jumping to standard time moves + // the clocks *forward*. Which would bring us to 00:00:00 on the first + // of the next year... but that is when DST begins! Hence, DST is + // permanent. + // + // Ideally, this could just be handled by our math automatically. But + // I couldn't figure out how to make it work. In particular, in the + // above example for year 2087, we get + // + // start == 2087-01-01T00:00:00Z + // end == 2087-12-31T22:00:00Z + // + // Which leaves a two hour gap for a timestamp to get erroneously + // categorized as standard time. + // + // ... so we special case this. We could pre-compute whether a POSIX + // time zone is in permanent DST at construction time, but it's not + // obvious to me that it's worth it. Especially since this is an + // exceptionally rare case. + // + // Note that I did try to consult tzcode's (incredibly inscrutable) + // `localtime` implementation to figure out how they deal with it. At + // first, it looks like they don't have any special handling for this + // case. But looking more closely, they skip any time zone transitions + // generated by POSIX time zones whose rule spans more than 1 year: + // + // https://github.com/eggert/tz/blob/8d65db9786753f3b263087e31c59d191561d63e3/localtime.c#L1717-L1735 + // + // By just ignoring them, I think it achieves the desired effect of + // permanent DST. But I'm not 100% confident in my understanding of + // the code. + if start.date.month == 1 + && start.date.day == 1 + && start.time == ITime::MIN + // NOTE: This should come last because it is potentially expensive. + && year + != end.saturating_add_seconds(self.std_offset.second).date.year + { + end = IDateTime { + date: IDate { year, month: 12, day: 31 }, + time: ITime::MAX, + }; + } + Some(DstInfo { dst, start, end }) + } + + /// Returns the range in which DST occurs. + /// + /// The civil datetimes returned are in "wall clock time." That is, they + /// represent the transitions as they are seen from humans reading a clock + /// within the geographic location of that time zone. + fn dst_info_wall(&self, year: i16) -> Option> { + let dst = self.dst.as_ref()?; + // POSIX time zones express their DST transitions in terms of wall + // clock time. Since this method specifically is returning wall + // clock times, we don't want to offset our datetimes at all. + let start = dst.rule.start.to_datetime(year, IOffset::UTC); + let end = dst.rule.end.to_datetime(year, IOffset::UTC); + Some(DstInfo { dst, start, end }) + } + + /// Returns the DST transition rule. This panics if this time zone doesn't + /// have DST. + #[cfg(test)] + fn rule(&self) -> &PosixRule { + &self.dst.as_ref().unwrap().rule + } +} + +impl> core::fmt::Display for PosixTimeZone { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + core::fmt::Display::fmt( + &AbbreviationDisplay(self.std_abbrev.as_ref()), + f, + )?; + core::fmt::Display::fmt(&self.std_offset, f)?; + if let Some(ref dst) = self.dst { + dst.display(&self.std_offset, f)?; + } + Ok(()) + } +} + +impl> PosixDst { + fn display( + &self, + std_offset: &PosixOffset, + f: &mut core::fmt::Formatter, + ) -> core::fmt::Result { + core::fmt::Display::fmt( + &AbbreviationDisplay(self.abbrev.as_ref()), + f, + )?; + // The overwhelming common case is that DST is exactly one hour ahead + // of standard time. So common that this is the default. So don't write + // the offset if we don't need to. + let default = PosixOffset { second: std_offset.second + 3600 }; + if self.offset != default { + core::fmt::Display::fmt(&self.offset, f)?; + } + f.write_str(",")?; + core::fmt::Display::fmt(&self.rule, f)?; + Ok(()) + } +} + +impl core::fmt::Display for PosixRule { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + core::fmt::Display::fmt(&self.start, f)?; + f.write_str(",")?; + core::fmt::Display::fmt(&self.end, f)?; + Ok(()) + } +} + +impl PosixDayTime { + /// Turns this POSIX datetime spec into a civil datetime in the year given + /// with the given offset. The datetimes returned are offset by the given + /// offset. For wall clock time, an offset of `0` should be given. For + /// UTC time, the offset (standard or DST) corresponding to this time + /// spec should be given. + /// + /// The datetime returned is guaranteed to have a year component equal + /// to the year given. This guarantee is upheld even when the datetime + /// specification (combined with the offset) would extend past the end of + /// the year (or before the start of the year). In this case, the maximal + /// (or minimal) datetime for the given year is returned. + pub(crate) fn to_datetime(&self, year: i16, offset: IOffset) -> IDateTime { + let mkmin = || IDateTime { + date: IDate { year, month: 1, day: 1 }, + time: ITime::MIN, + }; + let mkmax = || IDateTime { + date: IDate { year, month: 12, day: 31 }, + time: ITime::MAX, + }; + let Some(date) = self.date.to_date(year) else { return mkmax() }; + // The range on `self.time` is `-604799..=604799`, and the range + // on `offset.second` is `-93599..=93599`. Therefore, subtracting + // them can never overflow an `i32`. + let offset = self.time.second - offset.second; + // If the time goes negative or above 86400, then we might have + // to adjust our date. + let days = offset.div_euclid(86400); + let second = offset.rem_euclid(86400); + + let Ok(date) = date.checked_add_days(days) else { + return if offset < 0 { mkmin() } else { mkmax() }; + }; + if date.year < year { + mkmin() + } else if date.year > year { + mkmax() + } else { + let time = ITimeSecond { second }.to_time(); + IDateTime { date, time } + } + } +} + +impl core::fmt::Display for PosixDayTime { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + core::fmt::Display::fmt(&self.date, f)?; + // This is the default time, so don't write it if we + // don't need to. + if self.time != PosixTime::DEFAULT { + f.write_str("/")?; + core::fmt::Display::fmt(&self.time, f)?; + } + Ok(()) + } +} + +impl PosixDay { + /// Convert this date specification to a civil date in the year given. + /// + /// If this date specification couldn't be turned into a date in the year + /// given, then `None` is returned. This happens when `366` is given as + /// a day, but the year given is not a leap year. In this case, callers may + /// want to assume a datetime that is maximal for the year given. + fn to_date(&self, year: i16) -> Option { + match *self { + PosixDay::JulianOne(day) => { + // Parsing validates that our day is 1-365 which will always + // succeed for all possible year values. That is, every valid + // year has a December 31. + Some( + IDate::from_day_of_year_no_leap(year, day) + .expect("Julian `J day` should be in bounds"), + ) + } + PosixDay::JulianZero(day) => { + // OK because our value for `day` is validated to be `0..=365`, + // and since it is an `i16`, it is always valid to add 1. + // + // Also, while `day+1` is guaranteed to be in `1..=366`, it is + // possible that `366` is invalid, for when `year` is not a + // leap year. In this case, we throw our hands up, and ask the + // caller to make a decision for how to deal with it. Why does + // POSIX go out of its way to specifically not specify behavior + // in error cases? + IDate::from_day_of_year(year, day + 1).ok() + } + PosixDay::WeekdayOfMonth { month, week, weekday } => { + let weekday = IWeekday::from_sunday_zero_offset(weekday); + let first = IDate { year, month, day: 1 }; + let week = if week == 5 { -1 } else { week }; + debug_assert!(week == -1 || (1..=4).contains(&week)); + // This is maybe non-obvious, but this will always succeed + // because it can only fail when the week number is one of + // {-5, 0, 5}. Since we've validated that 'week' is in 1..=5, + // we know it can't be 0. Moreover, because of the conditional + // above and since `5` actually means "last weekday of month," + // that case will always translate to `-1`. + // + // Also, I looked at how other libraries deal with this case, + // and almost all of them just do a bunch of inline hairy + // arithmetic here. I suppose I could be reduced to such + // things if perf called for it, but we have a nice civil date + // abstraction. So use it, god damn it. (Well, we did, and now + // we have a lower level IDate abstraction. But it's still + // an abstraction!) + Some( + first + .nth_weekday_of_month(week, weekday) + .expect("nth weekday always exists"), + ) + } + } + } +} + +impl core::fmt::Display for PosixDay { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + match *self { + PosixDay::JulianOne(n) => { + f.write_str("J")?; + core::fmt::Display::fmt(&n, f) + } + PosixDay::JulianZero(n) => core::fmt::Display::fmt(&n, f), + PosixDay::WeekdayOfMonth { month, week, weekday } => { + f.write_str("M")?; + core::fmt::Display::fmt(&month, f)?; + f.write_str(".")?; + core::fmt::Display::fmt(&week, f)?; + f.write_str(".")?; + core::fmt::Display::fmt(&weekday, f)?; + Ok(()) + } + } + } +} + +impl PosixTime { + const DEFAULT: PosixTime = PosixTime { second: 2 * 60 * 60 }; +} + +impl core::fmt::Display for PosixTime { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + if self.second.is_negative() { + f.write_str("-")?; + // The default is positive, so when + // positive, we write nothing. + } + let second = self.second.unsigned_abs(); + let h = second / 3600; + let m = (second / 60) % 60; + let s = second % 60; + core::fmt::Display::fmt(&h, f)?; + if m != 0 || s != 0 { + write!(f, ":{m:02}")?; + if s != 0 { + write!(f, ":{s:02}")?; + } + } + Ok(()) + } +} + +impl PosixOffset { + fn to_ioffset(&self) -> IOffset { + IOffset { second: self.second } + } +} + +impl core::fmt::Display for PosixOffset { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + // Yes, this is backwards. Blame POSIX. + // N.B. `+` is the default, so we don't + // need to write that out. + if self.second > 0 { + f.write_str("-")?; + } + let second = self.second.unsigned_abs(); + let h = second / 3600; + let m = (second / 60) % 60; + let s = second % 60; + core::fmt::Display::fmt(&h, f)?; + if m != 0 || s != 0 { + write!(f, ":{m:02}")?; + if s != 0 { + write!(f, ":{s:02}")?; + } + } + Ok(()) + } +} + +/// A helper type for formatting a time zone abbreviation. +/// +/// Basically, this will write the `<` and `>` quotes if necessary, and +/// otherwise write out the abbreviation in its unquoted form. +#[derive(Debug)] +struct AbbreviationDisplay(S); + +impl> core::fmt::Display for AbbreviationDisplay { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + let s = self.0.as_ref(); + if s.chars().any(|ch| ch == '+' || ch == '-') { + f.write_str("<")?; + core::fmt::Display::fmt(&s, f)?; + f.write_str(">") + } else { + core::fmt::Display::fmt(&s, f) + } + } +} + +/// The daylight saving time (DST) info for a POSIX time zone in a particular +/// year. +#[derive(Debug, Eq, PartialEq)] +struct DstInfo<'a, ABBREV> { + /// The DST transition rule that generated this info. + dst: &'a PosixDst, + /// The start time (inclusive) that DST begins. + /// + /// Note that this may be greater than `end`. This tends to happen in the + /// southern hemisphere. + /// + /// Note also that this may be in UTC or in wall clock civil + /// time. It depends on whether `PosixTimeZone::dst_info_utc` or + /// `PosixTimeZone::dst_info_wall` was used. + start: IDateTime, + /// The end time (exclusive) that DST ends. + /// + /// Note that this may be less than `start`. This tends to happen in the + /// southern hemisphere. + /// + /// Note also that this may be in UTC or in wall clock civil + /// time. It depends on whether `PosixTimeZone::dst_info_utc` or + /// `PosixTimeZone::dst_info_wall` was used. + end: IDateTime, +} + +impl<'a, ABBREV> DstInfo<'a, ABBREV> { + /// Returns true if and only if the given civil datetime ought to be + /// considered in DST. + fn in_dst(&self, utc_dt: IDateTime) -> bool { + if self.start <= self.end { + self.start <= utc_dt && utc_dt < self.end + } else { + !(self.end <= utc_dt && utc_dt < self.start) + } + } + + /// Returns the earlier and later times for this DST info. + fn ordered(&self) -> (IDateTime, IDateTime) { + if self.start <= self.end { + (self.start, self.end) + } else { + (self.end, self.start) + } + } + + /// Returns the DST offset. + fn offset(&self) -> &PosixOffset { + &self.dst.offset + } +} + +/// A parser for POSIX time zones. +#[derive(Debug)] +struct Parser<'s> { + /// The `TZ` string that we're parsing. + tz: &'s [u8], + /// The parser's current position in `tz`. + pos: core::cell::Cell, + /// Whether to use IANA rules, i.e., when parsing a TZ string in a TZif + /// file of version 3 or greater. From `tzfile(5)`: + /// + /// > First, the hours part of its transition times may be signed and range + /// > from `-167` through `167` instead of the POSIX-required unsigned + /// > values from `0` through `24`. Second, DST is in effect all year if + /// > it starts January 1 at 00:00 and ends December 31 at 24:00 plus the + /// > difference between daylight saving and standard time. + /// + /// At time of writing, I don't think I understand the significance of + /// the second part above. (RFC 8536 elaborates that it is meant to be an + /// explicit clarification of something that POSIX itself implies.) But the + /// first part is clear: it permits the hours to be a bigger range. + ianav3plus: bool, +} + +impl<'s> Parser<'s> { + /// Create a new parser for extracting a POSIX time zone from the given + /// bytes. + fn new>(tz: &'s B) -> Parser<'s> { + Parser { + tz: tz.as_ref(), + pos: core::cell::Cell::new(0), + ianav3plus: false, + } + } + + /// Parses a POSIX time zone from the current position of the parser and + /// ensures that the entire TZ string corresponds to a single valid POSIX + /// time zone. + fn parse( + &self, + ) -> Result, PosixTimeZoneError> { + let (time_zone, remaining) = self.parse_prefix()?; + if !remaining.is_empty() { + return Err(ErrorKind::FoundRemaining.into()); + } + Ok(time_zone) + } + + /// Parses a POSIX time zone from the current position of the parser and + /// returns the remaining input. + fn parse_prefix( + &self, + ) -> Result<(PosixTimeZone, &'s [u8]), PosixTimeZoneError> + { + let time_zone = self.parse_posix_time_zone()?; + Ok((time_zone, self.remaining())) + } + + /// Parse a POSIX time zone from the current position of the parser. + /// + /// Upon success, the parser will be positioned immediately following the + /// TZ string. + fn parse_posix_time_zone( + &self, + ) -> Result, PosixTimeZoneError> { + if self.is_done() { + return Err(ErrorKind::Empty.into()); + } + let std_abbrev = + self.parse_abbreviation().map_err(ErrorKind::AbbreviationStd)?; + let std_offset = + self.parse_posix_offset().map_err(ErrorKind::OffsetStd)?; + let mut dst = None; + if !self.is_done() + && (self.byte().is_ascii_alphabetic() || self.byte() == b'<') + { + dst = Some(self.parse_posix_dst(&std_offset)?); + } + Ok(PosixTimeZone { std_abbrev, std_offset, dst }) + } + + /// Parse a DST zone with an optional explicit transition rule. + /// + /// This assumes the parser is positioned at the first byte of the DST + /// abbreviation. + /// + /// Upon success, the parser will be positioned immediately after the end + /// of the DST transition rule (which might just be the abbreviation, but + /// might also include explicit start/end datetime specifications). + fn parse_posix_dst( + &self, + std_offset: &PosixOffset, + ) -> Result, PosixTimeZoneError> { + let abbrev = + self.parse_abbreviation().map_err(ErrorKind::AbbreviationDst)?; + if self.is_done() { + return Err(ErrorKind::FoundDstNoRule.into()); + } + // This is the default: one hour ahead of standard time. We may + // override this if the DST portion specifies an offset. (But it + // usually doesn't.) + let mut offset = PosixOffset { second: std_offset.second + 3600 }; + if self.byte() != b',' { + offset = + self.parse_posix_offset().map_err(ErrorKind::OffsetDst)?; + if self.is_done() { + return Err(ErrorKind::FoundDstNoRuleWithOffset.into()); + } + } + if self.byte() != b',' { + return Err(ErrorKind::ExpectedCommaAfterDst.into()); + } + if !self.bump() { + return Err(ErrorKind::FoundEndAfterComma.into()); + } + let rule = self.parse_rule().map_err(ErrorKind::Rule)?; + Ok(PosixDst { abbrev, offset, rule }) + } + + /// Parse a time zone abbreviation. + /// + /// This assumes the parser is positioned at the first byte of + /// the abbreviation. This is either the first character in the + /// abbreviation, or the opening quote of a quoted abbreviation. + /// + /// Upon success, the parser will be positioned immediately following + /// the abbreviation name. + /// + /// The string returned is guaranteed to be no more than 30 bytes. + /// (This restriction is somewhat arbitrary, but it's so we can put + /// the abbreviation in a fixed capacity array.) + fn parse_abbreviation(&self) -> Result { + if self.byte() == b'<' { + if !self.bump() { + return Err(AbbreviationError::Quoted( + QuotedAbbreviationError::UnexpectedEndAfterOpening, + )); + } + self.parse_quoted_abbreviation().map_err(AbbreviationError::Quoted) + } else { + self.parse_unquoted_abbreviation() + .map_err(AbbreviationError::Unquoted) + } + } + + /// Parses an unquoted time zone abbreviation. + /// + /// This assumes the parser is position at the first byte in the + /// abbreviation. + /// + /// Upon success, the parser will be positioned immediately after the + /// last byte in the abbreviation. + /// + /// The string returned is guaranteed to be no more than 30 bytes. + /// (This restriction is somewhat arbitrary, but it's so we can put + /// the abbreviation in a fixed capacity array.) + fn parse_unquoted_abbreviation( + &self, + ) -> Result { + let start = self.pos(); + for i in 0.. { + if !self.byte().is_ascii_alphabetic() { + break; + } + if i >= Abbreviation::capacity() { + return Err(UnquotedAbbreviationError::TooLong); + } + if !self.bump() { + break; + } + } + let end = self.pos(); + let abbrev = + core::str::from_utf8(&self.tz[start..end]).map_err(|_| { + // NOTE: I believe this error is technically impossible + // since the loop above restricts letters in an + // abbreviation to ASCII. So everything from `start` to + // `end` is ASCII and thus should be UTF-8. But it doesn't + // cost us anything to report an error here in case the + // code above evolves somehow. + UnquotedAbbreviationError::InvalidUtf8 + })?; + if abbrev.len() < 3 { + return Err(UnquotedAbbreviationError::TooShort); + } + // OK because we verified above that the abbreviation + // does not exceed `Abbreviation::capacity`. + Ok(Abbreviation::new(abbrev).unwrap()) + } + + /// Parses a quoted time zone abbreviation. + /// + /// This assumes the parser is positioned immediately after the opening + /// `<` quote. That is, at the first byte in the abbreviation. + /// + /// Upon success, the parser will be positioned immediately after the + /// closing `>` quote. + /// + /// The string returned is guaranteed to be no more than 30 bytes. + /// (This restriction is somewhat arbitrary, but it's so we can put + /// the abbreviation in a fixed capacity array.) + fn parse_quoted_abbreviation( + &self, + ) -> Result { + let start = self.pos(); + for i in 0.. { + if !self.byte().is_ascii_alphanumeric() + && self.byte() != b'+' + && self.byte() != b'-' + { + break; + } + if i >= Abbreviation::capacity() { + return Err(QuotedAbbreviationError::TooLong); + } + if !self.bump() { + break; + } + } + let end = self.pos(); + let abbrev = + core::str::from_utf8(&self.tz[start..end]).map_err(|_| { + // NOTE: I believe this error is technically impossible + // since the loop above restricts letters in an + // abbreviation to ASCII. So everything from `start` to + // `end` is ASCII and thus should be UTF-8. But it doesn't + // cost us anything to report an error here in case the + // code above evolves somehow. + QuotedAbbreviationError::InvalidUtf8 + })?; + if self.is_done() { + return Err(QuotedAbbreviationError::UnexpectedEnd); + } + if self.byte() != b'>' { + return Err(QuotedAbbreviationError::UnexpectedLastByte); + } + self.bump(); + if abbrev.len() < 3 { + return Err(QuotedAbbreviationError::TooShort); + } + // OK because we verified above that the abbreviation + // does not exceed `Abbreviation::capacity`. + Ok(Abbreviation::new(abbrev).unwrap()) + } + + /// Parse a POSIX time offset. + /// + /// This assumes the parser is positioned at the first byte of the + /// offset. This can either be a digit (for a positive offset) or the + /// sign of the offset (which must be either `-` or `+`). + /// + /// Upon success, the parser will be positioned immediately after the + /// end of the offset. + fn parse_posix_offset(&self) -> Result { + let sign = self.parse_optional_sign()?.unwrap_or(1); + let hour = self.parse_hour_posix()?; + let (mut minute, mut second) = (0, 0); + if self.maybe_byte() == Some(b':') { + if !self.bump() { + return Err(PosixOffsetError::IncompleteMinutes); + } + minute = self.parse_minute()?; + if self.maybe_byte() == Some(b':') { + if !self.bump() { + return Err(PosixOffsetError::IncompleteSeconds); + } + second = self.parse_second()?; + } + } + let mut offset = PosixOffset { second: i32::from(hour) * 3600 }; + offset.second += i32::from(minute) * 60; + offset.second += i32::from(second); + // Yes, we flip the sign, because POSIX is backwards. + // For example, `EST5` corresponds to `-05:00`. + offset.second *= i32::from(-sign); + // Must be true because the parsing routines for hours, minutes + // and seconds enforce they are in the ranges -24..=24, 0..=59 + // and 0..=59, respectively. + assert!( + -89999 <= offset.second && offset.second <= 89999, + "POSIX offset seconds {} is out of range", + offset.second + ); + Ok(offset) + } + + /// Parses a POSIX DST transition rule. + /// + /// This assumes the parser is positioned at the first byte in the + /// rule. That is, it comes immediately after the DST abbreviation or + /// its optional offset. + /// + /// Upon success, the parser will be positioned immediately after the + /// DST transition rule. In typical cases, this corresponds to the end + /// of the TZ string. + fn parse_rule(&self) -> Result { + let start = self + .parse_posix_datetime() + .map_err(PosixRuleError::DateTimeStart)?; + if self.maybe_byte() != Some(b',') || !self.bump() { + return Err(PosixRuleError::ExpectedEnd); + } + let end = self + .parse_posix_datetime() + .map_err(PosixRuleError::DateTimeEnd)?; + Ok(PosixRule { start, end }) + } + + /// Parses a POSIX datetime specification. + /// + /// This assumes the parser is position at the first byte where a + /// datetime specification is expected to occur. + /// + /// Upon success, the parser will be positioned after the datetime + /// specification. This will either be immediately after the date, or + /// if it's present, the time part of the specification. + fn parse_posix_datetime( + &self, + ) -> Result { + let mut daytime = PosixDayTime { + date: self.parse_posix_date()?, + time: PosixTime::DEFAULT, + }; + if self.maybe_byte() != Some(b'/') { + return Ok(daytime); + } + if !self.bump() { + return Err(PosixDateTimeError::ExpectedTime); + } + daytime.time = self.parse_posix_time()?; + Ok(daytime) + } + + /// Parses a POSIX date specification. + /// + /// This assumes the parser is positioned at the first byte of the date + /// specification. This can be `J` (for one based Julian day without + /// leap days), `M` (for "weekday of month") or a digit starting the + /// zero based Julian day with leap days. This routine will validate + /// that the position points to one of these possible values. That is, + /// the caller doesn't need to parse the `M` or the `J` or the leading + /// digit. The caller should just call this routine when it *expect* a + /// date specification to follow. + /// + /// Upon success, the parser will be positioned immediately after the + /// date specification. + fn parse_posix_date(&self) -> Result { + match self.byte() { + b'J' => { + if !self.bump() { + return Err(PosixDateError::ExpectedJulianNoLeap); + } + Ok(PosixDay::JulianOne(self.parse_posix_julian_day_no_leap()?)) + } + b'0'..=b'9' => Ok(PosixDay::JulianZero( + self.parse_posix_julian_day_with_leap()?, + )), + b'M' => { + if !self.bump() { + return Err(PosixDateError::ExpectedMonthWeekWeekday); + } + let (month, week, weekday) = self.parse_weekday_of_month()?; + Ok(PosixDay::WeekdayOfMonth { month, week, weekday }) + } + _ => Err(PosixDateError::UnexpectedByte), + } + } + + /// Parses a POSIX Julian day that does not include leap days + /// (`1 <= n <= 365`). + /// + /// This assumes the parser is positioned just after the `J` and at the + /// first digit of the Julian day. Upon success, the parser will be + /// positioned immediately following the day number. + fn parse_posix_julian_day_no_leap( + &self, + ) -> Result { + let number = self + .parse_number_with_upto_n_digits(3) + .map_err(PosixJulianNoLeapError::Parse)?; + let number = i16::try_from(number) + .map_err(|_| PosixJulianNoLeapError::Range)?; + if !(1 <= number && number <= 365) { + return Err(PosixJulianNoLeapError::Range); + } + Ok(number) + } + + /// Parses a POSIX Julian day that includes leap days (`0 <= n <= + /// 365`). + /// + /// This assumes the parser is positioned at the first digit of the + /// Julian day. Upon success, the parser will be positioned immediately + /// following the day number. + fn parse_posix_julian_day_with_leap( + &self, + ) -> Result { + let number = self + .parse_number_with_upto_n_digits(3) + .map_err(PosixJulianLeapError::Parse)?; + let number = + i16::try_from(number).map_err(|_| PosixJulianLeapError::Range)?; + if !(0 <= number && number <= 365) { + return Err(PosixJulianLeapError::Range); + } + Ok(number) + } + + /// Parses a POSIX "weekday of month" specification. + /// + /// This assumes the parser is positioned just after the `M` byte and + /// at the first digit of the month. Upon success, the parser will be + /// positioned immediately following the "weekday of the month" that + /// was parsed. + /// + /// The tuple returned is month (1..=12), week (1..=5) and weekday + /// (0..=6 with 0=Sunday). + fn parse_weekday_of_month( + &self, + ) -> Result<(i8, i8, i8), WeekdayOfMonthError> { + let month = self.parse_month()?; + if self.maybe_byte() != Some(b'.') { + return Err(WeekdayOfMonthError::ExpectedDotAfterMonth); + } + if !self.bump() { + return Err(WeekdayOfMonthError::ExpectedWeekAfterMonth); + } + let week = self.parse_week()?; + if self.maybe_byte() != Some(b'.') { + return Err(WeekdayOfMonthError::ExpectedDotAfterWeek); + } + if !self.bump() { + return Err(WeekdayOfMonthError::ExpectedDayOfWeekAfterWeek); + } + let weekday = self.parse_weekday()?; + Ok((month, week, weekday)) + } + + /// This parses a POSIX time specification in the format + /// `[+/-]hh?[:mm[:ss]]`. + /// + /// This assumes the parser is positioned at the first `h` (or the + /// sign, if present). Upon success, the parser will be positioned + /// immediately following the end of the time specification. + fn parse_posix_time(&self) -> Result { + let (sign, hour) = if self.ianav3plus { + let sign = self.parse_optional_sign()?.unwrap_or(1); + let hour = self.parse_hour_ianav3plus()?; + (sign, hour) + } else { + (1, i16::from(self.parse_hour_posix()?)) + }; + let (mut minute, mut second) = (0, 0); + if self.maybe_byte() == Some(b':') { + if !self.bump() { + return Err(PosixTimeError::IncompleteMinutes); + } + minute = self.parse_minute()?; + if self.maybe_byte() == Some(b':') { + if !self.bump() { + return Err(PosixTimeError::IncompleteSeconds); + } + second = self.parse_second()?; + } + } + let mut time = PosixTime { second: i32::from(hour) * 3600 }; + time.second += i32::from(minute) * 60; + time.second += i32::from(second); + time.second *= i32::from(sign); + // Must be true because the parsing routines for hours, minutes + // and seconds enforce they are in the ranges -167..=167, 0..=59 + // and 0..=59, respectively. + assert!( + -604799 <= time.second && time.second <= 604799, + "POSIX time seconds {} is out of range", + time.second + ); + Ok(time) + } + + /// Parses a month. + /// + /// This is expected to be positioned at the first digit. Upon success, + /// the parser will be positioned after the month (which may contain + /// two digits). + fn parse_month(&self) -> Result { + let number = self + .parse_number_with_upto_n_digits(2) + .map_err(MonthError::Parse)?; + let number = i8::try_from(number).map_err(|_| MonthError::Range)?; + if !(1 <= number && number <= 12) { + return Err(MonthError::Range); + } + Ok(number) + } + + /// Parses a week-of-month number. + /// + /// This is expected to be positioned at the first digit. Upon success, + /// the parser will be positioned after the week digit. + fn parse_week(&self) -> Result { + let number = self + .parse_number_with_exactly_n_digits(1) + .map_err(WeekOfMonthError::Parse)?; + let number = + i8::try_from(number).map_err(|_| WeekOfMonthError::Range)?; + if !(1 <= number && number <= 5) { + return Err(WeekOfMonthError::Range); + } + Ok(number) + } + + /// Parses a weekday number. + /// + /// This is expected to be positioned at the first digit. Upon success, + /// the parser will be positioned after the week digit. + /// + /// The weekday returned is guaranteed to be in the range `0..=6`, with + /// `0` corresponding to Sunday. + fn parse_weekday(&self) -> Result { + let number = self + .parse_number_with_exactly_n_digits(1) + .map_err(WeekdayError::Parse)?; + let number = i8::try_from(number).map_err(|_| WeekdayError::Range)?; + if !(0 <= number && number <= 6) { + return Err(WeekdayError::Range); + } + Ok(number) + } + + /// Parses an hour from a POSIX time specification with the IANA + /// v3+ extension. That is, the hour may be in the range `0..=167`. + /// (Callers should parse an optional sign preceding the hour digits + /// when IANA V3+ parsing is enabled.) + /// + /// The hour is allowed to be a single digit (unlike minutes or + /// seconds). + /// + /// This assumes the parser is positioned at the position where the + /// first hour digit should occur. Upon success, the parser will be + /// positioned immediately after the last hour digit. + fn parse_hour_ianav3plus(&self) -> Result { + // Callers should only be using this method when IANA v3+ parsing + // is enabled. + assert!(self.ianav3plus); + let number = self + .parse_number_with_upto_n_digits(3) + .map_err(HourIanaError::Parse)?; + let number = + i16::try_from(number).map_err(|_| HourIanaError::Range)?; + if !(0 <= number && number <= 167) { + // The error message says -167 but the check above uses 0. + // This is because the caller is responsible for parsing + // the sign. + return Err(HourIanaError::Range); + } + Ok(number) + } + + /// Parses an hour from a POSIX time specification, with the allowed + /// range being `0..=24`. + /// + /// The hour is allowed to be a single digit (unlike minutes or + /// seconds). + /// + /// This assumes the parser is positioned at the position where the + /// first hour digit should occur. Upon success, the parser will be + /// positioned immediately after the last hour digit. + fn parse_hour_posix(&self) -> Result { + let number = self + .parse_number_with_upto_n_digits(2) + .map_err(HourPosixError::Parse)?; + let number = + i8::try_from(number).map_err(|_| HourPosixError::Range)?; + if !(0 <= number && number <= 24) { + return Err(HourPosixError::Range); + } + Ok(number) + } + + /// Parses a minute from a POSIX time specification. + /// + /// The minute must be exactly two digits. + /// + /// This assumes the parser is positioned at the position where the + /// first minute digit should occur. Upon success, the parser will be + /// positioned immediately after the second minute digit. + fn parse_minute(&self) -> Result { + let number = self + .parse_number_with_exactly_n_digits(2) + .map_err(MinuteError::Parse)?; + let number = i8::try_from(number).map_err(|_| MinuteError::Range)?; + if !(0 <= number && number <= 59) { + return Err(MinuteError::Range); + } + Ok(number) + } + + /// Parses a second from a POSIX time specification. + /// + /// The second must be exactly two digits. + /// + /// This assumes the parser is positioned at the position where the + /// first second digit should occur. Upon success, the parser will be + /// positioned immediately after the second second digit. + fn parse_second(&self) -> Result { + let number = self + .parse_number_with_exactly_n_digits(2) + .map_err(SecondError::Parse)?; + let number = i8::try_from(number).map_err(|_| SecondError::Range)?; + if !(0 <= number && number <= 59) { + return Err(SecondError::Range); + } + Ok(number) + } + + /// Parses a signed 64-bit integer expressed in exactly `n` digits. + /// + /// If `n` digits could not be found (or if the `TZ` string ends before + /// `n` digits could be found), then this returns an error. + /// + /// This assumes that `n >= 1` and that the parser is positioned at the + /// first digit. Upon success, the parser is positioned immediately + /// after the `n`th digit. + fn parse_number_with_exactly_n_digits( + &self, + n: usize, + ) -> Result { + assert!(n >= 1, "numbers must have at least 1 digit"); + let mut number: i32 = 0; + for _ in 0..n { + if self.is_done() { + return Err(NumberError::ExpectedLength); + } + let byte = self.byte(); + let digit = match byte.checked_sub(b'0') { + None => { + return Err(NumberError::InvalidDigit); + } + Some(digit) if digit > 9 => { + return Err(NumberError::InvalidDigit); + } + Some(digit) => { + debug_assert!((0..=9).contains(&digit)); + i32::from(digit) + } + }; + number = number + .checked_mul(10) + .and_then(|n| n.checked_add(digit)) + .ok_or(NumberError::TooBig)?; + self.bump(); + } + Ok(number) + } + + /// Parses a signed 64-bit integer expressed with up to `n` digits and + /// at least 1 digit. + /// + /// This assumes that `n >= 1` and that the parser is positioned at the + /// first digit. Upon success, the parser is position immediately after + /// the last digit (which can be at most `n`). + fn parse_number_with_upto_n_digits( + &self, + n: usize, + ) -> Result { + assert!(n >= 1, "numbers must have at least 1 digit"); + let mut number: i32 = 0; + for i in 0..n { + if self.is_done() || !self.byte().is_ascii_digit() { + if i == 0 { + return Err(NumberError::Empty); + } + break; + } + let digit = i32::from(self.byte() - b'0'); + number = number + .checked_mul(10) + .and_then(|n| n.checked_add(digit)) + .ok_or(NumberError::TooBig)?; + self.bump(); + } + Ok(number) + } + + /// Parses an optional sign. + /// + /// This assumes the parser is positioned at the position where a + /// positive or negative sign is permitted. If one exists, then it + /// is consumed and returned. Moreover, if one exists, then this + /// guarantees that it is not the last byte in the input. That is, upon + /// success, it is valid to call `self.byte()`. + fn parse_optional_sign(&self) -> Result, OptionalSignError> { + if self.is_done() { + return Ok(None); + } + Ok(match self.byte() { + b'-' => { + if !self.bump() { + return Err(OptionalSignError::ExpectedDigitAfterMinus); + } + Some(-1) + } + b'+' => { + if !self.bump() { + return Err(OptionalSignError::ExpectedDigitAfterPlus); + } + Some(1) + } + _ => None, + }) + } +} + +/// Helper routines for parsing a POSIX `TZ` string. +impl<'s> Parser<'s> { + /// Bump the parser to the next byte. + /// + /// If the end of the input has been reached, then `false` is returned. + fn bump(&self) -> bool { + if self.is_done() { + return false; + } + self.pos.set( + self.pos().checked_add(1).expect("pos cannot overflow usize"), + ); + !self.is_done() + } + + /// Returns true if the next call to `bump` would return false. + fn is_done(&self) -> bool { + self.pos() == self.tz.len() + } + + /// Return the byte at the current position of the parser. + /// + /// This panics if the parser is positioned at the end of the TZ + /// string. + fn byte(&self) -> u8 { + self.tz[self.pos()] + } + + /// Return the byte at the current position of the parser. If the TZ + /// string has been exhausted, then this returns `None`. + fn maybe_byte(&self) -> Option { + self.tz.get(self.pos()).copied() + } + + /// Return the current byte offset of the parser. + /// + /// The offset starts at `0` from the beginning of the TZ string. + fn pos(&self) -> usize { + self.pos.get() + } + + /// Returns the remaining bytes of the TZ string. + /// + /// This includes `self.byte()`. It may be empty. + fn remaining(&self) -> &'s [u8] { + &self.tz[self.pos()..] + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct PosixTimeZoneError { + kind: ErrorKind, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +enum ErrorKind { + AbbreviationDst(AbbreviationError), + AbbreviationStd(AbbreviationError), + Empty, + ExpectedCommaAfterDst, + FoundDstNoRule, + FoundDstNoRuleWithOffset, + FoundEndAfterComma, + FoundRemaining, + OffsetDst(PosixOffsetError), + OffsetStd(PosixOffsetError), + Rule(PosixRuleError), +} + +impl core::fmt::Display for PosixTimeZoneError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + use self::ErrorKind::*; + match self.kind { + AbbreviationDst(ref err) => { + f.write_str("failed to parse DST time zone abbreviation: ")?; + core::fmt::Display::fmt(err, f) + } + AbbreviationStd(ref err) => { + f.write_str( + "failed to parse standard time zone abbreviation: ", + )?; + core::fmt::Display::fmt(err, f) + } + Empty => f.write_str( + "an empty string is not a valid POSIX time zone \ + transition rule", + ), + ExpectedCommaAfterDst => f.write_str( + "expected `,` after parsing DST offset \ + in POSIX time zone string", + ), + FoundDstNoRule => f.write_str( + "found DST abbreviation in POSIX time zone string, \ + but no transition rule \ + (this is technically allowed by POSIX, but has \ + unspecified behavior)", + ), + FoundDstNoRuleWithOffset => f.write_str( + "found DST abbreviation and offset in POSIX time zone string, \ + but no transition rule \ + (this is technically allowed by POSIX, but has \ + unspecified behavior)", + ), + FoundEndAfterComma => f.write_str( + "after parsing DST offset in POSIX time zone string, \ + found end of string after a trailing `,`", + ), + FoundRemaining => f.write_str( + "expected entire POSIX TZ string to be a valid \ + time zone transition rule, but found data after \ + parsing a valid time zone transition rule", + ), + OffsetDst(ref err) => { + f.write_str("failed to parse DST offset: ")?; + core::fmt::Display::fmt(err, f) + } + OffsetStd(ref err) => { + f.write_str("failed to parse standard offset: ")?; + core::fmt::Display::fmt(err, f) + } + Rule(ref err) => core::fmt::Display::fmt(err, f), + } + } +} + +impl From for PosixTimeZoneError { + fn from(kind: ErrorKind) -> PosixTimeZoneError { + PosixTimeZoneError { kind } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +enum PosixOffsetError { + HourPosix(HourPosixError), + IncompleteMinutes, + IncompleteSeconds, + Minute(MinuteError), + OptionalSign(OptionalSignError), + Second(SecondError), +} + +impl core::fmt::Display for PosixOffsetError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + use self::PosixOffsetError::*; + match *self { + HourPosix(ref err) => core::fmt::Display::fmt(err, f), + IncompleteMinutes => f.write_str( + "incomplete time in \ + POSIX time zone string (missing minutes)", + ), + IncompleteSeconds => f.write_str( + "incomplete time in \ + POSIX time zone string (missing seconds)", + ), + Minute(ref err) => core::fmt::Display::fmt(err, f), + Second(ref err) => core::fmt::Display::fmt(err, f), + OptionalSign(ref err) => { + f.write_str( + "failed to parse sign for time offset \ + POSIX time zone string", + )?; + core::fmt::Display::fmt(err, f) + } + } + } +} + +impl From for PosixOffsetError { + fn from(err: HourPosixError) -> PosixOffsetError { + PosixOffsetError::HourPosix(err) + } +} + +impl From for PosixOffsetError { + fn from(err: MinuteError) -> PosixOffsetError { + PosixOffsetError::Minute(err) + } +} + +impl From for PosixOffsetError { + fn from(err: OptionalSignError) -> PosixOffsetError { + PosixOffsetError::OptionalSign(err) + } +} + +impl From for PosixOffsetError { + fn from(err: SecondError) -> PosixOffsetError { + PosixOffsetError::Second(err) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +enum PosixRuleError { + DateTimeEnd(PosixDateTimeError), + DateTimeStart(PosixDateTimeError), + ExpectedEnd, +} + +impl core::fmt::Display for PosixRuleError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + use self::PosixRuleError::*; + match *self { + DateTimeEnd(ref err) => { + f.write_str("failed to parse end of DST transition rule: ")?; + core::fmt::Display::fmt(err, f) + } + DateTimeStart(ref err) => { + f.write_str("failed to parse start of DST transition rule: ")?; + core::fmt::Display::fmt(err, f) + } + ExpectedEnd => f.write_str( + "expected end of DST rule after parsing the start \ + of the DST rule", + ), + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +enum PosixDateTimeError { + Date(PosixDateError), + ExpectedTime, + Time(PosixTimeError), +} + +impl core::fmt::Display for PosixDateTimeError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + use self::PosixDateTimeError::*; + match *self { + Date(ref err) => core::fmt::Display::fmt(err, f), + ExpectedTime => f.write_str( + "expected time specification after `/` following a date + specification in a POSIX time zone DST transition rule", + ), + Time(ref err) => core::fmt::Display::fmt(err, f), + } + } +} + +impl From for PosixDateTimeError { + fn from(err: PosixDateError) -> PosixDateTimeError { + PosixDateTimeError::Date(err) + } +} + +impl From for PosixDateTimeError { + fn from(err: PosixTimeError) -> PosixDateTimeError { + PosixDateTimeError::Time(err) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +enum PosixDateError { + ExpectedJulianNoLeap, + ExpectedMonthWeekWeekday, + JulianLeap(PosixJulianLeapError), + JulianNoLeap(PosixJulianNoLeapError), + UnexpectedByte, + WeekdayOfMonth(WeekdayOfMonthError), +} + +impl core::fmt::Display for PosixDateError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + use self::PosixDateError::*; + match *self { + ExpectedJulianNoLeap => f.write_str( + "expected one-based Julian day after `J` in date \ + specification of a POSIX time zone DST \ + transition rule, but found the end of input", + ), + ExpectedMonthWeekWeekday => f.write_str( + "expected month-week-weekday after `M` in date \ + specification of a POSIX time zone DST \ + transition rule, but found the end of input", + ), + JulianLeap(ref err) => core::fmt::Display::fmt(err, f), + JulianNoLeap(ref err) => core::fmt::Display::fmt(err, f), + UnexpectedByte => f.write_str( + "expected `J`, a digit or `M` at the beginning of a date \ + specification of a POSIX time zone DST transition rule", + ), + WeekdayOfMonth(ref err) => core::fmt::Display::fmt(err, f), + } + } +} + +impl From for PosixDateError { + fn from(err: PosixJulianLeapError) -> PosixDateError { + PosixDateError::JulianLeap(err) + } +} + +impl From for PosixDateError { + fn from(err: PosixJulianNoLeapError) -> PosixDateError { + PosixDateError::JulianNoLeap(err) + } +} + +impl From for PosixDateError { + fn from(err: WeekdayOfMonthError) -> PosixDateError { + PosixDateError::WeekdayOfMonth(err) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +enum PosixJulianNoLeapError { + Parse(NumberError), + Range, +} + +impl core::fmt::Display for PosixJulianNoLeapError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + use self::PosixJulianNoLeapError::*; + match *self { + Parse(ref err) => { + f.write_str("invalid one-based Julian day digits: ")?; + core::fmt::Display::fmt(err, f) + } + Range => f.write_str( + "parsed one-based Julian day, but it's not in supported \ + range of `1..=365`", + ), + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +enum PosixJulianLeapError { + Parse(NumberError), + Range, +} + +impl core::fmt::Display for PosixJulianLeapError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + use self::PosixJulianLeapError::*; + match *self { + Parse(ref err) => { + f.write_str("invalid zero-based Julian day digits: ")?; + core::fmt::Display::fmt(err, f) + } + Range => f.write_str( + "parsed zero-based Julian day, but it's not in supported \ + range of `0..=365`", + ), + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +enum AbbreviationError { + Quoted(QuotedAbbreviationError), + Unquoted(UnquotedAbbreviationError), +} + +impl core::fmt::Display for AbbreviationError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + use self::AbbreviationError::*; + match *self { + Quoted(ref err) => core::fmt::Display::fmt(err, f), + Unquoted(ref err) => core::fmt::Display::fmt(err, f), + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +enum UnquotedAbbreviationError { + InvalidUtf8, + TooLong, + TooShort, +} + +impl core::fmt::Display for UnquotedAbbreviationError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + use self::UnquotedAbbreviationError::*; + match *self { + InvalidUtf8 => f.write_str( + "unquoted time zone abbreviation must be valid UTF-8", + ), + TooLong => write!( + f, + "expected unquoted time zone abbreviation with at most \ + {} bytes, but found an abbreviation that is longer", + Abbreviation::capacity(), + ), + TooShort => f.write_str( + "expected unquoted time zone abbreviation to have length of \ + 3 or more bytes, but an abbreviation that is shorter", + ), + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +enum QuotedAbbreviationError { + InvalidUtf8, + TooLong, + TooShort, + UnexpectedEnd, + UnexpectedEndAfterOpening, + UnexpectedLastByte, +} + +impl core::fmt::Display for QuotedAbbreviationError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + use self::QuotedAbbreviationError::*; + match *self { + InvalidUtf8 => f.write_str( + "quoted time zone abbreviation must be valid UTF-8", + ), + TooLong => write!( + f, + "expected quoted time zone abbreviation with at most \ + {} bytes, but found an abbreviation that is longer", + Abbreviation::capacity(), + ), + TooShort => f.write_str( + "expected quoted time zone abbreviation to have length of \ + 3 or more bytes, but an abbreviation that is shorter", + ), + UnexpectedEnd => f.write_str( + "found non-empty quoted time zone abbreviation, but \ + found end of input before an end-of-quoted abbreviation \ + `>` character", + ), + UnexpectedEndAfterOpening => f.write_str( + "found opening `<` quote for time zone abbreviation in \ + POSIX time zone transition rule, and expected a name \ + following it, but found the end of input instead", + ), + UnexpectedLastByte => f.write_str( + "found non-empty quoted time zone abbreviation, but \ + found did not find end-of-quoted abbreviation `>` \ + character", + ), + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +enum WeekdayOfMonthError { + ExpectedDayOfWeekAfterWeek, + ExpectedDotAfterMonth, + ExpectedDotAfterWeek, + ExpectedWeekAfterMonth, + Month(MonthError), + WeekOfMonth(WeekOfMonthError), + Weekday(WeekdayError), +} + +impl core::fmt::Display for WeekdayOfMonthError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + use self::WeekdayOfMonthError::*; + match *self { + ExpectedDayOfWeekAfterWeek => f.write_str( + "expected day-of-week after week in POSIX time zone rule", + ), + ExpectedDotAfterMonth => { + f.write_str("expected `.` after month in POSIX time zone rule") + } + ExpectedWeekAfterMonth => f.write_str( + "expected week after month in POSIX time zone rule", + ), + ExpectedDotAfterWeek => { + f.write_str("expected `.` after week in POSIX time zone rule") + } + Month(ref err) => core::fmt::Display::fmt(err, f), + WeekOfMonth(ref err) => core::fmt::Display::fmt(err, f), + Weekday(ref err) => core::fmt::Display::fmt(err, f), + } + } +} + +impl From for WeekdayOfMonthError { + fn from(err: MonthError) -> WeekdayOfMonthError { + WeekdayOfMonthError::Month(err) + } +} + +impl From for WeekdayOfMonthError { + fn from(err: WeekOfMonthError) -> WeekdayOfMonthError { + WeekdayOfMonthError::WeekOfMonth(err) + } +} + +impl From for WeekdayOfMonthError { + fn from(err: WeekdayError) -> WeekdayOfMonthError { + WeekdayOfMonthError::Weekday(err) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +enum PosixTimeError { + HourIana(HourIanaError), + HourPosix(HourPosixError), + IncompleteMinutes, + IncompleteSeconds, + Minute(MinuteError), + OptionalSign(OptionalSignError), + Second(SecondError), +} + +impl core::fmt::Display for PosixTimeError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + use self::PosixTimeError::*; + match *self { + HourIana(ref err) => core::fmt::Display::fmt(err, f), + HourPosix(ref err) => core::fmt::Display::fmt(err, f), + IncompleteMinutes => f.write_str( + "incomplete time zone transition time in \ + POSIX time zone string (missing minutes)", + ), + IncompleteSeconds => f.write_str( + "incomplete time zone transition time in \ + POSIX time zone string (missing seconds)", + ), + Minute(ref err) => core::fmt::Display::fmt(err, f), + Second(ref err) => core::fmt::Display::fmt(err, f), + OptionalSign(ref err) => { + f.write_str( + "failed to parse sign for time zone transition time", + )?; + core::fmt::Display::fmt(err, f) + } + } + } +} + +impl From for PosixTimeError { + fn from(err: HourIanaError) -> PosixTimeError { + PosixTimeError::HourIana(err) + } +} + +impl From for PosixTimeError { + fn from(err: HourPosixError) -> PosixTimeError { + PosixTimeError::HourPosix(err) + } +} + +impl From for PosixTimeError { + fn from(err: MinuteError) -> PosixTimeError { + PosixTimeError::Minute(err) + } +} + +impl From for PosixTimeError { + fn from(err: OptionalSignError) -> PosixTimeError { + PosixTimeError::OptionalSign(err) + } +} + +impl From for PosixTimeError { + fn from(err: SecondError) -> PosixTimeError { + PosixTimeError::Second(err) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +enum MonthError { + Parse(NumberError), + Range, +} + +impl core::fmt::Display for MonthError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + use self::MonthError::*; + match *self { + Parse(ref err) => { + f.write_str("invalid month digits: ")?; + core::fmt::Display::fmt(err, f) + } + Range => f.write_str( + "parsed month, but it's not in supported \ + range of `1..=12`", + ), + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +enum WeekOfMonthError { + Parse(NumberError), + Range, +} + +impl core::fmt::Display for WeekOfMonthError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + use self::WeekOfMonthError::*; + match *self { + Parse(ref err) => { + f.write_str("invalid week-of-month digits: ")?; + core::fmt::Display::fmt(err, f) + } + Range => f.write_str( + "parsed week-of-month, but it's not in supported \ + range of `1..=5`", + ), + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +enum WeekdayError { + Parse(NumberError), + Range, +} + +impl core::fmt::Display for WeekdayError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + use self::WeekdayError::*; + match *self { + Parse(ref err) => { + f.write_str("invalid weekday digits: ")?; + core::fmt::Display::fmt(err, f) + } + Range => f.write_str( + "parsed weekday, but it's not in supported \ + range of `0..=6` (with `0` corresponding to Sunday)", + ), + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +enum HourIanaError { + Parse(NumberError), + Range, +} + +impl core::fmt::Display for HourIanaError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + use self::HourIanaError::*; + match *self { + Parse(ref err) => { + f.write_str("invalid hour digits: ")?; + core::fmt::Display::fmt(err, f) + } + Range => f.write_str( + "parsed hours, but it's not in supported \ + range of `-167..=167`", + ), + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +enum HourPosixError { + Parse(NumberError), + Range, +} + +impl core::fmt::Display for HourPosixError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + use self::HourPosixError::*; + match *self { + Parse(ref err) => { + f.write_str("invalid hour digits: ")?; + core::fmt::Display::fmt(err, f) + } + Range => f.write_str( + "parsed hours, but it's not in supported \ + range of `0..=24`", + ), + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +enum MinuteError { + Parse(NumberError), + Range, +} + +impl core::fmt::Display for MinuteError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + use self::MinuteError::*; + match *self { + Parse(ref err) => { + f.write_str("invalid minute digits: ")?; + core::fmt::Display::fmt(err, f) + } + Range => f.write_str( + "parsed minutes, but it's not in supported \ + range of `0..=59`", + ), + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +enum SecondError { + Parse(NumberError), + Range, +} + +impl core::fmt::Display for SecondError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + use self::SecondError::*; + match *self { + Parse(ref err) => { + f.write_str("invalid second digits: ")?; + core::fmt::Display::fmt(err, f) + } + Range => f.write_str( + "parsed seconds, but it's not in supported \ + range of `0..=59`", + ), + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +enum NumberError { + Empty, + ExpectedLength, + InvalidDigit, + TooBig, +} + +impl core::fmt::Display for NumberError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + use self::NumberError::*; + match *self { + Empty => f.write_str("invalid number, no digits found"), + ExpectedLength => f.write_str( + "expected a fixed number of digits, \ + but found incorrect number", + ), + InvalidDigit => f.write_str("expected digit in range `0..=9`"), + TooBig => f.write_str( + "parsed number too big to fit into a 32-bit signed integer", + ), + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +enum OptionalSignError { + ExpectedDigitAfterMinus, + ExpectedDigitAfterPlus, +} + +impl core::fmt::Display for OptionalSignError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + use self::OptionalSignError::*; + match *self { + ExpectedDigitAfterMinus => f.write_str( + "expected digit after `-` sign, \ + but got end of input", + ), + ExpectedDigitAfterPlus => f.write_str( + "expected digit after `+` sign, \ + but got end of input", + ), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn posix_time_zone( + input: impl AsRef<[u8]>, + ) -> PosixTimeZone { + let input = input.as_ref(); + let tz = PosixTimeZone::parse(input).unwrap(); + { + use alloc::string::ToString; + + // While we're here, assert that converting the TZ back + // to a string matches what we got. In the original version + // of the POSIX TZ parser, we were very meticulous about + // capturing the exact AST of the time zone. But I've + // since simplified the data structure considerably such + // that it is lossy in terms of what was actually parsed + // (but of course, not lossy in terms of the semantic + // meaning of the time zone). + // + // So to account for this, we serialize to a string and + // then parse it back. We should get what we started with. + let reparsed = + PosixTimeZone::parse(tz.to_string().as_bytes()).unwrap(); + assert_eq!(tz, reparsed); + assert_eq!(tz.to_string(), reparsed.to_string()); + } + tz + } + + fn parser(s: &str) -> Parser<'_> { + Parser::new(s.as_bytes()) + } + + fn date(year: i16, month: i8, day: i8) -> IDate { + IDate { year, month, day } + } + + #[test] + fn parse() { + let p = parser("NZST-12NZDT,J60,J300"); + assert_eq!( + p.parse().unwrap(), + PosixTimeZone { + std_abbrev: "NZST".into(), + std_offset: PosixOffset { second: 12 * 60 * 60 }, + dst: Some(PosixDst { + abbrev: "NZDT".into(), + offset: PosixOffset { second: 13 * 60 * 60 }, + rule: PosixRule { + start: PosixDayTime { + date: PosixDay::JulianOne(60), + time: PosixTime { second: 2 * 60 * 60 }, + }, + end: PosixDayTime { + date: PosixDay::JulianOne(300), + time: PosixTime { second: 2 * 60 * 60 }, + }, + }, + }), + }, + ); + + let p = Parser::new("NZST-12NZDT,J60,J300WAT"); + assert!(p.parse().is_err()); + } + + #[test] + fn parse_posix_time_zone() { + let p = Parser::new("NZST-12NZDT,M9.5.0,M4.1.0/3"); + assert_eq!( + p.parse_posix_time_zone().unwrap(), + PosixTimeZone { + std_abbrev: "NZST".into(), + std_offset: PosixOffset { second: 12 * 60 * 60 }, + dst: Some(PosixDst { + abbrev: "NZDT".into(), + offset: PosixOffset { second: 13 * 60 * 60 }, + rule: PosixRule { + start: PosixDayTime { + date: PosixDay::WeekdayOfMonth { + month: 9, + week: 5, + weekday: 0, + }, + time: PosixTime { second: 2 * 60 * 60 }, + }, + end: PosixDayTime { + date: PosixDay::WeekdayOfMonth { + month: 4, + week: 1, + weekday: 0, + }, + time: PosixTime { second: 3 * 60 * 60 }, + }, + }, + }), + }, + ); + + let p = Parser::new("NZST-12NZDT,M9.5.0,M4.1.0/3WAT"); + assert_eq!( + p.parse_posix_time_zone().unwrap(), + PosixTimeZone { + std_abbrev: "NZST".into(), + std_offset: PosixOffset { second: 12 * 60 * 60 }, + dst: Some(PosixDst { + abbrev: "NZDT".into(), + offset: PosixOffset { second: 13 * 60 * 60 }, + rule: PosixRule { + start: PosixDayTime { + date: PosixDay::WeekdayOfMonth { + month: 9, + week: 5, + weekday: 0, + }, + time: PosixTime { second: 2 * 60 * 60 }, + }, + end: PosixDayTime { + date: PosixDay::WeekdayOfMonth { + month: 4, + week: 1, + weekday: 0, + }, + time: PosixTime { second: 3 * 60 * 60 }, + }, + }, + }), + }, + ); + + let p = Parser::new("NZST-12NZDT,J60,J300"); + assert_eq!( + p.parse_posix_time_zone().unwrap(), + PosixTimeZone { + std_abbrev: "NZST".into(), + std_offset: PosixOffset { second: 12 * 60 * 60 }, + dst: Some(PosixDst { + abbrev: "NZDT".into(), + offset: PosixOffset { second: 13 * 60 * 60 }, + rule: PosixRule { + start: PosixDayTime { + date: PosixDay::JulianOne(60), + time: PosixTime { second: 2 * 60 * 60 }, + }, + end: PosixDayTime { + date: PosixDay::JulianOne(300), + time: PosixTime { second: 2 * 60 * 60 }, + }, + }, + }), + }, + ); + + let p = Parser::new("NZST-12NZDT,J60,J300WAT"); + assert_eq!( + p.parse_posix_time_zone().unwrap(), + PosixTimeZone { + std_abbrev: "NZST".into(), + std_offset: PosixOffset { second: 12 * 60 * 60 }, + dst: Some(PosixDst { + abbrev: "NZDT".into(), + offset: PosixOffset { second: 13 * 60 * 60 }, + rule: PosixRule { + start: PosixDayTime { + date: PosixDay::JulianOne(60), + time: PosixTime { second: 2 * 60 * 60 }, + }, + end: PosixDayTime { + date: PosixDay::JulianOne(300), + time: PosixTime { second: 2 * 60 * 60 }, + }, + }, + }), + }, + ); + } + + #[test] + fn parse_posix_dst() { + let p = Parser::new("NZDT,M9.5.0,M4.1.0/3"); + assert_eq!( + p.parse_posix_dst(&PosixOffset { second: 12 * 60 * 60 }).unwrap(), + PosixDst { + abbrev: "NZDT".into(), + offset: PosixOffset { second: 13 * 60 * 60 }, + rule: PosixRule { + start: PosixDayTime { + date: PosixDay::WeekdayOfMonth { + month: 9, + week: 5, + weekday: 0, + }, + time: PosixTime { second: 2 * 60 * 60 }, + }, + end: PosixDayTime { + date: PosixDay::WeekdayOfMonth { + month: 4, + week: 1, + weekday: 0, + }, + time: PosixTime { second: 3 * 60 * 60 }, + }, + }, + }, + ); + + let p = Parser::new("NZDT,J60,J300"); + assert_eq!( + p.parse_posix_dst(&PosixOffset { second: 12 * 60 * 60 }).unwrap(), + PosixDst { + abbrev: "NZDT".into(), + offset: PosixOffset { second: 13 * 60 * 60 }, + rule: PosixRule { + start: PosixDayTime { + date: PosixDay::JulianOne(60), + time: PosixTime { second: 2 * 60 * 60 }, + }, + end: PosixDayTime { + date: PosixDay::JulianOne(300), + time: PosixTime { second: 2 * 60 * 60 }, + }, + }, + }, + ); + + let p = Parser::new("NZDT-7,J60,J300"); + assert_eq!( + p.parse_posix_dst(&PosixOffset { second: 12 * 60 * 60 }).unwrap(), + PosixDst { + abbrev: "NZDT".into(), + offset: PosixOffset { second: 7 * 60 * 60 }, + rule: PosixRule { + start: PosixDayTime { + date: PosixDay::JulianOne(60), + time: PosixTime { second: 2 * 60 * 60 }, + }, + end: PosixDayTime { + date: PosixDay::JulianOne(300), + time: PosixTime { second: 2 * 60 * 60 }, + }, + }, + }, + ); + + let p = Parser::new("NZDT+7,J60,J300"); + assert_eq!( + p.parse_posix_dst(&PosixOffset { second: 12 * 60 * 60 }).unwrap(), + PosixDst { + abbrev: "NZDT".into(), + offset: PosixOffset { second: -7 * 60 * 60 }, + rule: PosixRule { + start: PosixDayTime { + date: PosixDay::JulianOne(60), + time: PosixTime { second: 2 * 60 * 60 }, + }, + end: PosixDayTime { + date: PosixDay::JulianOne(300), + time: PosixTime { second: 2 * 60 * 60 }, + }, + }, + }, + ); + + let p = Parser::new("NZDT7,J60,J300"); + assert_eq!( + p.parse_posix_dst(&PosixOffset { second: 12 * 60 * 60 }).unwrap(), + PosixDst { + abbrev: "NZDT".into(), + offset: PosixOffset { second: -7 * 60 * 60 }, + rule: PosixRule { + start: PosixDayTime { + date: PosixDay::JulianOne(60), + time: PosixTime { second: 2 * 60 * 60 }, + }, + end: PosixDayTime { + date: PosixDay::JulianOne(300), + time: PosixTime { second: 2 * 60 * 60 }, + }, + }, + }, + ); + + let p = Parser::new("NZDT7,"); + assert!(p + .parse_posix_dst(&PosixOffset { second: 12 * 60 * 60 }) + .is_err()); + + let p = Parser::new("NZDT7!"); + assert!(p + .parse_posix_dst(&PosixOffset { second: 12 * 60 * 60 }) + .is_err()); + } + + #[test] + fn parse_abbreviation() { + let p = Parser::new("ABC"); + assert_eq!(p.parse_abbreviation().unwrap(), "ABC"); + + let p = Parser::new(""); + assert_eq!(p.parse_abbreviation().unwrap(), "ABC"); + + let p = Parser::new("<+09>"); + assert_eq!(p.parse_abbreviation().unwrap(), "+09"); + + let p = Parser::new("+09"); + assert!(p.parse_abbreviation().is_err()); + } + + #[test] + fn parse_unquoted_abbreviation() { + let p = Parser::new("ABC"); + assert_eq!(p.parse_unquoted_abbreviation().unwrap(), "ABC"); + + let p = Parser::new("ABCXYZ"); + assert_eq!(p.parse_unquoted_abbreviation().unwrap(), "ABCXYZ"); + + let p = Parser::new("ABC123"); + assert_eq!(p.parse_unquoted_abbreviation().unwrap(), "ABC"); + + let tz = "a".repeat(30); + let p = Parser::new(&tz); + assert_eq!(p.parse_unquoted_abbreviation().unwrap(), &*tz); + + let p = Parser::new("a"); + assert!(p.parse_unquoted_abbreviation().is_err()); + + let p = Parser::new("ab"); + assert!(p.parse_unquoted_abbreviation().is_err()); + + let p = Parser::new("ab1"); + assert!(p.parse_unquoted_abbreviation().is_err()); + + let tz = "a".repeat(31); + let p = Parser::new(&tz); + assert!(p.parse_unquoted_abbreviation().is_err()); + + let p = Parser::new(b"ab\xFFcd"); + assert!(p.parse_unquoted_abbreviation().is_err()); + } + + #[test] + fn parse_quoted_abbreviation() { + // The inputs look a little funny here, but that's because + // 'parse_quoted_abbreviation' starts after the opening quote + // has been parsed. + + let p = Parser::new("ABC>"); + assert_eq!(p.parse_quoted_abbreviation().unwrap(), "ABC"); + + let p = Parser::new("ABCXYZ>"); + assert_eq!(p.parse_quoted_abbreviation().unwrap(), "ABCXYZ"); + + let p = Parser::new("ABC>123"); + assert_eq!(p.parse_quoted_abbreviation().unwrap(), "ABC"); + + let p = Parser::new("ABC123>"); + assert_eq!(p.parse_quoted_abbreviation().unwrap(), "ABC123"); + + let p = Parser::new("ab1>"); + assert_eq!(p.parse_quoted_abbreviation().unwrap(), "ab1"); + + let p = Parser::new("+09>"); + assert_eq!(p.parse_quoted_abbreviation().unwrap(), "+09"); + + let p = Parser::new("-09>"); + assert_eq!(p.parse_quoted_abbreviation().unwrap(), "-09"); + + let tz = alloc::format!("{}>", "a".repeat(30)); + let p = Parser::new(&tz); + assert_eq!( + p.parse_quoted_abbreviation().unwrap(), + tz.trim_end_matches(">") + ); + + let p = Parser::new("a>"); + assert!(p.parse_quoted_abbreviation().is_err()); + + let p = Parser::new("ab>"); + assert!(p.parse_quoted_abbreviation().is_err()); + + let tz = alloc::format!("{}>", "a".repeat(31)); + let p = Parser::new(&tz); + assert!(p.parse_quoted_abbreviation().is_err()); + + let p = Parser::new(b"ab\xFFcd>"); + assert!(p.parse_quoted_abbreviation().is_err()); + + let p = Parser::new("ABC"); + assert!(p.parse_quoted_abbreviation().is_err()); + + let p = Parser::new("ABC!>"); + assert!(p.parse_quoted_abbreviation().is_err()); + } + + #[test] + fn parse_posix_offset() { + let p = Parser::new("5"); + assert_eq!(p.parse_posix_offset().unwrap().second, -5 * 60 * 60); + + let p = Parser::new("+5"); + assert_eq!(p.parse_posix_offset().unwrap().second, -5 * 60 * 60); + + let p = Parser::new("-5"); + assert_eq!(p.parse_posix_offset().unwrap().second, 5 * 60 * 60); + + let p = Parser::new("-12:34:56"); + assert_eq!( + p.parse_posix_offset().unwrap().second, + 12 * 60 * 60 + 34 * 60 + 56, + ); + + let p = Parser::new("a"); + assert!(p.parse_posix_offset().is_err()); + + let p = Parser::new("-"); + assert!(p.parse_posix_offset().is_err()); + + let p = Parser::new("+"); + assert!(p.parse_posix_offset().is_err()); + + let p = Parser::new("-a"); + assert!(p.parse_posix_offset().is_err()); + + let p = Parser::new("+a"); + assert!(p.parse_posix_offset().is_err()); + + let p = Parser::new("-25"); + assert!(p.parse_posix_offset().is_err()); + + let p = Parser::new("+25"); + assert!(p.parse_posix_offset().is_err()); + + // This checks that we don't accidentally permit IANA rules for + // offset parsing. Namely, the IANA tzfile v3+ extension only applies + // to transition times. But since POSIX says that the "time" for the + // offset and transition is the same format, it would be an easy + // implementation mistake to implement the more flexible rule for + // IANA and have it accidentally also apply to the offset. So we check + // that it doesn't here. + let p = Parser { ianav3plus: true, ..Parser::new("25") }; + assert!(p.parse_posix_offset().is_err()); + let p = Parser { ianav3plus: true, ..Parser::new("+25") }; + assert!(p.parse_posix_offset().is_err()); + let p = Parser { ianav3plus: true, ..Parser::new("-25") }; + assert!(p.parse_posix_offset().is_err()); + } + + #[test] + fn parse_rule() { + let p = Parser::new("M9.5.0,M4.1.0/3"); + assert_eq!( + p.parse_rule().unwrap(), + PosixRule { + start: PosixDayTime { + date: PosixDay::WeekdayOfMonth { + month: 9, + week: 5, + weekday: 0, + }, + time: PosixTime { second: 2 * 60 * 60 }, + }, + end: PosixDayTime { + date: PosixDay::WeekdayOfMonth { + month: 4, + week: 1, + weekday: 0, + }, + time: PosixTime { second: 3 * 60 * 60 }, + }, + }, + ); + + let p = Parser::new("M9.5.0"); + assert!(p.parse_rule().is_err()); + + let p = Parser::new(",M9.5.0,M4.1.0/3"); + assert!(p.parse_rule().is_err()); + + let p = Parser::new("M9.5.0/"); + assert!(p.parse_rule().is_err()); + + let p = Parser::new("M9.5.0,M4.1.0/"); + assert!(p.parse_rule().is_err()); + } + + #[test] + fn parse_posix_datetime() { + let p = Parser::new("J1"); + assert_eq!( + p.parse_posix_datetime().unwrap(), + PosixDayTime { + date: PosixDay::JulianOne(1), + time: PosixTime { second: 2 * 60 * 60 } + }, + ); + + let p = Parser::new("J1/3"); + assert_eq!( + p.parse_posix_datetime().unwrap(), + PosixDayTime { + date: PosixDay::JulianOne(1), + time: PosixTime { second: 3 * 60 * 60 } + }, + ); + + let p = Parser::new("M4.1.0/3"); + assert_eq!( + p.parse_posix_datetime().unwrap(), + PosixDayTime { + date: PosixDay::WeekdayOfMonth { + month: 4, + week: 1, + weekday: 0, + }, + time: PosixTime { second: 3 * 60 * 60 }, + }, + ); + + let p = Parser::new("1/3:45:05"); + assert_eq!( + p.parse_posix_datetime().unwrap(), + PosixDayTime { + date: PosixDay::JulianZero(1), + time: PosixTime { second: 3 * 60 * 60 + 45 * 60 + 5 }, + }, + ); + + let p = Parser::new("a"); + assert!(p.parse_posix_datetime().is_err()); + + let p = Parser::new("J1/"); + assert!(p.parse_posix_datetime().is_err()); + + let p = Parser::new("1/"); + assert!(p.parse_posix_datetime().is_err()); + + let p = Parser::new("M4.1.0/"); + assert!(p.parse_posix_datetime().is_err()); + } + + #[test] + fn parse_posix_date() { + let p = Parser::new("J1"); + assert_eq!(p.parse_posix_date().unwrap(), PosixDay::JulianOne(1)); + let p = Parser::new("J365"); + assert_eq!(p.parse_posix_date().unwrap(), PosixDay::JulianOne(365)); + + let p = Parser::new("0"); + assert_eq!(p.parse_posix_date().unwrap(), PosixDay::JulianZero(0)); + let p = Parser::new("1"); + assert_eq!(p.parse_posix_date().unwrap(), PosixDay::JulianZero(1)); + let p = Parser::new("365"); + assert_eq!(p.parse_posix_date().unwrap(), PosixDay::JulianZero(365)); + + let p = Parser::new("M9.5.0"); + assert_eq!( + p.parse_posix_date().unwrap(), + PosixDay::WeekdayOfMonth { month: 9, week: 5, weekday: 0 }, + ); + let p = Parser::new("M9.5.6"); + assert_eq!( + p.parse_posix_date().unwrap(), + PosixDay::WeekdayOfMonth { month: 9, week: 5, weekday: 6 }, + ); + let p = Parser::new("M09.5.6"); + assert_eq!( + p.parse_posix_date().unwrap(), + PosixDay::WeekdayOfMonth { month: 9, week: 5, weekday: 6 }, + ); + let p = Parser::new("M12.1.1"); + assert_eq!( + p.parse_posix_date().unwrap(), + PosixDay::WeekdayOfMonth { month: 12, week: 1, weekday: 1 }, + ); + + let p = Parser::new("a"); + assert!(p.parse_posix_date().is_err()); + + let p = Parser::new("j"); + assert!(p.parse_posix_date().is_err()); + + let p = Parser::new("m"); + assert!(p.parse_posix_date().is_err()); + + let p = Parser::new("n"); + assert!(p.parse_posix_date().is_err()); + + let p = Parser::new("J366"); + assert!(p.parse_posix_date().is_err()); + + let p = Parser::new("366"); + assert!(p.parse_posix_date().is_err()); + } + + #[test] + fn parse_posix_julian_day_no_leap() { + let p = Parser::new("1"); + assert_eq!(p.parse_posix_julian_day_no_leap().unwrap(), 1); + + let p = Parser::new("001"); + assert_eq!(p.parse_posix_julian_day_no_leap().unwrap(), 1); + + let p = Parser::new("365"); + assert_eq!(p.parse_posix_julian_day_no_leap().unwrap(), 365); + + let p = Parser::new("3655"); + assert_eq!(p.parse_posix_julian_day_no_leap().unwrap(), 365); + + let p = Parser::new("0"); + assert!(p.parse_posix_julian_day_no_leap().is_err()); + + let p = Parser::new("366"); + assert!(p.parse_posix_julian_day_no_leap().is_err()); + } + + #[test] + fn parse_posix_julian_day_with_leap() { + let p = Parser::new("0"); + assert_eq!(p.parse_posix_julian_day_with_leap().unwrap(), 0); + + let p = Parser::new("1"); + assert_eq!(p.parse_posix_julian_day_with_leap().unwrap(), 1); + + let p = Parser::new("001"); + assert_eq!(p.parse_posix_julian_day_with_leap().unwrap(), 1); + + let p = Parser::new("365"); + assert_eq!(p.parse_posix_julian_day_with_leap().unwrap(), 365); + + let p = Parser::new("3655"); + assert_eq!(p.parse_posix_julian_day_with_leap().unwrap(), 365); + + let p = Parser::new("366"); + assert!(p.parse_posix_julian_day_with_leap().is_err()); + } + + #[test] + fn parse_weekday_of_month() { + let p = Parser::new("9.5.0"); + assert_eq!(p.parse_weekday_of_month().unwrap(), (9, 5, 0)); + + let p = Parser::new("9.1.6"); + assert_eq!(p.parse_weekday_of_month().unwrap(), (9, 1, 6)); + + let p = Parser::new("09.1.6"); + assert_eq!(p.parse_weekday_of_month().unwrap(), (9, 1, 6)); + + let p = Parser::new("9"); + assert!(p.parse_weekday_of_month().is_err()); + + let p = Parser::new("9."); + assert!(p.parse_weekday_of_month().is_err()); + + let p = Parser::new("9.5"); + assert!(p.parse_weekday_of_month().is_err()); + + let p = Parser::new("9.5."); + assert!(p.parse_weekday_of_month().is_err()); + + let p = Parser::new("0.5.0"); + assert!(p.parse_weekday_of_month().is_err()); + + let p = Parser::new("13.5.0"); + assert!(p.parse_weekday_of_month().is_err()); + + let p = Parser::new("9.0.0"); + assert!(p.parse_weekday_of_month().is_err()); + + let p = Parser::new("9.6.0"); + assert!(p.parse_weekday_of_month().is_err()); + + let p = Parser::new("9.5.7"); + assert!(p.parse_weekday_of_month().is_err()); + } + + #[test] + fn parse_posix_time() { + let p = Parser::new("5"); + assert_eq!(p.parse_posix_time().unwrap().second, 5 * 60 * 60); + + let p = Parser::new("22"); + assert_eq!(p.parse_posix_time().unwrap().second, 22 * 60 * 60); + + let p = Parser::new("02"); + assert_eq!(p.parse_posix_time().unwrap().second, 2 * 60 * 60); + + let p = Parser::new("5:45"); + assert_eq!( + p.parse_posix_time().unwrap().second, + 5 * 60 * 60 + 45 * 60 + ); + + let p = Parser::new("5:45:12"); + assert_eq!( + p.parse_posix_time().unwrap().second, + 5 * 60 * 60 + 45 * 60 + 12 + ); + + let p = Parser::new("5:45:129"); + assert_eq!( + p.parse_posix_time().unwrap().second, + 5 * 60 * 60 + 45 * 60 + 12 + ); + + let p = Parser::new("5:45:12:"); + assert_eq!( + p.parse_posix_time().unwrap().second, + 5 * 60 * 60 + 45 * 60 + 12 + ); + + let p = Parser { ianav3plus: true, ..Parser::new("+5:45:12") }; + assert_eq!( + p.parse_posix_time().unwrap().second, + 5 * 60 * 60 + 45 * 60 + 12 + ); + + let p = Parser { ianav3plus: true, ..Parser::new("-5:45:12") }; + assert_eq!( + p.parse_posix_time().unwrap().second, + -(5 * 60 * 60 + 45 * 60 + 12) + ); + + let p = Parser { ianav3plus: true, ..Parser::new("-167:45:12") }; + assert_eq!( + p.parse_posix_time().unwrap().second, + -(167 * 60 * 60 + 45 * 60 + 12), + ); + + let p = Parser::new("25"); + assert!(p.parse_posix_time().is_err()); + + let p = Parser::new("12:2"); + assert!(p.parse_posix_time().is_err()); + + let p = Parser::new("12:"); + assert!(p.parse_posix_time().is_err()); + + let p = Parser::new("12:23:5"); + assert!(p.parse_posix_time().is_err()); + + let p = Parser::new("12:23:"); + assert!(p.parse_posix_time().is_err()); + + let p = Parser { ianav3plus: true, ..Parser::new("168") }; + assert!(p.parse_posix_time().is_err()); + + let p = Parser { ianav3plus: true, ..Parser::new("-168") }; + assert!(p.parse_posix_time().is_err()); + + let p = Parser { ianav3plus: true, ..Parser::new("+168") }; + assert!(p.parse_posix_time().is_err()); + } + + #[test] + fn parse_month() { + let p = Parser::new("1"); + assert_eq!(p.parse_month().unwrap(), 1); + + // Should this be allowed? POSIX spec is unclear. + // We allow it because our parse does stop at 2 + // digits, so this seems harmless. Namely, '001' + // results in an error. + let p = Parser::new("01"); + assert_eq!(p.parse_month().unwrap(), 1); + + let p = Parser::new("12"); + assert_eq!(p.parse_month().unwrap(), 12); + + let p = Parser::new("0"); + assert!(p.parse_month().is_err()); + + let p = Parser::new("00"); + assert!(p.parse_month().is_err()); + + let p = Parser::new("001"); + assert!(p.parse_month().is_err()); + + let p = Parser::new("13"); + assert!(p.parse_month().is_err()); + } + + #[test] + fn parse_week() { + let p = Parser::new("1"); + assert_eq!(p.parse_week().unwrap(), 1); + + let p = Parser::new("5"); + assert_eq!(p.parse_week().unwrap(), 5); + + let p = Parser::new("55"); + assert_eq!(p.parse_week().unwrap(), 5); + + let p = Parser::new("0"); + assert!(p.parse_week().is_err()); + + let p = Parser::new("6"); + assert!(p.parse_week().is_err()); + + let p = Parser::new("00"); + assert!(p.parse_week().is_err()); + + let p = Parser::new("01"); + assert!(p.parse_week().is_err()); + + let p = Parser::new("05"); + assert!(p.parse_week().is_err()); + } + + #[test] + fn parse_weekday() { + let p = Parser::new("0"); + assert_eq!(p.parse_weekday().unwrap(), 0); + + let p = Parser::new("1"); + assert_eq!(p.parse_weekday().unwrap(), 1); + + let p = Parser::new("6"); + assert_eq!(p.parse_weekday().unwrap(), 6); + + let p = Parser::new("00"); + assert_eq!(p.parse_weekday().unwrap(), 0); + + let p = Parser::new("06"); + assert_eq!(p.parse_weekday().unwrap(), 0); + + let p = Parser::new("60"); + assert_eq!(p.parse_weekday().unwrap(), 6); + + let p = Parser::new("7"); + assert!(p.parse_weekday().is_err()); + } + + #[test] + fn parse_hour_posix() { + let p = Parser::new("5"); + assert_eq!(p.parse_hour_posix().unwrap(), 5); + + let p = Parser::new("0"); + assert_eq!(p.parse_hour_posix().unwrap(), 0); + + let p = Parser::new("00"); + assert_eq!(p.parse_hour_posix().unwrap(), 0); + + let p = Parser::new("24"); + assert_eq!(p.parse_hour_posix().unwrap(), 24); + + let p = Parser::new("100"); + assert_eq!(p.parse_hour_posix().unwrap(), 10); + + let p = Parser::new("25"); + assert!(p.parse_hour_posix().is_err()); + + let p = Parser::new("99"); + assert!(p.parse_hour_posix().is_err()); + } + + #[test] + fn parse_hour_ianav3plus() { + let new = |input| Parser { ianav3plus: true, ..Parser::new(input) }; + + let p = new("5"); + assert_eq!(p.parse_hour_ianav3plus().unwrap(), 5); + + let p = new("0"); + assert_eq!(p.parse_hour_ianav3plus().unwrap(), 0); + + let p = new("00"); + assert_eq!(p.parse_hour_ianav3plus().unwrap(), 0); + + let p = new("000"); + assert_eq!(p.parse_hour_ianav3plus().unwrap(), 0); + + let p = new("24"); + assert_eq!(p.parse_hour_ianav3plus().unwrap(), 24); + + let p = new("100"); + assert_eq!(p.parse_hour_ianav3plus().unwrap(), 100); + + let p = new("1000"); + assert_eq!(p.parse_hour_ianav3plus().unwrap(), 100); + + let p = new("167"); + assert_eq!(p.parse_hour_ianav3plus().unwrap(), 167); + + let p = new("168"); + assert!(p.parse_hour_ianav3plus().is_err()); + + let p = new("999"); + assert!(p.parse_hour_ianav3plus().is_err()); + } + + #[test] + fn parse_minute() { + let p = Parser::new("00"); + assert_eq!(p.parse_minute().unwrap(), 0); + + let p = Parser::new("24"); + assert_eq!(p.parse_minute().unwrap(), 24); + + let p = Parser::new("59"); + assert_eq!(p.parse_minute().unwrap(), 59); + + let p = Parser::new("599"); + assert_eq!(p.parse_minute().unwrap(), 59); + + let p = Parser::new("0"); + assert!(p.parse_minute().is_err()); + + let p = Parser::new("1"); + assert!(p.parse_minute().is_err()); + + let p = Parser::new("9"); + assert!(p.parse_minute().is_err()); + + let p = Parser::new("60"); + assert!(p.parse_minute().is_err()); + } + + #[test] + fn parse_second() { + let p = Parser::new("00"); + assert_eq!(p.parse_second().unwrap(), 0); + + let p = Parser::new("24"); + assert_eq!(p.parse_second().unwrap(), 24); + + let p = Parser::new("59"); + assert_eq!(p.parse_second().unwrap(), 59); + + let p = Parser::new("599"); + assert_eq!(p.parse_second().unwrap(), 59); + + let p = Parser::new("0"); + assert!(p.parse_second().is_err()); + + let p = Parser::new("1"); + assert!(p.parse_second().is_err()); + + let p = Parser::new("9"); + assert!(p.parse_second().is_err()); + + let p = Parser::new("60"); + assert!(p.parse_second().is_err()); + } + + #[test] + fn parse_number_with_exactly_n_digits() { + let p = Parser::new("1"); + assert_eq!(p.parse_number_with_exactly_n_digits(1).unwrap(), 1); + + let p = Parser::new("12"); + assert_eq!(p.parse_number_with_exactly_n_digits(2).unwrap(), 12); + + let p = Parser::new("123"); + assert_eq!(p.parse_number_with_exactly_n_digits(2).unwrap(), 12); + + let p = Parser::new(""); + assert!(p.parse_number_with_exactly_n_digits(1).is_err()); + + let p = Parser::new("1"); + assert!(p.parse_number_with_exactly_n_digits(2).is_err()); + + let p = Parser::new("12"); + assert!(p.parse_number_with_exactly_n_digits(3).is_err()); + } + + #[test] + fn parse_number_with_upto_n_digits() { + let p = Parser::new("1"); + assert_eq!(p.parse_number_with_upto_n_digits(1).unwrap(), 1); + + let p = Parser::new("1"); + assert_eq!(p.parse_number_with_upto_n_digits(2).unwrap(), 1); + + let p = Parser::new("12"); + assert_eq!(p.parse_number_with_upto_n_digits(2).unwrap(), 12); + + let p = Parser::new("12"); + assert_eq!(p.parse_number_with_upto_n_digits(3).unwrap(), 12); + + let p = Parser::new("123"); + assert_eq!(p.parse_number_with_upto_n_digits(2).unwrap(), 12); + + let p = Parser::new(""); + assert!(p.parse_number_with_upto_n_digits(1).is_err()); + + let p = Parser::new("a"); + assert!(p.parse_number_with_upto_n_digits(1).is_err()); + } + + #[test] + fn to_dst_civil_datetime_utc_range() { + let tz = posix_time_zone("WART4WARST,J1/-3,J365/20"); + let dst_info = DstInfo { + // We test this in other places. It's too annoying to write this + // out here, and I didn't adopt snapshot testing until I had + // written out these tests by hand. ¯\_(ツ)_/¯ + dst: tz.dst.as_ref().unwrap(), + start: date(2024, 1, 1).at(1, 0, 0, 0), + end: date(2024, 12, 31).at(23, 0, 0, 0), + }; + assert_eq!(tz.dst_info_utc(2024), Some(dst_info)); + + let tz = posix_time_zone("WART4WARST,J1/-4,J365/21"); + let dst_info = DstInfo { + dst: tz.dst.as_ref().unwrap(), + start: date(2024, 1, 1).at(0, 0, 0, 0), + end: date(2024, 12, 31).at(23, 59, 59, 999_999_999), + }; + assert_eq!(tz.dst_info_utc(2024), Some(dst_info)); + + let tz = posix_time_zone("EST5EDT,M3.2.0,M11.1.0"); + let dst_info = DstInfo { + dst: tz.dst.as_ref().unwrap(), + start: date(2024, 3, 10).at(7, 0, 0, 0), + end: date(2024, 11, 3).at(6, 0, 0, 0), + }; + assert_eq!(tz.dst_info_utc(2024), Some(dst_info)); + } + + // See: https://github.com/BurntSushi/jiff/issues/386 + #[test] + fn regression_permanent_dst() { + let tz = posix_time_zone("XXX-2<+01>-1,0/0,J365/23"); + let dst_info = DstInfo { + dst: tz.dst.as_ref().unwrap(), + start: date(2087, 1, 1).at(0, 0, 0, 0), + end: date(2087, 12, 31).at(23, 59, 59, 999_999_999), + }; + assert_eq!(tz.dst_info_utc(2087), Some(dst_info)); + } + + #[test] + fn reasonable() { + assert!(PosixTimeZone::parse(b"EST5").is_ok()); + assert!(PosixTimeZone::parse(b"EST5EDT").is_err()); + assert!(PosixTimeZone::parse(b"EST5EDT,J1,J365").is_ok()); + + let tz = posix_time_zone("EST24EDT,J1,J365"); + assert_eq!( + tz, + PosixTimeZone { + std_abbrev: "EST".into(), + std_offset: PosixOffset { second: -24 * 60 * 60 }, + dst: Some(PosixDst { + abbrev: "EDT".into(), + offset: PosixOffset { second: -23 * 60 * 60 }, + rule: PosixRule { + start: PosixDayTime { + date: PosixDay::JulianOne(1), + time: PosixTime::DEFAULT, + }, + end: PosixDayTime { + date: PosixDay::JulianOne(365), + time: PosixTime::DEFAULT, + }, + }, + }), + }, + ); + + let tz = posix_time_zone("EST-24EDT,J1,J365"); + assert_eq!( + tz, + PosixTimeZone { + std_abbrev: "EST".into(), + std_offset: PosixOffset { second: 24 * 60 * 60 }, + dst: Some(PosixDst { + abbrev: "EDT".into(), + offset: PosixOffset { second: 25 * 60 * 60 }, + rule: PosixRule { + start: PosixDayTime { + date: PosixDay::JulianOne(1), + time: PosixTime::DEFAULT, + }, + end: PosixDayTime { + date: PosixDay::JulianOne(365), + time: PosixTime::DEFAULT, + }, + }, + }), + }, + ); + } + + #[test] + fn posix_date_time_spec_to_datetime() { + // For this test, we just keep the offset to zero to simplify things + // a bit. We get coverage for non-zero offsets in higher level tests. + let to_datetime = |daytime: &PosixDayTime, year: i16| { + daytime.to_datetime(year, IOffset::UTC) + }; + + let tz = posix_time_zone("EST5EDT,J1,J365/5:12:34"); + assert_eq!( + to_datetime(&tz.rule().start, 2023), + date(2023, 1, 1).at(2, 0, 0, 0), + ); + assert_eq!( + to_datetime(&tz.rule().end, 2023), + date(2023, 12, 31).at(5, 12, 34, 0), + ); + + let tz = posix_time_zone("EST+5EDT,M3.2.0/2,M11.1.0/2"); + assert_eq!( + to_datetime(&tz.rule().start, 2024), + date(2024, 3, 10).at(2, 0, 0, 0), + ); + assert_eq!( + to_datetime(&tz.rule().end, 2024), + date(2024, 11, 3).at(2, 0, 0, 0), + ); + + let tz = posix_time_zone("EST+5EDT,M1.1.1,M12.5.2"); + assert_eq!( + to_datetime(&tz.rule().start, 2024), + date(2024, 1, 1).at(2, 0, 0, 0), + ); + assert_eq!( + to_datetime(&tz.rule().end, 2024), + date(2024, 12, 31).at(2, 0, 0, 0), + ); + + let tz = posix_time_zone("EST5EDT,0/0,J365/25"); + assert_eq!( + to_datetime(&tz.rule().start, 2024), + date(2024, 1, 1).at(0, 0, 0, 0), + ); + assert_eq!( + to_datetime(&tz.rule().end, 2024), + date(2024, 12, 31).at(23, 59, 59, 999_999_999), + ); + + let tz = posix_time_zone("XXX3EDT4,0/0,J365/23"); + assert_eq!( + to_datetime(&tz.rule().start, 2024), + date(2024, 1, 1).at(0, 0, 0, 0), + ); + assert_eq!( + to_datetime(&tz.rule().end, 2024), + date(2024, 12, 31).at(23, 0, 0, 0), + ); + + let tz = posix_time_zone("XXX3EDT4,0/0,365"); + assert_eq!( + to_datetime(&tz.rule().end, 2023), + date(2023, 12, 31).at(23, 59, 59, 999_999_999), + ); + assert_eq!( + to_datetime(&tz.rule().end, 2024), + date(2024, 12, 31).at(2, 0, 0, 0), + ); + + let tz = posix_time_zone("XXX3EDT4,J1/-167:59:59,J365/167:59:59"); + assert_eq!( + to_datetime(&tz.rule().start, 2024), + date(2024, 1, 1).at(0, 0, 0, 0), + ); + assert_eq!( + to_datetime(&tz.rule().end, 2024), + date(2024, 12, 31).at(23, 59, 59, 999_999_999), + ); + } + + #[test] + fn posix_date_time_spec_time() { + let tz = posix_time_zone("EST5EDT,J1,J365/5:12:34"); + assert_eq!(tz.rule().start.time, PosixTime::DEFAULT); + assert_eq!( + tz.rule().end.time, + PosixTime { second: 5 * 60 * 60 + 12 * 60 + 34 }, + ); + } + + #[test] + fn posix_date_spec_to_date() { + let tz = posix_time_zone("EST+5EDT,M3.2.0/2,M11.1.0/2"); + let start = tz.rule().start.date.to_date(2023); + assert_eq!(start, Some(date(2023, 3, 12))); + let end = tz.rule().end.date.to_date(2023); + assert_eq!(end, Some(date(2023, 11, 5))); + let start = tz.rule().start.date.to_date(2024); + assert_eq!(start, Some(date(2024, 3, 10))); + let end = tz.rule().end.date.to_date(2024); + assert_eq!(end, Some(date(2024, 11, 3))); + + let tz = posix_time_zone("EST+5EDT,J60,J365"); + let start = tz.rule().start.date.to_date(2023); + assert_eq!(start, Some(date(2023, 3, 1))); + let end = tz.rule().end.date.to_date(2023); + assert_eq!(end, Some(date(2023, 12, 31))); + let start = tz.rule().start.date.to_date(2024); + assert_eq!(start, Some(date(2024, 3, 1))); + let end = tz.rule().end.date.to_date(2024); + assert_eq!(end, Some(date(2024, 12, 31))); + + let tz = posix_time_zone("EST+5EDT,59,365"); + let start = tz.rule().start.date.to_date(2023); + assert_eq!(start, Some(date(2023, 3, 1))); + let end = tz.rule().end.date.to_date(2023); + assert_eq!(end, None); + let start = tz.rule().start.date.to_date(2024); + assert_eq!(start, Some(date(2024, 2, 29))); + let end = tz.rule().end.date.to_date(2024); + assert_eq!(end, Some(date(2024, 12, 31))); + + let tz = posix_time_zone("EST+5EDT,M1.1.1,M12.5.2"); + let start = tz.rule().start.date.to_date(2024); + assert_eq!(start, Some(date(2024, 1, 1))); + let end = tz.rule().end.date.to_date(2024); + assert_eq!(end, Some(date(2024, 12, 31))); + } + + #[test] + fn posix_time_spec_to_civil_time() { + let tz = posix_time_zone("EST5EDT,J1,J365/5:12:34"); + assert_eq!( + tz.dst.as_ref().unwrap().rule.start.time.second, + 2 * 60 * 60, + ); + assert_eq!( + tz.dst.as_ref().unwrap().rule.end.time.second, + 5 * 60 * 60 + 12 * 60 + 34, + ); + + let tz = posix_time_zone("EST5EDT,J1/23:59:59,J365/24:00:00"); + assert_eq!( + tz.dst.as_ref().unwrap().rule.start.time.second, + 23 * 60 * 60 + 59 * 60 + 59, + ); + assert_eq!( + tz.dst.as_ref().unwrap().rule.end.time.second, + 24 * 60 * 60, + ); + + let tz = posix_time_zone("EST5EDT,J1/-1,J365/167:00:00"); + assert_eq!( + tz.dst.as_ref().unwrap().rule.start.time.second, + -1 * 60 * 60, + ); + assert_eq!( + tz.dst.as_ref().unwrap().rule.end.time.second, + 167 * 60 * 60, + ); + } + + #[test] + fn parse_iana() { + // Ref: https://github.com/chronotope/chrono/issues/1153 + let p = PosixTimeZone::parse(b"CRAZY5SHORT,M12.5.0/50,0/2").unwrap(); + assert_eq!( + p, + PosixTimeZone { + std_abbrev: "CRAZY".into(), + std_offset: PosixOffset { second: -5 * 60 * 60 }, + dst: Some(PosixDst { + abbrev: "SHORT".into(), + offset: PosixOffset { second: -4 * 60 * 60 }, + rule: PosixRule { + start: PosixDayTime { + date: PosixDay::WeekdayOfMonth { + month: 12, + week: 5, + weekday: 0, + }, + time: PosixTime { second: 50 * 60 * 60 }, + }, + end: PosixDayTime { + date: PosixDay::JulianZero(0), + time: PosixTime { second: 2 * 60 * 60 }, + }, + }, + }), + }, + ); + + assert!(PosixTimeZone::parse(b"America/New_York").is_err()); + assert!(PosixTimeZone::parse(b":America/New_York").is_err()); + } + + // See: https://github.com/BurntSushi/jiff/issues/407 + #[test] + fn parse_empty_is_err() { + assert!(PosixTimeZone::parse(b"").is_err()); + } + + // See: https://github.com/BurntSushi/jiff/issues/407 + #[test] + fn parse_weird_is_err() { + let s = + b"AAAAAAAAAAAAAAACAAAAAAAAAAAAQA8AACAAAAAAAAAAAAAAAAACAAAAAAAAAAA"; + assert!(PosixTimeZone::parse(s).is_err()); + + let s = + b"8"; + assert!(PosixTimeZone::parse(s).is_err()); + + let s = b"PPPPPPPPPPPPPPPPPPPPnoofPPPAAA6DaPPPPPPPPPPPPPPPPPPPPPnoofPPPPP,n"; + assert!(PosixTimeZone::parse(s).is_err()); + + let s = b"oooooooooovooooooooooooooooool9, + bytes: &[u8], + ) -> Result { + let original = bytes; + let name = name.into(); + let (header32, rest) = + Header::parse(4, bytes).map_err(TzifErrorKind::Header32)?; + let (mut tzif, rest) = if header32.version == 0 { + TzifOwned::parse32(name, header32, rest)? + } else { + TzifOwned::parse64(name, header32, rest)? + }; + tzif.fatten(); + // This should come after fattening, because fattening may add new + // transitions and we want to add civil datetimes to those. + tzif.add_civil_datetimes_to_transitions(); + tzif.verify_posix_time_zone_consistency()?; + // Compute the checksum using the entire contents of the TZif data. + let tzif_raw_len = (rest.as_ptr() as usize) + .checked_sub(original.as_ptr() as usize) + .unwrap(); + let tzif_raw_bytes = &original[..tzif_raw_len]; + tzif.fixed.checksum = super::crc32::sum(tzif_raw_bytes); + + // Shrink all of our allocs so we don't keep excess capacity around. + tzif.fixed.designations.shrink_to_fit(); + tzif.types.shrink_to_fit(); + tzif.transitions.timestamps.shrink_to_fit(); + tzif.transitions.civil_starts.shrink_to_fit(); + tzif.transitions.civil_ends.shrink_to_fit(); + tzif.transitions.infos.shrink_to_fit(); + + Ok(tzif) + } + + fn parse32<'b>( + name: Option, + header32: Header, + bytes: &'b [u8], + ) -> Result<(TzifOwned, &'b [u8]), TzifError> { + let mut tzif = TzifOwned { + fixed: TzifFixed { + name, + version: header32.version, + // filled in later + checksum: 0, + designations: String::new(), + posix_tz: None, + }, + types: vec![], + transitions: TzifTransitions { + timestamps: vec![], + civil_starts: vec![], + civil_ends: vec![], + infos: vec![], + }, + }; + let rest = tzif.parse_transitions(&header32, bytes)?; + let rest = tzif.parse_transition_types(&header32, rest)?; + let rest = tzif.parse_local_time_types(&header32, rest)?; + let rest = tzif.parse_time_zone_designations(&header32, rest)?; + let rest = tzif.parse_leap_seconds(&header32, rest)?; + let rest = tzif.parse_indicators(&header32, rest)?; + Ok((tzif, rest)) + } + + fn parse64<'b>( + name: Option, + header32: Header, + bytes: &'b [u8], + ) -> Result<(TzifOwned, &'b [u8]), TzifError> { + let (_, rest) = + try_split_at(SplitAtError::V1, bytes, header32.data_block_len()?)?; + let (header64, rest) = + Header::parse(8, rest).map_err(TzifErrorKind::Header64)?; + let mut tzif = TzifOwned { + fixed: TzifFixed { + name, + version: header64.version, + // filled in later + checksum: 0, + designations: String::new(), + posix_tz: None, + }, + types: vec![], + transitions: TzifTransitions { + timestamps: vec![], + civil_starts: vec![], + civil_ends: vec![], + infos: vec![], + }, + }; + let rest = tzif.parse_transitions(&header64, rest)?; + let rest = tzif.parse_transition_types(&header64, rest)?; + let rest = tzif.parse_local_time_types(&header64, rest)?; + let rest = tzif.parse_time_zone_designations(&header64, rest)?; + let rest = tzif.parse_leap_seconds(&header64, rest)?; + let rest = tzif.parse_indicators(&header64, rest)?; + let rest = tzif.parse_footer(&header64, rest)?; + // Note that we don't check that the TZif data is fully valid. It is + // possible for it to contain superfluous information. For example, a + // non-zero local time type that is never referenced by a transition. + Ok((tzif, rest)) + } + + fn parse_transitions<'b>( + &mut self, + header: &Header, + bytes: &'b [u8], + ) -> Result<&'b [u8], TzifError> { + let (bytes, rest) = try_split_at( + SplitAtError::TransitionTimes, + bytes, + header.transition_times_len()?, + )?; + let mut it = bytes.chunks_exact(header.time_size); + // RFC 8536 says: "If there are no transitions, local time for all + // timestamps is specified by the TZ string in the footer if present + // and nonempty; otherwise, it is specified by time type 0." + // + // RFC 8536 also says: "Local time for timestamps before the first + // transition is specified by the first time type (time type + // 0)." + // + // So if there are no transitions, pushing this dummy one will result + // in the desired behavior even when it's the only transition. + // Similarly, since this is the minimum timestamp value, it will + // trigger for any times before the first transition found in the TZif + // data. + self.transitions.add_with_type_index(TIMESTAMP_MIN, 0); + while let Some(chunk) = it.next() { + let mut timestamp = if header.is_32bit() { + i64::from(from_be_bytes_i32(chunk)) + } else { + from_be_bytes_i64(chunk) + }; + if !(TIMESTAMP_MIN <= timestamp && timestamp <= TIMESTAMP_MAX) { + // We really shouldn't error here just because the Unix + // timestamp is outside what Jiff supports. Since what Jiff + // supports is _somewhat_ arbitrary. But Jiff's supported + // range is good enough for all realistic purposes, so we + // just clamp an out-of-range Unix timestamp to the Jiff + // min or max value. + // + // This can't result in the sorting order being wrong, but + // it can result in a transition that is duplicative with + // the dummy transition we inserted above. This should be + // fine. + let clamped = timestamp.clamp(TIMESTAMP_MIN, TIMESTAMP_MAX); + timestamp = clamped; + } + self.transitions.add(timestamp); + } + assert!(it.remainder().is_empty()); + Ok(rest) + } + + fn parse_transition_types<'b>( + &mut self, + header: &Header, + bytes: &'b [u8], + ) -> Result<&'b [u8], TransitionTypeError> { + let (bytes, rest) = try_split_at( + SplitAtError::TransitionTypes, + bytes, + header.transition_types_len(), + )?; + // We skip the first transition because it is our minimum dummy + // transition. + for (transition_index, &type_index) in (1..).zip(bytes) { + if usize::from(type_index) >= header.tzh_typecnt { + return Err(TransitionTypeError::ExceedsLocalTimeTypes); + } + self.transitions.infos[transition_index].type_index = type_index; + } + Ok(rest) + } + + fn parse_local_time_types<'b>( + &mut self, + header: &Header, + bytes: &'b [u8], + ) -> Result<&'b [u8], TzifError> { + let (bytes, rest) = try_split_at( + SplitAtError::LocalTimeTypes, + bytes, + header.local_time_types_len()?, + )?; + let mut it = bytes.chunks_exact(6); + while let Some(chunk) = it.next() { + let offset = from_be_bytes_i32(&chunk[..4]); + if !(OFFSET_MIN <= offset && offset <= OFFSET_MAX) { + return Err(TzifError::from( + LocalTimeTypeError::InvalidOffset { offset }, + )); + } + let is_dst = chunk[4] == 1; + let designation = (chunk[5], chunk[5]); + self.types.push(TzifLocalTimeType { + offset, + is_dst, + designation, + indicator: TzifIndicator::LocalWall, + }); + } + assert!(it.remainder().is_empty()); + Ok(rest) + } + + fn parse_time_zone_designations<'b>( + &mut self, + header: &Header, + bytes: &'b [u8], + ) -> Result<&'b [u8], TimeZoneDesignatorError> { + let (bytes, rest) = try_split_at( + SplitAtError::TimeZoneDesignations, + bytes, + header.time_zone_designations_len(), + )?; + self.fixed.designations = String::from_utf8(bytes.to_vec()) + .map_err(|_| TimeZoneDesignatorError::InvalidUtf8)?; + // Holy hell, this is brutal. The boundary conditions are crazy. + for typ in self.types.iter_mut() { + let start = usize::from(typ.designation.0); + let suffix = self + .fixed + .designations + .get(start..) + .ok_or(TimeZoneDesignatorError::InvalidStart)?; + let len = suffix + .find('\x00') + .ok_or(TimeZoneDesignatorError::MissingNul)?; + let end = start + .checked_add(len) + .ok_or(TimeZoneDesignatorError::InvalidLength)?; + typ.designation.1 = u8::try_from(end) + .map_err(|_| TimeZoneDesignatorError::InvalidEnd)?; + } + Ok(rest) + } + + /// This parses the leap second corrections in the TZif data. + /// + /// Note that we only parse and verify them. We don't actually use them. + /// Jiff effectively ignores leap seconds. + fn parse_leap_seconds<'b>( + &mut self, + header: &Header, + bytes: &'b [u8], + ) -> Result<&'b [u8], TzifError> { + let (bytes, rest) = try_split_at( + SplitAtError::LeapSeconds, + bytes, + header.leap_second_len()?, + )?; + let chunk_len = header + .time_size + .checked_add(4) + .expect("time_size plus 4 fits in usize"); + let mut it = bytes.chunks_exact(chunk_len); + while let Some(chunk) = it.next() { + let (occur_bytes, _corr_bytes) = chunk.split_at(header.time_size); + let occur = if header.is_32bit() { + i64::from(from_be_bytes_i32(occur_bytes)) + } else { + from_be_bytes_i64(occur_bytes) + }; + if !(TIMESTAMP_MIN <= occur && occur <= TIMESTAMP_MAX) {} + } + assert!(it.remainder().is_empty()); + Ok(rest) + } + + fn parse_indicators<'b>( + &mut self, + header: &Header, + bytes: &'b [u8], + ) -> Result<&'b [u8], IndicatorError> { + let (std_wall_bytes, rest) = try_split_at( + SplitAtError::StandardWallIndicators, + bytes, + header.standard_wall_len(), + )?; + let (ut_local_bytes, rest) = try_split_at( + SplitAtError::UTLocalIndicators, + rest, + header.ut_local_len(), + )?; + if std_wall_bytes.is_empty() && !ut_local_bytes.is_empty() { + // This is a weird case, but technically possible only if all + // UT/local indicators are 0. If any are 1, then it's an error, + // because it would require the corresponding std/wall indicator + // to be 1 too. Which it can't be, because there aren't any. So + // we just check that they're all zeros. + if ut_local_bytes.iter().any(|&byte| byte != 0) { + return Err(IndicatorError::UtLocalNonZero); + } + } else if !std_wall_bytes.is_empty() && ut_local_bytes.is_empty() { + for (i, &byte) in std_wall_bytes.iter().enumerate() { + // Indexing is OK because Header guarantees that the number of + // indicators is 0 or equal to the number of types. + self.types[i].indicator = if byte == 0 { + TzifIndicator::LocalWall + } else if byte == 1 { + TzifIndicator::LocalStandard + } else { + return Err(IndicatorError::InvalidStdWallIndicator); + }; + } + } else if !std_wall_bytes.is_empty() && !ut_local_bytes.is_empty() { + assert_eq!(std_wall_bytes.len(), ut_local_bytes.len()); + let it = std_wall_bytes.iter().zip(ut_local_bytes); + for (i, (&stdwall, &utlocal)) in it.enumerate() { + // Indexing is OK because Header guarantees that the number of + // indicators is 0 or equal to the number of types. + self.types[i].indicator = match (stdwall, utlocal) { + (0, 0) => TzifIndicator::LocalWall, + (1, 0) => TzifIndicator::LocalStandard, + (1, 1) => TzifIndicator::UTStandard, + (0, 1) => { + return Err(IndicatorError::InvalidUtWallCombination); + } + _ => return Err(IndicatorError::InvalidCombination), + }; + } + } else { + // If they're both empty then we don't need to do anything. Every + // local time type record already has the correct default for this + // case set. + debug_assert!(std_wall_bytes.is_empty()); + debug_assert!(ut_local_bytes.is_empty()); + } + Ok(rest) + } + + fn parse_footer<'b>( + &mut self, + _header: &Header, + bytes: &'b [u8], + ) -> Result<&'b [u8], FooterError> { + if bytes.is_empty() { + return Err(FooterError::UnexpectedEnd); + } + if bytes[0] != b'\n' { + return Err(FooterError::MismatchEnd); + } + let bytes = &bytes[1..]; + // Only scan up to 1KB for a NUL terminator in case we somehow got + // passed a huge block of bytes. + let toscan = &bytes[..bytes.len().min(1024)]; + let nlat = toscan + .iter() + .position(|&b| b == b'\n') + .ok_or(FooterError::TerminatorNotFound)?; + let (bytes, rest) = bytes.split_at(nlat); + if !bytes.is_empty() { + // We could in theory limit TZ strings to their strict POSIX + // definition here for TZif V2, but I don't think there is any + // harm in allowing the extensions in V2 formatted TZif data. Note + // that GNU tooling allows it via the `TZ` environment variable + // even though POSIX doesn't specify it. This all seems okay to me + // because the V3+ extension is a strict superset of functionality. + let posix_tz = PosixTimeZone::parse(bytes) + .map_err(FooterError::InvalidPosixTz)?; + self.fixed.posix_tz = Some(posix_tz); + } + Ok(&rest[1..]) + } + + /// Validates that the POSIX TZ string we parsed (if one exists) is + /// consistent with the last transition in this time zone. This is + /// required by RFC 8536. + /// + /// RFC 8536 says, "If the string is nonempty and one or more + /// transitions appear in the version 2+ data, the string MUST be + /// consistent with the last version 2+ transition." + fn verify_posix_time_zone_consistency( + &self, + ) -> Result<(), InconsistentPosixTimeZoneError> { + // We need to be a little careful, since we always have at least one + // transition (accounting for the dummy `Timestamp::MIN` transition). + // So if we only have 1 transition and a POSIX TZ string, then we + // should not validate it since it's equivalent to the case of 0 + // transitions and a POSIX TZ string. + if self.transitions.timestamps.len() <= 1 { + return Ok(()); + } + let Some(ref tz) = self.fixed.posix_tz else { + return Ok(()); + }; + let last = self + .transitions + .timestamps + .last() + .expect("last transition timestamp"); + let type_index = self + .transitions + .infos + .last() + .expect("last transition info") + .type_index; + let typ = &self.types[usize::from(type_index)]; + let (ioff, abbrev, is_dst) = + tz.to_offset_info(ITimestamp::from_second(*last)); + if ioff.second != typ.offset { + return Err(InconsistentPosixTimeZoneError::Offset); + } + if is_dst != typ.is_dst { + return Err(InconsistentPosixTimeZoneError::Dst); + } + if abbrev != self.designation(&typ) { + return Err(InconsistentPosixTimeZoneError::Designation); + } + Ok(()) + } + + /// Add civil datetimes to our transitions. + /// + /// This isn't strictly necessary, but it speeds up time zone lookups when + /// the input is a civil datetime. It lets us do comparisons directly on + /// the civil datetime as given, instead of needing to convert the civil + /// datetime given to a timestamp first. (Even if we didn't do this, I + /// believe we'd still need at least one additional timestamp that is + /// offset, because TZ lookups for a civil datetime are done in local time, + /// and the timestamps in TZif data are, of course, all in UTC.) + fn add_civil_datetimes_to_transitions(&mut self) { + fn to_datetime(timestamp: i64, offset: i32) -> TzifDateTime { + let its = ITimestamp { second: timestamp, nanosecond: 0 }; + let ioff = IOffset { second: offset }; + let dt = its.to_datetime(ioff); + TzifDateTime::new( + dt.date.year, + dt.date.month, + dt.date.day, + dt.time.hour, + dt.time.minute, + dt.time.second, + ) + } + + let trans = &mut self.transitions; + // Special case the first transition, which is always a dummy + // transition establishing the lower bound. The loop below doesn't + // handle this correctly because it isn't guaranteed to get a + // `DateTime::MIN` value. + trans.infos[0].kind = TzifTransitionKind::Unambiguous; + trans.civil_starts[0] = TzifDateTime::MIN; + for i in 1..trans.timestamps.len() { + let timestamp = trans.timestamps[i]; + let offset = { + let type_index = trans.infos[i].type_index; + self.types[usize::from(type_index)].offset + }; + let prev_offset = { + let type_index = trans.infos[i.saturating_sub(1)].type_index; + self.types[usize::from(type_index)].offset + }; + + if prev_offset == offset { + // Equivalent offsets means there can never be any ambiguity. + let start = to_datetime(timestamp, prev_offset); + trans.infos[i].kind = TzifTransitionKind::Unambiguous; + trans.civil_starts[i] = start; + } else if prev_offset < offset { + // When the offset of the previous transition is less, that + // means there is some non-zero amount of time that is + // "skipped" when moving to the next transition. Thus, we have + // a gap. The start of the gap is the offset which gets us the + // earliest time, i.e., the smaller of the two offsets. + trans.infos[i].kind = TzifTransitionKind::Gap; + trans.civil_starts[i] = to_datetime(timestamp, prev_offset); + trans.civil_ends[i] = to_datetime(timestamp, offset); + } else { + // When the offset of the previous transition is greater, that + // means there is some non-zero amount of time that will be + // replayed on a wall clock in this time zone. Thus, we have + // a fold. The start of the gold is the offset which gets us + // the earliest time, i.e., the smaller of the two offsets. + assert!(prev_offset > offset); + trans.infos[i].kind = TzifTransitionKind::Fold; + trans.civil_starts[i] = to_datetime(timestamp, offset); + trans.civil_ends[i] = to_datetime(timestamp, prev_offset); + } + } + } + + /// Fatten up this TZif data with additional transitions. + /// + /// These additional transitions often make time zone lookups faster, and + /// they smooth out the performance difference between using "slim" and + /// "fat" tzdbs. + fn fatten(&mut self) { + // Note that this is a crate feature for *both* `jiff` and + // `jiff-static`. + if !cfg!(feature = "tz-fat") { + return; + } + let Some(posix_tz) = self.fixed.posix_tz.clone() else { return }; + let last = + self.transitions.timestamps.last().expect("last transition"); + let mut i = 0; + let mut prev = ITimestamp::from_second(*last); + loop { + if i > FATTEN_MAX_TRANSITIONS { + return; + } + i += 1; + prev = match self.add_transition(&posix_tz, prev) { + None => break, + Some(next) => next, + }; + } + } + + /// If there's a transition strictly after the given timestamp for the + /// given POSIX time zone, then add it to this TZif data. + fn add_transition( + &mut self, + posix_tz: &PosixTimeZone, + prev: ITimestamp, + ) -> Option { + let (its, ioff, abbrev, is_dst) = posix_tz.next_transition(prev)?; + if its.to_datetime(IOffset::UTC).date.year >= FATTEN_UP_TO_YEAR { + return None; + } + let type_index = + self.find_or_create_local_time_type(ioff, abbrev, is_dst)?; + self.transitions.add_with_type_index(its.second, type_index); + Some(its) + } + + /// Look for a local time type matching the data given. + /// + /// If one could not be found, then one is created and its index is + /// returned. + /// + /// If one could not be found and one could not be created (e.g., the index + /// would overflow `u8`), then `None` is returned. + fn find_or_create_local_time_type( + &mut self, + offset: IOffset, + abbrev: &str, + is_dst: bool, + ) -> Option { + for (i, typ) in self.types.iter().enumerate() { + if offset.second == typ.offset + && abbrev == self.designation(typ) + && is_dst == typ.is_dst + { + return u8::try_from(i).ok(); + } + } + let i = u8::try_from(self.types.len()).ok()?; + let designation = self.find_or_create_designation(abbrev)?; + self.types.push(TzifLocalTimeType { + offset: offset.second, + is_dst, + designation, + // Not really clear if this is correct, but Jiff + // ignores this anyway, so ¯\_(ツ)_/¯. + indicator: TzifIndicator::LocalWall, + }); + Some(i) + } + + /// Look for a designation (i.e., time zone abbreviation) matching the data + /// given, and return its range into `self.fixed.designations`. + /// + /// If one could not be found, then one is created and its range is + /// returned. + /// + /// If one could not be found and one could not be created (e.g., the range + /// would overflow `u8`), then `None` is returned. + fn find_or_create_designation( + &mut self, + needle: &str, + ) -> Option<(u8, u8)> { + let mut start = 0; + while let Some(offset) = self.fixed.designations[start..].find('\0') { + let end = start + offset; + let abbrev = &self.fixed.designations[start..end]; + if needle == abbrev { + return Some((start.try_into().ok()?, end.try_into().ok()?)); + } + start = end + 1; + } + + // Now we need to add a new abbreviation. This + // should generally only happen for malformed TZif + // data. i.e., TZif data with a POSIX time zone that + // contains an TZ abbreviation that isn't found in + // the TZif's designation list. + // + // And since we're guarding against malformed data, + // the designation list might not end with NUL. If + // not, add one. + if !self.fixed.designations.ends_with('\0') { + self.fixed.designations.push('\0'); + } + let start = self.fixed.designations.len(); + self.fixed.designations.push_str(needle); + self.fixed.designations.push('\0'); + let end = self.fixed.designations.len(); + Some((start.try_into().ok()?, end.try_into().ok()?)) + } + + fn designation(&self, typ: &TzifLocalTimeType) -> &str { + let range = + usize::from(typ.designation.0)..usize::from(typ.designation.1); + // OK because we verify that the designation range on every local + // time type is a valid range into `self.designations`. + &self.fixed.designations[range] + } +} + +impl TzifTransitionsOwned { + /// Add a single transition with the given timestamp. + /// + /// This also fills in the other columns (civil starts, civil ends and + /// infos) with sensible default values. It is expected that callers will + /// later fill them in. + fn add(&mut self, timestamp: i64) { + self.add_with_type_index(timestamp, 0); + } + + /// Like `TzifTransitionsOwned::add`, but let's the caller provide a type + /// index if it is known. + fn add_with_type_index(&mut self, timestamp: i64, type_index: u8) { + self.timestamps.push(timestamp); + self.civil_starts.push(TzifDateTime::ZERO); + self.civil_ends.push(TzifDateTime::ZERO); + self.infos.push(TzifTransitionInfo { + type_index, + kind: TzifTransitionKind::Unambiguous, + }); + } +} + +/// The header for a TZif formatted file. +/// +/// V2+ TZif format have two headers: one for V1 data, and then a second +/// following the V1 data block that describes another data block which uses +/// 64-bit timestamps. The two headers both have the same format and both +/// use 32-bit big-endian encoded integers. +#[derive(Debug)] +struct Header { + /// The size of the timestamps encoded in the data block. + /// + /// This is guaranteed to be either 4 (for V1) or 8 (for the 64-bit header + /// block in V2+). + time_size: usize, + /// The file format version. + /// + /// Note that this is either a NUL byte (for version 1), or an ASCII byte + /// corresponding to the version number. That is, `0x32` for `2`, `0x33` + /// for `3` or `0x34` for `4`. Note also that just because zoneinfo might + /// have been recently generated does not mean it uses the latest format + /// version. It seems like newer versions are only compiled by `zic` when + /// they are needed. For example, `America/New_York` on my system (as of + /// `2024-03-25`) has version `0x32`, but `Asia/Jerusalem` has version + /// `0x33`. + version: u8, + /// Number of UT/local indicators stored in the file. + /// + /// This is checked to be either equal to `0` or equal to `tzh_typecnt`. + tzh_ttisutcnt: usize, + /// The number of standard/wall indicators stored in the file. + /// + /// This is checked to be either equal to `0` or equal to `tzh_typecnt`. + tzh_ttisstdcnt: usize, + /// The number of leap seconds for which data entries are stored in the + /// file. + tzh_leapcnt: usize, + /// The number of transition times for which data entries are stored in + /// the file. + tzh_timecnt: usize, + /// The number of local time types for which data entries are stored in the + /// file. + /// + /// This is checked to be at least `1`. + tzh_typecnt: usize, + /// The number of bytes of time zone abbreviation strings stored in the + /// file. + /// + /// This is checked to be at least `1`. + tzh_charcnt: usize, +} + +impl Header { + /// Parse the header record from the given bytes. + /// + /// Upon success, return the header and all bytes after the header. + /// + /// The given `time_size` must be 4 or 8, corresponding to either the + /// V1 header block or the V2+ header block, respectively. + fn parse( + time_size: usize, + bytes: &[u8], + ) -> Result<(Header, &[u8]), HeaderError> { + assert!(time_size == 4 || time_size == 8, "time size must be 4 or 8"); + if bytes.len() < 44 { + return Err(HeaderError::TooShort); + } + let (magic, rest) = bytes.split_at(4); + if magic != b"TZif" { + return Err(HeaderError::MismatchMagic); + } + let (version, rest) = rest.split_at(1); + let (_reserved, rest) = rest.split_at(15); + + let (tzh_ttisutcnt_bytes, rest) = rest.split_at(4); + let (tzh_ttisstdcnt_bytes, rest) = rest.split_at(4); + let (tzh_leapcnt_bytes, rest) = rest.split_at(4); + let (tzh_timecnt_bytes, rest) = rest.split_at(4); + let (tzh_typecnt_bytes, rest) = rest.split_at(4); + let (tzh_charcnt_bytes, rest) = rest.split_at(4); + + let tzh_ttisutcnt = + from_be_bytes_u32_to_usize(tzh_ttisutcnt_bytes).map_err(|e| { + HeaderError::ParseCount { kind: CountKind::Ut, convert: e } + })?; + let tzh_ttisstdcnt = + from_be_bytes_u32_to_usize(tzh_ttisstdcnt_bytes).map_err(|e| { + HeaderError::ParseCount { kind: CountKind::Std, convert: e } + })?; + let tzh_leapcnt = + from_be_bytes_u32_to_usize(tzh_leapcnt_bytes).map_err(|e| { + HeaderError::ParseCount { kind: CountKind::Leap, convert: e } + })?; + let tzh_timecnt = + from_be_bytes_u32_to_usize(tzh_timecnt_bytes).map_err(|e| { + HeaderError::ParseCount { kind: CountKind::Time, convert: e } + })?; + let tzh_typecnt = + from_be_bytes_u32_to_usize(tzh_typecnt_bytes).map_err(|e| { + HeaderError::ParseCount { kind: CountKind::Type, convert: e } + })?; + let tzh_charcnt = + from_be_bytes_u32_to_usize(tzh_charcnt_bytes).map_err(|e| { + HeaderError::ParseCount { kind: CountKind::Char, convert: e } + })?; + + if tzh_ttisutcnt != 0 && tzh_ttisutcnt != tzh_typecnt { + return Err(HeaderError::MismatchUtType); + } + if tzh_ttisstdcnt != 0 && tzh_ttisstdcnt != tzh_typecnt { + return Err(HeaderError::MismatchStdType); + } + if tzh_typecnt < 1 { + return Err(HeaderError::ZeroType); + } + if tzh_charcnt < 1 { + return Err(HeaderError::ZeroChar); + } + + let header = Header { + time_size, + version: version[0], + tzh_ttisutcnt, + tzh_ttisstdcnt, + tzh_leapcnt, + tzh_timecnt, + tzh_typecnt, + tzh_charcnt, + }; + Ok((header, rest)) + } + + /// Returns true if this header is for a 32-bit data block. + /// + /// When false, it is guaranteed that this header is for a 64-bit data + /// block. + fn is_32bit(&self) -> bool { + self.time_size == 4 + } + + /// Returns the size of the data block, in bytes, for this header. + /// + /// This returns an error if the arithmetic required to compute the + /// length would overflow. + /// + /// This is useful for, e.g., skipping over the 32-bit V1 data block in + /// V2+ TZif formatted files. + fn data_block_len(&self) -> Result { + let a = self.transition_times_len()?; + let b = self.transition_types_len(); + let c = self.local_time_types_len()?; + let d = self.time_zone_designations_len(); + let e = self.leap_second_len()?; + let f = self.standard_wall_len(); + let g = self.ut_local_len(); + a.checked_add(b) + .and_then(|z| z.checked_add(c)) + .and_then(|z| z.checked_add(d)) + .and_then(|z| z.checked_add(e)) + .and_then(|z| z.checked_add(f)) + .and_then(|z| z.checked_add(g)) + .ok_or(HeaderError::InvalidDataBlock { version: self.version }) + } + + fn transition_times_len(&self) -> Result { + self.tzh_timecnt + .checked_mul(self.time_size) + .ok_or(HeaderError::InvalidTimeCount) + } + + fn transition_types_len(&self) -> usize { + self.tzh_timecnt + } + + fn local_time_types_len(&self) -> Result { + self.tzh_typecnt.checked_mul(6).ok_or(HeaderError::InvalidTypeCount) + } + + fn time_zone_designations_len(&self) -> usize { + self.tzh_charcnt + } + + fn leap_second_len(&self) -> Result { + let record_len = self + .time_size + .checked_add(4) + .expect("4-or-8 plus 4 always fits in usize"); + self.tzh_leapcnt + .checked_mul(record_len) + .ok_or(HeaderError::InvalidLeapSecondCount) + } + + fn standard_wall_len(&self) -> usize { + self.tzh_ttisstdcnt + } + + fn ut_local_len(&self) -> usize { + self.tzh_ttisutcnt + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub(crate) struct TzifError { + kind: TzifErrorKind, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +enum TzifErrorKind { + Footer(FooterError), + Header(HeaderError), + Header32(HeaderError), + Header64(HeaderError), + InconsistentPosixTimeZone(InconsistentPosixTimeZoneError), + Indicator(IndicatorError), + LocalTimeType(LocalTimeTypeError), + SplitAt(SplitAtError), + TimeZoneDesignator(TimeZoneDesignatorError), + TransitionType(TransitionTypeError), +} + +impl std::error::Error for TzifError {} + +impl core::fmt::Display for TzifError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + use self::TzifErrorKind::*; + match self.kind { + Footer(ref err) => { + f.write_str("invalid TZif footer: ")?; + err.fmt(f) + } + Header(ref err) => { + f.write_str("invalid TZif header: ")?; + err.fmt(f) + } + Header32(ref err) => { + f.write_str("invalid 32-bit TZif header: ")?; + err.fmt(f) + } + Header64(ref err) => { + f.write_str("invalid 64-bit TZif header: ")?; + err.fmt(f) + } + InconsistentPosixTimeZone(ref err) => { + f.write_str( + "found inconsistency with \ + POSIX time zone transition rule \ + in TZif file footer: ", + )?; + err.fmt(f) + } + Indicator(ref err) => { + f.write_str("failed to parse indicators: ")?; + err.fmt(f) + } + LocalTimeType(ref err) => { + f.write_str("failed to parse local time types: ")?; + err.fmt(f) + } + SplitAt(ref err) => err.fmt(f), + TimeZoneDesignator(ref err) => { + f.write_str("failed to parse time zone designators: ")?; + err.fmt(f) + } + TransitionType(ref err) => { + f.write_str("failed to parse time zone transition types: ")?; + err.fmt(f) + } + } + } +} + +impl From for TzifError { + fn from(kind: TzifErrorKind) -> TzifError { + TzifError { kind } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +enum TransitionTypeError { + ExceedsLocalTimeTypes, + Split(SplitAtError), +} + +impl From for TzifError { + fn from(err: TransitionTypeError) -> TzifError { + TzifErrorKind::TransitionType(err).into() + } +} + +impl From for TransitionTypeError { + fn from(err: SplitAtError) -> TransitionTypeError { + TransitionTypeError::Split(err) + } +} + +impl core::fmt::Display for TransitionTypeError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + use self::TransitionTypeError::*; + + match *self { + ExceedsLocalTimeTypes => f.write_str( + "found time zone transition type index \ + that exceeds the number of local time types", + ), + Split(ref err) => err.fmt(f), + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +enum LocalTimeTypeError { + InvalidOffset { offset: i32 }, + Split(SplitAtError), +} + +impl From for TzifError { + fn from(err: LocalTimeTypeError) -> TzifError { + TzifErrorKind::LocalTimeType(err).into() + } +} + +impl From for LocalTimeTypeError { + fn from(err: SplitAtError) -> LocalTimeTypeError { + LocalTimeTypeError::Split(err) + } +} + +impl core::fmt::Display for LocalTimeTypeError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + use self::LocalTimeTypeError::*; + + match *self { + InvalidOffset { offset } => write!( + f, + "found local time type with \ + out-of-bounds time zone offset: {offset}, \ + Jiff's allowed range is `{OFFSET_MIN}..={OFFSET_MAX}`" + ), + Split(ref err) => err.fmt(f), + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +enum TimeZoneDesignatorError { + InvalidEnd, + InvalidLength, + InvalidStart, + InvalidUtf8, + MissingNul, + Split(SplitAtError), +} + +impl From for TzifError { + fn from(err: TimeZoneDesignatorError) -> TzifError { + TzifErrorKind::TimeZoneDesignator(err).into() + } +} + +impl From for TimeZoneDesignatorError { + fn from(err: SplitAtError) -> TimeZoneDesignatorError { + TimeZoneDesignatorError::Split(err) + } +} + +impl core::fmt::Display for TimeZoneDesignatorError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + use self::TimeZoneDesignatorError::*; + + match *self { + InvalidEnd => f.write_str( + "found invalid end of time zone designator \ + for local time type", + ), + InvalidLength => f.write_str( + "found invalid length of time zone designator \ + for local time type", + ), + InvalidStart => f.write_str( + "found invalid start of time zone designator \ + for local time type", + ), + InvalidUtf8 => { + f.write_str("found invalid UTF-8 in time zone designators") + } + MissingNul => f.write_str( + "could not find NUL terminator for time zone designator", + ), + Split(ref err) => err.fmt(f), + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +enum IndicatorError { + InvalidCombination, + InvalidStdWallIndicator, + InvalidUtWallCombination, + Split(SplitAtError), + UtLocalNonZero, +} + +impl From for TzifError { + fn from(err: IndicatorError) -> TzifError { + TzifErrorKind::Indicator(err).into() + } +} + +impl From for IndicatorError { + fn from(err: SplitAtError) -> IndicatorError { + IndicatorError::Split(err) + } +} + +impl core::fmt::Display for IndicatorError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + use self::IndicatorError::*; + + match *self { + InvalidCombination => f.write_str( + "found invalid std/wall or UT/local value for \ + local time type, each must be 0 or 1", + ), + InvalidStdWallIndicator => f.write_str( + "found invalid std/wall indicator, \ + expected it to be 0 or 1", + ), + InvalidUtWallCombination => f.write_str( + "found invalid UT-wall combination for \ + local time type, only local-wall, \ + local-standard and UT-standard are allowed", + ), + Split(ref err) => err.fmt(f), + UtLocalNonZero => f.write_str( + "found non-zero UT/local indicator, \ + but all such indicators should be zero", + ), + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +enum InconsistentPosixTimeZoneError { + Designation, + Dst, + Offset, +} + +impl From for TzifError { + fn from(err: InconsistentPosixTimeZoneError) -> TzifError { + TzifErrorKind::InconsistentPosixTimeZone(err).into() + } +} + +impl core::fmt::Display for InconsistentPosixTimeZoneError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + use self::InconsistentPosixTimeZoneError::*; + + match *self { + Designation => f.write_str( + "expected last transition in TZif file to have \ + a time zone abbreviation matching the abbreviation \ + derived from the POSIX time zone transition rule", + ), + Dst => f.write_str( + "expected last transition in TZif file to have \ + a DST status matching the status derived from the \ + POSIX time zone transition rule", + ), + Offset => f.write_str( + "expected last transition in TZif file to have \ + DST offset matching the offset derived from the \ + POSIX time zone transition rule", + ), + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +enum FooterError { + InvalidPosixTz(crate::shared::posix::PosixTimeZoneError), + MismatchEnd, + TerminatorNotFound, + UnexpectedEnd, +} + +impl From for TzifError { + fn from(err: FooterError) -> TzifError { + TzifErrorKind::Footer(err).into() + } +} + +impl core::fmt::Display for FooterError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + use self::FooterError::*; + + match *self { + InvalidPosixTz(ref err) => { + f.write_str("invalid POSIX time zone transition rule")?; + core::fmt::Display::fmt(err, f) + } + MismatchEnd => f.write_str( + "expected to find `\\n` at the beginning of \ + the TZif file footer, \ + but found something else instead", + ), + TerminatorNotFound => f.write_str( + "expected to find `\\n` terminating \ + the TZif file footer, \ + but no line terminator could be found", + ), + UnexpectedEnd => f.write_str( + "expected to find `\\n` at the beginning of \ + the TZif file footer, \ + but found unexpected end of data", + ), + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +enum HeaderError { + InvalidDataBlock { version: u8 }, + InvalidLeapSecondCount, + InvalidTimeCount, + InvalidTypeCount, + MismatchMagic, + MismatchStdType, + MismatchUtType, + ParseCount { kind: CountKind, convert: U32UsizeError }, + TooShort, + ZeroChar, + ZeroType, +} + +impl From for TzifError { + fn from(err: HeaderError) -> TzifError { + TzifErrorKind::Header(err).into() + } +} + +impl core::fmt::Display for HeaderError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + use self::HeaderError::*; + + match *self { + InvalidDataBlock { version } => write!( + f, + "length of data block in V{version} TZif file is too big", + ), + InvalidLeapSecondCount => { + f.write_str("number of leap seconds is too big") + } + InvalidTimeCount => { + f.write_str("number of transition times is too big") + } + InvalidTypeCount => { + f.write_str("number of local time types is too big") + } + MismatchMagic => f.write_str("magic bytes mismatch"), + MismatchStdType => f.write_str( + "expected number of standard/wall indicators to be zero \ + or equal to the number of local time types", + ), + MismatchUtType => f.write_str( + "expected number of UT/local indicators to be zero \ + or equal to the number of local time types", + ), + ParseCount { ref kind, ref convert } => { + write!(f, "failed to parse `{kind}`: {convert}") + } + TooShort => f.write_str("too short"), + ZeroChar => f.write_str( + "expected number of time zone abbreviations fo be at least 1", + ), + ZeroType => f.write_str( + "expected number of local time types fo be at least 1", + ), + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +enum CountKind { + Ut, + Std, + Leap, + Time, + Type, + Char, +} + +impl core::fmt::Display for CountKind { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + use self::CountKind::*; + match *self { + Ut => f.write_str("tzh_ttisutcnt"), + Std => f.write_str("tzh_ttisstdcnt"), + Leap => f.write_str("tzh_leapcnt"), + Time => f.write_str("tzh_timecnt"), + Type => f.write_str("tzh_typecnt"), + Char => f.write_str("tzh_charcnt"), + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub(crate) enum SplitAtError { + V1, + LeapSeconds, + LocalTimeTypes, + StandardWallIndicators, + TimeZoneDesignations, + TransitionTimes, + TransitionTypes, + UTLocalIndicators, +} + +impl From for TzifError { + fn from(err: SplitAtError) -> TzifError { + TzifErrorKind::SplitAt(err).into() + } +} + +impl core::fmt::Display for SplitAtError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + use self::SplitAtError::*; + + f.write_str("expected bytes for '")?; + f.write_str(match *self { + V1 => "v1 TZif", + LeapSeconds => "leap seconds", + LocalTimeTypes => "local time types", + StandardWallIndicators => "standard/wall indicators", + TimeZoneDesignations => "time zone designations", + TransitionTimes => "transition times", + TransitionTypes => "transition types", + UTLocalIndicators => "UT/local indicators", + })?; + f.write_str("data block', but did not find enough bytes")?; + Ok(()) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +struct U32UsizeError; + +impl core::fmt::Display for U32UsizeError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!( + f, + "failed to parse integer because it is bigger than `{max}`", + max = usize::MAX, + ) + } +} + +/// Splits the given slice of bytes at the index given. +/// +/// If the index is out of range (greater than `bytes.len()`) then an error is +/// returned. The error message will include the `what` string given, which is +/// meant to describe the thing being split. +fn try_split_at<'b>( + what: SplitAtError, + bytes: &'b [u8], + at: usize, +) -> Result<(&'b [u8], &'b [u8]), SplitAtError> { + if at > bytes.len() { + Err(what) + } else { + Ok(bytes.split_at(at)) + } +} + +/// Interprets the given slice as an unsigned 32-bit big endian integer, +/// attempts to convert it to a `usize` and returns it. +/// +/// # Panics +/// +/// When `bytes.len() != 4`. +/// +/// # Errors +/// +/// This errors if the `u32` parsed from the given bytes cannot fit in a +/// `usize`. +fn from_be_bytes_u32_to_usize(bytes: &[u8]) -> Result { + let n = from_be_bytes_u32(bytes); + usize::try_from(n).map_err(|_| U32UsizeError) +} + +/// Interprets the given slice as an unsigned 32-bit big endian integer and +/// returns it. +/// +/// # Panics +/// +/// When `bytes.len() != 4`. +fn from_be_bytes_u32(bytes: &[u8]) -> u32 { + u32::from_be_bytes(bytes.try_into().unwrap()) +} + +/// Interprets the given slice as a signed 32-bit big endian integer and +/// returns it. +/// +/// # Panics +/// +/// When `bytes.len() != 4`. +fn from_be_bytes_i32(bytes: &[u8]) -> i32 { + i32::from_be_bytes(bytes.try_into().unwrap()) +} + +/// Interprets the given slice as a signed 64-bit big endian integer and +/// returns it. +/// +/// # Panics +/// +/// When `bytes.len() != 8`. +fn from_be_bytes_i64(bytes: &[u8]) -> i64 { + i64::from_be_bytes(bytes.try_into().unwrap()) +} diff --git a/tools/vendor/jiff-static/src/shared/util/array_str.rs b/tools/vendor/jiff-static/src/shared/util/array_str.rs new file mode 100644 index 0000000000..c160fa2be5 --- /dev/null +++ b/tools/vendor/jiff-static/src/shared/util/array_str.rs @@ -0,0 +1,211 @@ +// auto-generated by: jiff-cli generate shared + +/// A simple and not the most-efficient fixed size string on the stack. +/// +/// This supplanted some uses of `Box` for storing tiny strings in an +/// effort to reduce our dependence on dynamic memory allocation. +/// +/// Also, since it isn't needed and it lets us save on storage requirements, +/// `N` must be less than `256` (so that the length can fit in a `u8`). +#[derive(Clone, Copy, Eq, Hash, PartialEq, PartialOrd, Ord)] +#[doc(hidden)] // not part of Jiff's public API +pub struct ArrayStr { + /// The UTF-8 bytes that make up the string. + /// + /// This array---the entire array---is always valid UTF-8. And + /// the `0..self.len` sub-slice is also always valid UTF-8. + bytes: [u8; N], + /// The number of bytes used by the string in `bytes`. + /// + /// (We could technically save this byte in some cases and use a NUL + /// terminator. For example, since we don't permit NUL bytes in POSIX time + /// zone abbreviation strings, but this is simpler and only one byte and + /// generalizes. And we're not really trying to micro-optimize the storage + /// requirements when we use these array strings. Or at least, I don't know + /// of a reason to.) + len: u8, +} + +impl ArrayStr { + /// Creates a new fixed capacity string. + /// + /// If the given string exceeds `N` bytes, then this returns + /// `None`. + pub(crate) const fn new(s: &str) -> Option> { + let len = s.len(); + if len > N { + return None; + } + let mut bytes = [0; N]; + let mut i = 0; + while i < s.as_bytes().len() { + bytes[i] = s.as_bytes()[i]; + i += 1; + } + // OK because we don't ever use anything bigger than u8::MAX for `N`. + // And we probably shouldn't, because that would be a pretty chunky + // array. If such a thing is needed, please file an issue to discuss. + debug_assert!(N <= u8::MAX as usize, "size of ArrayStr is too big"); + Some(ArrayStr { bytes, len: len as u8 }) + } + + /// Returns the capacity of this array string. + pub(crate) fn capacity() -> usize { + N + } + + /// Append the bytes given to the end of this string. + /// + /// If the capacity would be exceeded, then this is a no-op and `false` + /// is returned. + pub(crate) fn push_str(&mut self, s: &str) -> bool { + let len = usize::from(self.len); + let Some(new_len) = len.checked_add(s.len()) else { return false }; + if new_len > N { + return false; + } + self.bytes[len..new_len].copy_from_slice(s.as_bytes()); + // OK because we don't ever use anything bigger than u8::MAX for `N`. + // And we probably shouldn't, because that would be a pretty chunky + // array. If such a thing is needed, please file an issue to discuss. + debug_assert!( + N <= usize::from(u8::MAX), + "size of ArrayStr is too big" + ); + self.len = u8::try_from(new_len).unwrap(); + true + } + + /// Returns this array string as a string slice. + pub(crate) fn as_str(&self) -> &str { + // OK because construction guarantees valid UTF-8. + // + // This is bullet proof enough to use unchecked `str` construction + // here, but I can't dream up of a benchmark where it matters. + core::str::from_utf8(&self.bytes[..usize::from(self.len)]).unwrap() + } +} + +/// Easy construction of `ArrayStr` from `&'static str`. +/// +/// We specifically limit to `&'static str` to approximate string literals. +/// This prevents most cases of accidentally creating a non-string literal +/// that panics if the string is too big. +/// +/// This impl primarily exists to make writing tests more convenient. +impl From<&'static str> for ArrayStr { + fn from(s: &'static str) -> ArrayStr { + ArrayStr::new(s).unwrap() + } +} + +impl PartialEq for ArrayStr { + fn eq(&self, rhs: &str) -> bool { + self.as_str() == rhs + } +} + +impl PartialEq<&str> for ArrayStr { + fn eq(&self, rhs: &&str) -> bool { + self.as_str() == *rhs + } +} + +impl PartialEq> for str { + fn eq(&self, rhs: &ArrayStr) -> bool { + self == rhs.as_str() + } +} + +impl core::fmt::Debug for ArrayStr { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + core::fmt::Debug::fmt(self.as_str(), f) + } +} + +impl core::fmt::Display for ArrayStr { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + core::fmt::Display::fmt(self.as_str(), f) + } +} + +impl core::fmt::Write for ArrayStr { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + if self.push_str(s) { + Ok(()) + } else { + Err(core::fmt::Error) + } + } +} + +impl AsRef for ArrayStr { + fn as_ref(&self) -> &str { + self.as_str() + } +} + +/// A self-imposed limit on the size of a time zone abbreviation, in bytes. +/// +/// POSIX says this: +/// +/// > Indicate no less than three, nor more than {TZNAME_MAX}, bytes that are +/// > the designation for the standard (std) or the alternative (dst -such as +/// > Daylight Savings Time) timezone. +/// +/// But it doesn't seem worth the trouble to query `TZNAME_MAX`. Interestingly, +/// IANA says: +/// +/// > are 3 or more characters specifying the standard and daylight saving time +/// > (DST) zone abbreviations +/// +/// Which implies that IANA thinks there is no limit. But that seems unwise. +/// Moreover, in practice, it seems like the `date` utility supports fairly +/// long abbreviations. On my mac (so, BSD `date` as I understand it): +/// +/// ```text +/// $ TZ=ZZZ5YYYYYYYYYYYYYYYYYYYYY date +/// Sun Mar 17 20:05:58 YYYYYYYYYYYYYYYYYYYYY 2024 +/// ``` +/// +/// And on my Linux machine (so, GNU `date`): +/// +/// ```text +/// $ TZ=ZZZ5YYYYYYYYYYYYYYYYYYYYY date +/// Sun Mar 17 08:05:36 PM YYYYYYYYYYYYYYYYYYYYY 2024 +/// ``` +/// +/// I don't know exactly what limit these programs use, but 30 seems good +/// enough? +/// +/// (Previously, I had been using 255 and stuffing the string in a `Box`. +/// But as part of work on [#168], I was looking to remove allocation from as +/// many places as possible. And this was one candidate. But making room on the +/// stack for 255 byte abbreviations seemed gratuitous. So I picked something +/// smaller. If we come across an abbreviation bigger than this max, then we'll +/// error.) +/// +/// [#168]: https://github.com/BurntSushi/jiff/issues/168 +const ABBREVIATION_MAX: usize = 30; + +/// A type alias for centralizing the definition of a time zone abbreviation. +/// +/// Basically, this creates one single coherent place where we control the +/// length of a time zone abbreviation. +#[doc(hidden)] // not part of Jiff's public API +pub type Abbreviation = ArrayStr; + +#[cfg(test)] +mod tests { + use core::fmt::Write; + + use super::*; + + #[test] + fn fmt_write() { + let mut dst = ArrayStr::<5>::new("").unwrap(); + assert!(write!(&mut dst, "abcd").is_ok()); + assert!(write!(&mut dst, "e").is_ok()); + assert!(write!(&mut dst, "f").is_err()); + } +} diff --git a/tools/vendor/jiff-static/src/shared/util/error.rs b/tools/vendor/jiff-static/src/shared/util/error.rs new file mode 100644 index 0000000000..fb329ed3b3 --- /dev/null +++ b/tools/vendor/jiff-static/src/shared/util/error.rs @@ -0,0 +1,32 @@ +// auto-generated by: jiff-cli generate shared + +macro_rules! err { + ($($tt:tt)*) => {{ + crate::shared::util::error::Error::from_args(format_args!($($tt)*)) + }} +} + +pub(crate) use err; + +/// An error that can be returned when parsing. +#[derive(Clone, Debug)] +pub struct Error { + message: alloc::boxed::Box, +} + +impl Error { + pub(crate) fn from_args<'a>(message: core::fmt::Arguments<'a>) -> Error { + { + use alloc::string::ToString; + + let message = message.to_string().into_boxed_str(); + Error { message } + } + } +} + +impl core::fmt::Display for Error { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + core::fmt::Display::fmt(&self.message, f) + } +} diff --git a/tools/vendor/jiff-static/src/shared/util/escape.rs b/tools/vendor/jiff-static/src/shared/util/escape.rs new file mode 100644 index 0000000000..e5b182ba19 --- /dev/null +++ b/tools/vendor/jiff-static/src/shared/util/escape.rs @@ -0,0 +1,125 @@ +// auto-generated by: jiff-cli generate shared + +/*! +Provides convenience routines for escaping raw bytes. + +This was copied from `regex-automata` with a few light edits. +*/ + +use super::utf8; + +/// Provides a convenient `Debug` implementation for a `u8`. +/// +/// The `Debug` impl treats the byte as an ASCII, and emits a human +/// readable representation of it. If the byte isn't ASCII, then it's +/// emitted as a hex escape sequence. +#[derive(Clone, Copy)] +pub(crate) struct Byte(pub u8); + +impl core::fmt::Display for Byte { + #[inline(never)] + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + if self.0 == b' ' { + return write!(f, " "); + } + // 10 bytes is enough for any output from ascii::escape_default. + let mut bytes = [0u8; 10]; + let mut len = 0; + for (i, mut b) in core::ascii::escape_default(self.0).enumerate() { + // capitalize \xab to \xAB + if i >= 2 && b'a' <= b && b <= b'f' { + b -= 32; + } + bytes[len] = b; + len += 1; + } + write!(f, "{}", core::str::from_utf8(&bytes[..len]).unwrap()) + } +} + +impl core::fmt::Debug for Byte { + #[inline(never)] + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "\"")?; + core::fmt::Display::fmt(self, f)?; + write!(f, "\"")?; + Ok(()) + } +} + +/// Provides a convenient `Debug` implementation for `&[u8]`. +/// +/// This generally works best when the bytes are presumed to be mostly +/// UTF-8, but will work for anything. For any bytes that aren't UTF-8, +/// they are emitted as hex escape sequences. +#[derive(Clone, Copy)] +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> core::fmt::Display for Bytes<'a> { + #[inline(never)] + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + // This is a sad re-implementation of a similar impl found in bstr. + let mut bytes = self.0; + while let Some(result) = utf8::decode(bytes) { + let ch = match result { + Ok(ch) => ch, + Err(err) => { + // The decode API guarantees `errant_bytes` is non-empty. + write!(f, r"\x{:02x}", err.as_slice()[0])?; + bytes = &bytes[1..]; + continue; + } + }; + bytes = &bytes[ch.len_utf8()..]; + match ch { + '\0' => write!(f, "\\0")?, + '\x01'..='\x7f' => { + write!(f, "{}", (ch as u8).escape_ascii())?; + } + _ => write!(f, "{}", ch.escape_debug())?, + } + } + Ok(()) + } +} + +impl<'a> core::fmt::Debug for Bytes<'a> { + #[inline(never)] + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "\"")?; + core::fmt::Display::fmt(self, f)?; + write!(f, "\"")?; + Ok(()) + } +} + +/// A helper for repeating a single byte utilizing `Byte`. +/// +/// This is limited to repeating a byte up to `u8::MAX` times in order +/// to reduce its size overhead. And in practice, Jiff just doesn't +/// need more than this (at time of writing, 2025-11-29). +pub(crate) struct RepeatByte { + pub(crate) byte: u8, + pub(crate) count: u8, +} + +impl core::fmt::Display for RepeatByte { + #[inline(never)] + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + for _ in 0..self.count { + write!(f, "{}", Byte(self.byte))?; + } + Ok(()) + } +} + +impl core::fmt::Debug for RepeatByte { + #[inline(never)] + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "\"")?; + core::fmt::Display::fmt(self, f)?; + write!(f, "\"")?; + Ok(()) + } +} + diff --git a/tools/vendor/jiff-static/src/shared/util/itime.rs b/tools/vendor/jiff-static/src/shared/util/itime.rs new file mode 100644 index 0000000000..c919a26df7 --- /dev/null +++ b/tools/vendor/jiff-static/src/shared/util/itime.rs @@ -0,0 +1,967 @@ +// auto-generated by: jiff-cli generate shared + +/*! +This module defines the internal core time data types. + +This includes physical time (i.e., a timestamp) and civil time. + +These types exist to provide a home for the core algorithms in a datetime +crate. For example, converting from a timestamp to a Gregorian calendar date +and clock time. + +These routines are specifically implemented on simple primitive integer types +and implicitly assume that the inputs are valid (i.e., within Jiff's minimum +and maximum ranges). + +These exist to provide `const` capabilities, and also to provide a small +reusable core of important algorithms that can be shared between `jiff` and +`jiff-static`. + +# Naming + +The types in this module are prefixed with letter `I` to make it clear that +they are internal types. Specifically, to distinguish them from Jiff's public +types. For example, `Date` versus `IDate`. +*/ + +#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)] +pub(crate) struct ITimestamp { + pub(crate) second: i64, + pub(crate) nanosecond: i32, +} + +impl ITimestamp { + const MIN: ITimestamp = + ITimestamp { second: -377705023201, nanosecond: 0 }; + const MAX: ITimestamp = + ITimestamp { second: 253402207200, nanosecond: 999_999_999 }; + + /// Creates an `ITimestamp` from a Unix timestamp in seconds. + #[inline] + pub(crate) const fn from_second(second: i64) -> ITimestamp { + ITimestamp { second, nanosecond: 0 } + } + + /// Converts a Unix timestamp with an offset to a Gregorian datetime. + /// + /// The offset should correspond to the number of seconds required to + /// add to this timestamp to get the local time. + #[cfg_attr(feature = "perf-inline", inline(always))] + pub(crate) const fn to_datetime(&self, offset: IOffset) -> IDateTime { + let ITimestamp { mut second, mut nanosecond } = *self; + second += offset.second as i64; + let mut epoch_day = second.div_euclid(86_400) as i32; + second = second.rem_euclid(86_400); + if nanosecond < 0 { + if second > 0 { + second -= 1; + nanosecond += 1_000_000_000; + } else { + epoch_day -= 1; + second += 86_399; + nanosecond += 1_000_000_000; + } + } + + let date = IEpochDay { epoch_day }.to_date(); + let mut time = ITimeSecond { second: second as i32 }.to_time(); + time.subsec_nanosecond = nanosecond; + IDateTime { date, time } + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)] +pub(crate) struct IOffset { + pub(crate) second: i32, +} + +impl IOffset { + pub(crate) const UTC: IOffset = IOffset { second: 0 }; +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)] +pub(crate) struct IDateTime { + pub(crate) date: IDate, + pub(crate) time: ITime, +} + +impl IDateTime { + const MIN: IDateTime = IDateTime { date: IDate::MIN, time: ITime::MIN }; + const MAX: IDateTime = IDateTime { date: IDate::MAX, time: ITime::MAX }; + + /// Converts a Gregorian datetime and its offset to a Unix timestamp. + /// + /// The offset should correspond to the number of seconds required to + /// subtract from this datetime in order to get to UTC. + #[cfg_attr(feature = "perf-inline", inline(always))] + pub(crate) fn to_timestamp(&self, offset: IOffset) -> ITimestamp { + let epoch_day = self.date.to_epoch_day().epoch_day; + let mut second = (epoch_day as i64) * 86_400 + + (self.time.to_second().second as i64); + let mut nanosecond = self.time.subsec_nanosecond; + second -= offset.second as i64; + if epoch_day < 0 && nanosecond != 0 { + second += 1; + nanosecond -= 1_000_000_000; + } + ITimestamp { second, nanosecond } + } + + /// Converts a Gregorian datetime and its offset to a Unix timestamp. + /// + /// If the timestamp would overflow Jiff's timestamp range, then this + /// returns `None`. + /// + /// The offset should correspond to the number of seconds required to + /// subtract from this datetime in order to get to UTC. + #[cfg_attr(feature = "perf-inline", inline(always))] + pub(crate) fn to_timestamp_checked( + &self, + offset: IOffset, + ) -> Option { + let ts = self.to_timestamp(offset); + if !(ITimestamp::MIN <= ts && ts <= ITimestamp::MAX) { + return None; + } + Some(ts) + } + + #[cfg_attr(feature = "perf-inline", inline(always))] + pub(crate) fn saturating_add_seconds(&self, seconds: i32) -> IDateTime { + self.checked_add_seconds(seconds).unwrap_or_else(|_| { + if seconds < 0 { + IDateTime::MIN + } else { + IDateTime::MAX + } + }) + } + + #[cfg_attr(feature = "perf-inline", inline(always))] + pub(crate) fn checked_add_seconds( + &self, + seconds: i32, + ) -> Result { + let day_second = self + .time + .to_second() + .second + .checked_add(seconds) + .ok_or_else(|| RangeError::DateTimeSeconds)?; + let days = day_second.div_euclid(86400); + let second = day_second.rem_euclid(86400); + let date = self.date.checked_add_days(days)?; + let time = ITimeSecond { second }.to_time(); + Ok(IDateTime { date, time }) + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)] +pub(crate) struct IEpochDay { + pub(crate) epoch_day: i32, +} + +impl IEpochDay { + pub(crate) const MIN: IEpochDay = IEpochDay { epoch_day: -4371587 }; + pub(crate) const MAX: IEpochDay = IEpochDay { epoch_day: 2932896 }; + + /// Converts days since the Unix epoch to a Gregorian date. + /// + /// This is Neri-Schneider. There's no branching or divisions. + /// + /// Ref: + #[cfg_attr(feature = "perf-inline", inline(always))] + #[allow(non_upper_case_globals, non_snake_case)] // to mimic source + pub(crate) const fn to_date(&self) -> IDate { + const s: u32 = 82; + const K: u32 = 719468 + 146097 * s; + const L: u32 = 400 * s; + + let N_U = self.epoch_day as u32; + let N = N_U.wrapping_add(K); + + let N_1 = 4 * N + 3; + let C = N_1 / 146097; + let N_C = (N_1 % 146097) / 4; + + let N_2 = 4 * N_C + 3; + let P_2 = 2939745 * (N_2 as u64); + let Z = (P_2 / 4294967296) as u32; + let N_Y = (P_2 % 4294967296) as u32 / 2939745 / 4; + let Y = 100 * C + Z; + + let N_3 = 2141 * N_Y + 197913; + let M = N_3 / 65536; + let D = (N_3 % 65536) / 2141; + + let J = N_Y >= 306; + let year = Y.wrapping_sub(L).wrapping_add(J as u32) as i16; + let month = (if J { M - 12 } else { M }) as i8; + let day = (D + 1) as i8; + IDate { year, month, day } + } + + /// Returns the day of the week for this epoch day. + #[cfg_attr(feature = "perf-inline", inline(always))] + pub(crate) const fn weekday(&self) -> IWeekday { + // 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 + IWeekday::from_monday_zero_offset( + (self.epoch_day + 3).rem_euclid(7) as i8 + ) + } + + /// Add the given number of days to this epoch day. + /// + /// If this would overflow an `i32` or result in an out-of-bounds epoch + /// day, then this returns an error. + #[inline] + pub(crate) fn checked_add( + &self, + amount: i32, + ) -> Result { + let epoch_day = self.epoch_day; + let sum = epoch_day + .checked_add(amount) + .ok_or_else(|| RangeError::EpochDayI32)?; + let ret = IEpochDay { epoch_day: sum }; + if !(IEpochDay::MIN <= ret && ret <= IEpochDay::MAX) { + return Err(RangeError::EpochDayDays); + } + Ok(ret) + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)] +pub(crate) struct IDate { + pub(crate) year: i16, + pub(crate) month: i8, + pub(crate) day: i8, +} + +impl IDate { + const MIN: IDate = IDate { year: -9999, month: 1, day: 1 }; + const MAX: IDate = IDate { year: 9999, month: 12, day: 31 }; + + /// Fallibly builds a new date. + /// + /// This checks that the given day is valid for the given year/month. + /// + /// No other conditions are checked. This assumes `year` and `month` are + /// valid, and that `day >= 1`. + #[inline] + pub(crate) fn try_new( + year: i16, + month: i8, + day: i8, + ) -> Result { + if day > 28 { + let max_day = days_in_month(year, month); + if day > max_day { + return Err(RangeError::DateInvalidDays { year, month }); + } + } + Ok(IDate { year, month, day }) + } + + /// Returns the date corresponding to the day of the given year. The day + /// of the year should be a value in `1..=366`, with `366` only being valid + /// if `year` is a leap year. + /// + /// This assumes that `year` is valid, but returns an error if `day` is + /// not in the range `1..=366`. + #[inline] + pub(crate) fn from_day_of_year( + year: i16, + day: i16, + ) -> Result { + if !(1 <= day && day <= 366) { + return Err(RangeError::DateInvalidDayOfYear { year }); + } + let start = IDate { year, month: 1, day: 1 }.to_epoch_day(); + let end = start + .checked_add(i32::from(day) - 1) + // This can only happen when `year=9999` and `day=366`. + .map_err(|_| RangeError::DayOfYear)? + .to_date(); + // If we overflowed into the next year, then `day` is too big. + if year != end.year { + // Can only happen given day=366 and this is a leap year. + debug_assert_eq!(day, 366); + debug_assert!(!is_leap_year(year)); + return Err(RangeError::DateInvalidDayOfYear { year }); + } + Ok(end) + } + + /// Returns the date corresponding to the day of the given year. The day + /// of the year should be a value in `1..=365`, with February 29 being + /// completely ignored. That is, it is guaranteed that February 29 will + /// never be returned by this function. It is impossible. + /// + /// This assumes that `year` is valid, but returns an error if `day` is + /// not in the range `1..=365`. + #[inline] + pub(crate) fn from_day_of_year_no_leap( + year: i16, + mut day: i16, + ) -> Result { + if !(1 <= day && day <= 365) { + return Err(RangeError::DateInvalidDayOfYearNoLeap); + } + if day >= 60 && is_leap_year(year) { + day += 1; + } + // The boundary check above guarantees this always succeeds. + Ok(IDate::from_day_of_year(year, day).unwrap()) + } + + /// Converts a Gregorian date to days since the Unix epoch. + /// + /// This is Neri-Schneider. There's no branching or divisions. + /// + /// Ref: https://github.com/cassioneri/eaf/blob/684d3cc32d14eee371d0abe4f683d6d6a49ed5c1/algorithms/neri_schneider.hpp#L83 + #[cfg_attr(feature = "perf-inline", inline(always))] + #[allow(non_upper_case_globals, non_snake_case)] // to mimic source + pub(crate) const fn to_epoch_day(&self) -> IEpochDay { + const s: u32 = 82; + const K: u32 = 719468 + 146097 * s; + const L: u32 = 400 * s; + + let year = self.year as u32; + let month = self.month as u32; + let day = self.day as u32; + + let J = month <= 2; + let Y = year.wrapping_add(L).wrapping_sub(J as u32); + let M = if J { month + 12 } else { month }; + let D = day - 1; + let C = Y / 100; + + let y_star = 1461 * Y / 4 - C + C / 4; + let m_star = (979 * M - 2919) / 32; + let N = y_star + m_star + D; + + let N_U = N.wrapping_sub(K); + let epoch_day = N_U as i32; + IEpochDay { epoch_day } + } + + /// Returns the day of the week for this date. + #[inline] + pub(crate) const fn weekday(&self) -> IWeekday { + self.to_epoch_day().weekday() + } + + /// Returns the `nth` weekday of the month represented by this date. + /// + /// `nth` must be non-zero and otherwise in the range `-5..=5`. If it + /// isn't, an error is returned. + /// + /// This also returns an error if `abs(nth)==5` and there is no "5th" + /// weekday of this month. + #[inline] + pub(crate) fn nth_weekday_of_month( + &self, + nth: i8, + weekday: IWeekday, + ) -> Result { + if nth == 0 || !(-5 <= nth && nth <= 5) { + return Err(RangeError::NthWeekdayOfMonth); + } + if nth > 0 { + let first_weekday = self.first_of_month().weekday(); + let diff = weekday.since(first_weekday); + let day = diff + 1 + (nth - 1) * 7; + IDate::try_new(self.year, self.month, day) + } else { + let last = self.last_of_month(); + let last_weekday = last.weekday(); + let diff = last_weekday.since(weekday); + let day = last.day - diff - (nth.abs() - 1) * 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(RangeError::DateInvalidDays { + year: self.year, + month: self.month, + }); + } + IDate::try_new(self.year, self.month, day) + } + } + + /// Returns the day before this date. + #[inline] + pub(crate) fn yesterday(self) -> Result { + if self.day == 1 { + if self.month == 1 { + let year = self.year - 1; + if year <= -10000 { + return Err(RangeError::Yesterday); + } + return Ok(IDate { year, month: 12, day: 31 }); + } + let month = self.month - 1; + let day = days_in_month(self.year, month); + return Ok(IDate { month, day, ..self }); + } + Ok(IDate { day: self.day - 1, ..self }) + } + + /// Returns the day after this date. + #[inline] + pub(crate) fn tomorrow(self) -> Result { + if self.day >= 28 && self.day == days_in_month(self.year, self.month) { + if self.month == 12 { + let year = self.year + 1; + if year >= 10000 { + return Err(RangeError::Tomorrow); + } + return Ok(IDate { year, month: 1, day: 1 }); + } + let month = self.month + 1; + return Ok(IDate { month, day: 1, ..self }); + } + Ok(IDate { day: self.day + 1, ..self }) + } + + /// Returns the year one year before this date. + #[inline] + pub(crate) fn prev_year(self) -> Result { + let year = self.year - 1; + if year <= -10_000 { + return Err(RangeError::YearPrevious); + } + Ok(year) + } + + /// Returns the year one year from this date. + #[inline] + pub(crate) fn next_year(self) -> Result { + let year = self.year + 1; + if year >= 10_000 { + return Err(RangeError::YearNext); + } + Ok(year) + } + + /// Add the number of days to this date. + #[inline] + pub(crate) fn checked_add_days( + &self, + amount: i32, + ) -> Result { + match amount { + 0 => Ok(*self), + -1 => self.yesterday(), + 1 => self.tomorrow(), + n => self.to_epoch_day().checked_add(n).map(|d| d.to_date()), + } + } + + #[inline] + fn first_of_month(&self) -> IDate { + IDate { day: 1, ..*self } + } + + #[inline] + fn last_of_month(&self) -> IDate { + IDate { day: days_in_month(self.year, self.month), ..*self } + } + + #[cfg(test)] + pub(crate) fn at( + &self, + hour: i8, + minute: i8, + second: i8, + subsec_nanosecond: i32, + ) -> IDateTime { + let time = ITime { hour, minute, second, subsec_nanosecond }; + IDateTime { date: *self, time } + } +} + +/// Represents a clock time. +/// +/// This uses units of hours, minutes, seconds and fractional seconds (to +/// nanosecond precision). +#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)] +pub(crate) struct ITime { + pub(crate) hour: i8, + pub(crate) minute: i8, + pub(crate) second: i8, + pub(crate) subsec_nanosecond: i32, +} + +impl ITime { + pub(crate) const ZERO: ITime = + ITime { hour: 0, minute: 0, second: 0, subsec_nanosecond: 0 }; + pub(crate) const MIN: ITime = + ITime { hour: 0, minute: 0, second: 0, subsec_nanosecond: 0 }; + pub(crate) const MAX: ITime = ITime { + hour: 23, + minute: 59, + second: 59, + subsec_nanosecond: 999_999_999, + }; + + #[cfg_attr(feature = "perf-inline", inline(always))] + pub(crate) const fn to_second(&self) -> ITimeSecond { + let mut second: i32 = 0; + second += (self.hour as i32) * 3600; + second += (self.minute as i32) * 60; + second += self.second as i32; + ITimeSecond { second } + } + + #[cfg_attr(feature = "perf-inline", inline(always))] + pub(crate) const fn to_nanosecond(&self) -> ITimeNanosecond { + let mut nanosecond: i64 = 0; + nanosecond += (self.hour as i64) * 3_600_000_000_000; + nanosecond += (self.minute as i64) * 60_000_000_000; + nanosecond += (self.second as i64) * 1_000_000_000; + nanosecond += self.subsec_nanosecond as i64; + ITimeNanosecond { nanosecond } + } +} + +/// Represents a single point in the day, to second precision. +#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)] +pub(crate) struct ITimeSecond { + pub(crate) second: i32, +} + +impl ITimeSecond { + #[cfg_attr(feature = "perf-inline", inline(always))] + pub(crate) const fn to_time(&self) -> ITime { + let mut second = self.second; + let mut time = ITime::ZERO; + if second != 0 { + time.hour = (second / 3600) as i8; + second %= 3600; + if second != 0 { + time.minute = (second / 60) as i8; + time.second = (second % 60) as i8; + } + } + time + } +} + +/// Represents a single point in the day, to nanosecond precision. +#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)] +pub(crate) struct ITimeNanosecond { + pub(crate) nanosecond: i64, +} + +impl ITimeNanosecond { + #[cfg_attr(feature = "perf-inline", inline(always))] + pub(crate) const fn to_time(&self) -> ITime { + let mut nanosecond = self.nanosecond; + let mut time = ITime::ZERO; + if nanosecond != 0 { + time.hour = (nanosecond / 3_600_000_000_000) as i8; + nanosecond %= 3_600_000_000_000; + if nanosecond != 0 { + time.minute = (nanosecond / 60_000_000_000) as i8; + nanosecond %= 60_000_000_000; + if nanosecond != 0 { + time.second = (nanosecond / 1_000_000_000) as i8; + time.subsec_nanosecond = + (nanosecond % 1_000_000_000) as i32; + } + } + } + time + } +} + +/// Represents a weekday. +#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)] +pub(crate) struct IWeekday { + /// Range is `1..=6` with `1=Monday`. + offset: i8, +} + +impl IWeekday { + /// Creates a weekday assuming the week starts on Monday and Monday is at + /// offset `0`. + #[inline] + pub(crate) const fn from_monday_zero_offset(offset: i8) -> IWeekday { + assert!(0 <= offset && offset <= 6); + IWeekday::from_monday_one_offset(offset + 1) + } + + /// Creates a weekday assuming the week starts on Monday and Monday is at + /// offset `1`. + #[inline] + pub(crate) const fn from_monday_one_offset(offset: i8) -> IWeekday { + assert!(1 <= offset && offset <= 7); + IWeekday { offset } + } + + /// Creates a weekday assuming the week starts on Sunday and Sunday is at + /// offset `0`. + #[inline] + pub(crate) const fn from_sunday_zero_offset(offset: i8) -> IWeekday { + assert!(0 <= offset && offset <= 6); + IWeekday::from_monday_zero_offset((offset - 1).rem_euclid(7)) + } + + /// Creates a weekday assuming the week starts on Sunday and Sunday is at + /// offset `1`. + #[cfg(test)] // currently dead code + #[inline] + pub(crate) const fn from_sunday_one_offset(offset: i8) -> IWeekday { + assert!(1 <= offset && offset <= 7); + IWeekday::from_sunday_zero_offset(offset - 1) + } + + /// Returns this weekday as an offset in the range `0..=6` where + /// `0=Monday`. + #[inline] + pub(crate) const fn to_monday_zero_offset(self) -> i8 { + self.to_monday_one_offset() - 1 + } + + /// Returns this weekday as an offset in the range `1..=7` where + /// `1=Monday`. + #[inline] + pub(crate) const fn to_monday_one_offset(self) -> i8 { + self.offset + } + + /// Returns this weekday as an offset in the range `0..=6` where + /// `0=Sunday`. + #[cfg(test)] // currently dead code + #[inline] + pub(crate) const fn to_sunday_zero_offset(self) -> i8 { + (self.to_monday_zero_offset() + 1) % 7 + } + + /// Returns this weekday as an offset in the range `1..=7` where + /// `1=Sunday`. + #[cfg(test)] // currently dead code + #[inline] + pub(crate) const fn to_sunday_one_offset(self) -> i8 { + self.to_sunday_zero_offset() + 1 + } + + #[inline] + pub(crate) const fn since(self, other: IWeekday) -> i8 { + (self.to_monday_zero_offset() - other.to_monday_zero_offset()) + .rem_euclid(7) + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub(crate) enum IAmbiguousOffset { + Unambiguous { offset: IOffset }, + Gap { before: IOffset, after: IOffset }, + Fold { before: IOffset, after: IOffset }, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub(crate) enum RangeError { + DateInvalidDayOfYear { year: i16 }, + DateInvalidDayOfYearNoLeap, + DateInvalidDays { year: i16, month: i8 }, + DateTimeSeconds, + DayOfYear, + EpochDayDays, + EpochDayI32, + NthWeekdayOfMonth, + Tomorrow, + YearNext, + YearPrevious, + Yesterday, +} + +impl core::fmt::Display for RangeError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + use self::RangeError::*; + + match *self { + DateInvalidDayOfYear { year } => write!( + f, + "number of days for `{year:04}` is invalid, \ + must be in range `1..={max_day}`", + max_day = days_in_year(year), + ), + DateInvalidDayOfYearNoLeap => f.write_str( + "number of days is invalid, must be in range `1..=365`", + ), + DateInvalidDays { year, month } => write!( + f, + "number of days for `{year:04}-{month:02}` is invalid, \ + must be in range `1..={max_day}`", + max_day = days_in_month(year, month), + ), + DateTimeSeconds => { + f.write_str("adding seconds to datetime overflowed") + } + DayOfYear => f.write_str("day of year is invalid"), + EpochDayDays => write!( + f, + "adding to epoch day resulted in a value outside \ + the allowed range of `{min}..={max}`", + min = IEpochDay::MIN.epoch_day, + max = IEpochDay::MAX.epoch_day, + ), + EpochDayI32 => f.write_str( + "adding to epoch day overflowed 32-bit signed integer", + ), + NthWeekdayOfMonth => f.write_str( + "invalid nth weekday of month, \ + must be non-zero and in range `-5..=5`", + ), + Tomorrow => f.write_str( + "returning tomorrow for `9999-12-31` is not \ + possible because it is greater than Jiff's supported + maximum date", + ), + YearNext => f.write_str( + "creating a date for a year following `9999` is \ + not possible because it is greater than Jiff's supported \ + maximum date", + ), + YearPrevious => f.write_str( + "creating a date for a year preceding `-9999` is \ + not possible because it is less than Jiff's supported \ + minimum date", + ), + Yesterday => f.write_str( + "returning yesterday for `-9999-01-01` is not \ + possible because it is less than Jiff's supported + minimum date", + ), + } + } +} + +/// Returns true if and only if the given year is a leap year. +/// +/// A leap year is a year with 366 days. Typical years have 365 days. +#[inline] +pub(crate) const fn is_leap_year(year: i16) -> bool { + // From: https://github.com/BurntSushi/jiff/pull/23 + let d = if year % 25 != 0 { 4 } else { 16 }; + (year % d) == 0 +} + +/// Return the number of days in the given year. +#[inline] +pub(crate) const fn days_in_year(year: i16) -> i16 { + if is_leap_year(year) { + 366 + } else { + 365 + } +} + +/// Return the number of days in the given month. +#[inline] +pub(crate) const fn days_in_month(year: i16, month: i8) -> i8 { + // From: https://github.com/BurntSushi/jiff/pull/23 + if month == 2 { + if is_leap_year(year) { + 29 + } else { + 28 + } + } else { + 30 | (month ^ month >> 3) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn roundtrip_epochday_date() { + for year in -9999..=9999 { + for month in 1..=12 { + for day in 1..=days_in_month(year, month) { + let date = IDate { year, month, day }; + let epoch_day = date.to_epoch_day(); + let date_roundtrip = epoch_day.to_date(); + assert_eq!(date, date_roundtrip); + } + } + } + } + + #[test] + fn roundtrip_second_time() { + for second in 0..=86_399 { + let second = ITimeSecond { second }; + let time = second.to_time(); + let second_roundtrip = time.to_second(); + assert_eq!(second, second_roundtrip); + } + } + + #[test] + fn roundtrip_nanosecond_time() { + for second in 0..=86_399 { + for nanosecond in + [0, 250_000_000, 500_000_000, 750_000_000, 900_000_000] + { + let nanosecond = ITimeNanosecond { + nanosecond: (second * 1_000_000_000 + nanosecond), + }; + let time = nanosecond.to_time(); + let nanosecond_roundtrip = time.to_nanosecond(); + assert_eq!(nanosecond, nanosecond_roundtrip); + } + } + } + + #[test] + fn nth_weekday() { + let d1 = IDate { year: 2017, month: 3, day: 1 }; + let wday = IWeekday::from_sunday_zero_offset(5); + let d2 = d1.nth_weekday_of_month(2, wday).unwrap(); + assert_eq!(d2, IDate { year: 2017, month: 3, day: 10 }); + + let d1 = IDate { year: 2024, month: 3, day: 1 }; + let wday = IWeekday::from_sunday_zero_offset(4); + let d2 = d1.nth_weekday_of_month(-1, wday).unwrap(); + assert_eq!(d2, IDate { year: 2024, month: 3, day: 28 }); + + let d1 = IDate { year: 2024, month: 3, day: 25 }; + let wday = IWeekday::from_sunday_zero_offset(1); + assert!(d1.nth_weekday_of_month(5, wday).is_err()); + assert!(d1.nth_weekday_of_month(-5, wday).is_err()); + + let d1 = IDate { year: 1998, month: 1, day: 1 }; + let wday = IWeekday::from_sunday_zero_offset(6); + let d2 = d1.nth_weekday_of_month(5, wday).unwrap(); + assert_eq!(d2, IDate { year: 1998, month: 1, day: 31 }); + } + + #[test] + fn weekday() { + let wday = IWeekday::from_sunday_zero_offset(0); + assert_eq!(wday.to_monday_one_offset(), 7); + + let wday = IWeekday::from_monday_one_offset(7); + assert_eq!(wday.to_sunday_zero_offset(), 0); + + let wday = IWeekday::from_sunday_one_offset(1); + assert_eq!(wday.to_monday_zero_offset(), 6); + + let wday = IWeekday::from_monday_zero_offset(6); + assert_eq!(wday.to_sunday_one_offset(), 1); + } + + #[test] + fn weekday_since() { + let wday1 = IWeekday::from_sunday_zero_offset(0); + let wday2 = IWeekday::from_sunday_zero_offset(6); + assert_eq!(wday2.since(wday1), 6); + assert_eq!(wday1.since(wday2), 1); + } + + #[test] + fn leap_year() { + assert!(!is_leap_year(1900)); + assert!(is_leap_year(2000)); + assert!(!is_leap_year(2001)); + assert!(!is_leap_year(2002)); + assert!(!is_leap_year(2003)); + assert!(is_leap_year(2004)); + } + + #[test] + fn number_of_days_in_month() { + assert_eq!(days_in_month(2024, 1), 31); + assert_eq!(days_in_month(2024, 2), 29); + assert_eq!(days_in_month(2024, 3), 31); + assert_eq!(days_in_month(2024, 4), 30); + assert_eq!(days_in_month(2024, 5), 31); + assert_eq!(days_in_month(2024, 6), 30); + assert_eq!(days_in_month(2024, 7), 31); + assert_eq!(days_in_month(2024, 8), 31); + assert_eq!(days_in_month(2024, 9), 30); + assert_eq!(days_in_month(2024, 10), 31); + assert_eq!(days_in_month(2024, 11), 30); + assert_eq!(days_in_month(2024, 12), 31); + + assert_eq!(days_in_month(2025, 1), 31); + assert_eq!(days_in_month(2025, 2), 28); + assert_eq!(days_in_month(2025, 3), 31); + assert_eq!(days_in_month(2025, 4), 30); + assert_eq!(days_in_month(2025, 5), 31); + assert_eq!(days_in_month(2025, 6), 30); + assert_eq!(days_in_month(2025, 7), 31); + assert_eq!(days_in_month(2025, 8), 31); + assert_eq!(days_in_month(2025, 9), 30); + assert_eq!(days_in_month(2025, 10), 31); + assert_eq!(days_in_month(2025, 11), 30); + assert_eq!(days_in_month(2025, 12), 31); + + assert_eq!(days_in_month(1900, 2), 28); + assert_eq!(days_in_month(2000, 2), 29); + } + + #[test] + fn yesterday() { + let d1 = IDate { year: 2025, month: 4, day: 7 }; + let d2 = d1.yesterday().unwrap(); + assert_eq!(d2, IDate { year: 2025, month: 4, day: 6 }); + + let d1 = IDate { year: 2025, month: 4, day: 1 }; + let d2 = d1.yesterday().unwrap(); + assert_eq!(d2, IDate { year: 2025, month: 3, day: 31 }); + + let d1 = IDate { year: 2025, month: 1, day: 1 }; + let d2 = d1.yesterday().unwrap(); + assert_eq!(d2, IDate { year: 2024, month: 12, day: 31 }); + + let d1 = IDate { year: -9999, month: 1, day: 1 }; + assert_eq!(d1.yesterday().ok(), None); + } + + #[test] + fn tomorrow() { + let d1 = IDate { year: 2025, month: 4, day: 7 }; + let d2 = d1.tomorrow().unwrap(); + assert_eq!(d2, IDate { year: 2025, month: 4, day: 8 }); + + let d1 = IDate { year: 2025, month: 3, day: 31 }; + let d2 = d1.tomorrow().unwrap(); + assert_eq!(d2, IDate { year: 2025, month: 4, day: 1 }); + + let d1 = IDate { year: 2025, month: 12, day: 31 }; + let d2 = d1.tomorrow().unwrap(); + assert_eq!(d2, IDate { year: 2026, month: 1, day: 1 }); + + let d1 = IDate { year: 9999, month: 12, day: 31 }; + assert_eq!(d1.tomorrow().ok(), None); + } + + #[test] + fn from_day_of_year() { + assert_eq!( + IDate::from_day_of_year(9999, 365), + Ok(IDate { year: 9999, month: 12, day: 31 }), + ); + assert_eq!( + IDate::from_day_of_year(9998, 366), + Err(RangeError::DateInvalidDayOfYear { year: 9998 }), + ); + assert_eq!( + IDate::from_day_of_year(9999, 366), + Err(RangeError::DayOfYear), + ); + } +} diff --git a/tools/vendor/jiff-static/src/shared/util/mod.rs b/tools/vendor/jiff-static/src/shared/util/mod.rs new file mode 100644 index 0000000000..3630e7321d --- /dev/null +++ b/tools/vendor/jiff-static/src/shared/util/mod.rs @@ -0,0 +1,4 @@ +// auto-generated by: jiff-cli generate shared + +pub(crate) mod array_str; +pub(crate) mod itime; diff --git a/tools/vendor/jiff-static/src/shared/util/utf8.rs b/tools/vendor/jiff-static/src/shared/util/utf8.rs new file mode 100644 index 0000000000..1738a20375 --- /dev/null +++ b/tools/vendor/jiff-static/src/shared/util/utf8.rs @@ -0,0 +1,92 @@ +// auto-generated by: jiff-cli generate shared + +/// Represents an invalid UTF-8 sequence. +/// +/// This is an error returned by `decode`. It is guaranteed to +/// contain 1, 2 or 3 bytes. +pub(crate) struct Utf8Error { + bytes: [u8; 3], + len: u8, +} + +impl Utf8Error { + #[cold] + #[inline(never)] + fn new(original_bytes: &[u8], err: core::str::Utf8Error) -> Utf8Error { + let len = err.error_len().unwrap_or_else(|| original_bytes.len()); + // OK because the biggest invalid UTF-8 + // sequence possible is 3. + debug_assert!(1 <= len && len <= 3); + let mut bytes = [0; 3]; + bytes[..len].copy_from_slice(&original_bytes[..len]); + Utf8Error { + bytes, + // OK because the biggest invalid UTF-8 + // sequence possible is 3. + len: u8::try_from(len).unwrap(), + } + } + + /// Returns the slice of invalid UTF-8 bytes. + /// + /// The slice returned is guaranteed to have length equivalent + /// to `Utf8Error::len`. + pub(crate) fn as_slice(&self) -> &[u8] { + &self.bytes[..self.len()] + } + + /// Returns the length of the invalid UTF-8 sequence found. + /// + /// This is guaranteed to be 1, 2 or 3. + pub(crate) fn len(&self) -> usize { + usize::from(self.len) + } +} + +impl core::fmt::Display for Utf8Error { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!( + f, + "found invalid UTF-8 byte {errant_bytes:?} in format \ + string (format strings must be valid UTF-8)", + errant_bytes = crate::shared::util::escape::Bytes(self.as_slice()), + ) + } +} + +/// Decodes the next UTF-8 encoded codepoint from the given byte slice. +/// +/// If no valid encoding of a codepoint exists at the beginning of the +/// given byte slice, then a 1-3 byte slice is returned (which is guaranteed +/// to be a prefix of `bytes`). That byte slice corresponds either to a single +/// invalid byte, or to a prefix of a valid UTF-8 encoding of a Unicode scalar +/// value (but which ultimately did not lead to a valid encoding). +/// +/// This returns `None` if and only if `bytes` is empty. +/// +/// This never panics. +/// +/// *WARNING*: This is not designed for performance. If you're looking for +/// a fast UTF-8 decoder, this is not it. If you feel like you need one in +/// this crate, then please file an issue and discuss your use case. +pub(crate) fn decode(bytes: &[u8]) -> Option> { + if bytes.is_empty() { + return None; + } + let string = match core::str::from_utf8(&bytes[..bytes.len().min(4)]) { + Ok(s) => s, + Err(ref err) if err.valid_up_to() > 0 => { + // OK because we just verified we have at least some + // valid UTF-8. + core::str::from_utf8(&bytes[..err.valid_up_to()]).unwrap() + } + // In this case, we want to return 1-3 bytes that make up a prefix of + // a potentially valid codepoint. + Err(err) => return Some(Err(Utf8Error::new(bytes, err))), + }; + // OK because we guaranteed above that `string` + // must be non-empty. And thus, `str::chars` must + // yield at least one Unicode scalar value. + Some(Ok(string.chars().next().unwrap())) +} + diff --git a/tools/vendor/jiff/.cargo-checksum.json b/tools/vendor/jiff/.cargo-checksum.json new file mode 100644 index 0000000000..8964a9f3f4 --- /dev/null +++ b/tools/vendor/jiff/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{".cargo_vcs_info.json":"3bcad76e2ce734f67a8e75806f7807c5c2a4ad93f99e8c7f5c9f867533a549f4","CHANGELOG.md":"4211ddda8971049768bd64da7cb2f5174a81c2039935902c15d6954bf7f309b4","COMPARE.md":"850f8fda3e7a89367289cbd815500aea6fcb435ef410b367a38071085626dcb4","COPYING":"01c266bced4a434da0051174d6bee16a4c82cf634e2679b6155d40d75012390f","Cargo.lock":"ae177d114769cf9aebca01c0cf3f1b7179b7dd7f5952400909bb50b731b30a4e","Cargo.toml":"42d6c38f9bb7f609f9699173f5333d9bdd2525b509e82070686ef7db8919afda","Cargo.toml.orig":"b4aa98266264ed4b89bb81962f644ceb996f89737e72ac992606182116f6145e","DESIGN.md":"38c9152d19b5761d468c53a91f3bb0445b499de53537bfdfa41935897a12e798","LICENSE-MIT":"0f96a83840e146e43c0ec96a22ec1f392e0680e6c1226e6f3ba87e0740af850f","PLATFORM.md":"02a7a064986fa2a4a0541dfacfb55d105340c5aebd7f1c3bdef5d60ae8778445","README.md":"2dcf41fb1f79203dcc64dcb201c7decc6b3af7b9d91491887c695e74a2750eb1","UNLICENSE":"7e12e5df4bae12cb21581ba157ced20e1986a0508dd10d0e8a4ab9a4cf94e85c","src/civil/date.rs":"777151f9f9e48e8d7eeaeb39b6e812e9d594199347702c0b7e7c8b14012a3017","src/civil/datetime.rs":"92a5cef984c89d38cd8a11e128b7af2080ecda19ec48c215c0dcb6d885eebd8e","src/civil/iso_week_date.rs":"88b4be41d6f6afe79d39e80c4383f085f3eef7408b93c98629071cfc0d5f6f32","src/civil/mod.rs":"0860304a71e1139335ba03a8e2da811ed85c8052f1ca5c391143e9341db4533e","src/civil/time.rs":"52fa1e80d6cd5f2741f1f7bfbc667449d15e8c0384ad540723e236b8bb1a1b01","src/civil/weekday.rs":"bf2646b7943d78f5970341fc098e6de894eebd35ba502cb1487165894edf3528","src/duration.rs":"5303b17ea38c7815923ed687ad832e804bbdd0959385076a9c1ef2f40b2a8c76","src/error/civil.rs":"40ff03071958d6289aef24b81e464786ba56619684b69768dc8ec72820edf7fd","src/error/duration.rs":"f7441181ba851dad753fc20fa7c8ca4b54c4b565a5d13b3070a2f9138956d88f","src/error/fmt/friendly.rs":"5e07fc237cd9addfd60692e2a931268c7c6886652db2423fb043b3de3560a7f1","src/error/fmt/mod.rs":"5ed8215ace05e01890769a8d66f548ea35cf7acd72dc72171e804a638e40c9e4","src/error/fmt/offset.rs":"6289813e420990391e345c95a65cb49bd1d5c02baecdabf78c2094b5e87f76c3","src/error/fmt/rfc2822.rs":"1f6c30365714f50b2db3495c9fa56d8bfcfeabbb5a125ebd555d743ce58036b9","src/error/fmt/rfc9557.rs":"f041a4e46881e8651c0ac4fa95c659adba753f3c8ffa1b0c4dd5ddf5c51a20a8","src/error/fmt/strtime.rs":"c4cde14a5cd003f8d223b1d18f505bec68d52fbfa865b46661661bc5c36cb066","src/error/fmt/temporal.rs":"97b09ef7c5fc80f112ef3a641868898381b6027df20fe1b2668c3cde0549d061","src/error/fmt/util.rs":"60595171152242fb544138378ee5dccea0b590b0ba74f08389ec0035d9d2a2f6","src/error/mod.rs":"500ea6fd11a29421189f8b501587d7eaddd8d5ff837d92643d66a5562bb197bd","src/error/signed_duration.rs":"b07af7f5ec26a1932b2baa7cb2f9794f95c2112f49b5a2f68da29ca7933605a5","src/error/span.rs":"6049bb49e3d4d7b5505f06983ea89d16bd7895f02668a1ef237f7bbcb45038cd","src/error/timestamp.rs":"73e4acc2e12082d580cd1d1d9b611721ca3854c737b976f4f540dc084848d3a7","src/error/tz/ambiguous.rs":"343950b71cf18772b6d892d0627237128fe557736a2a6b01b9efaf275805ba02","src/error/tz/concatenated.rs":"a073b5d9e46d898d26e661198da4c0cf4894faa4b278d453908d32d7189a4073","src/error/tz/db.rs":"7f1b850176d5093f0b6285fc8cd327f34083cd39d2140d49f2199f6f8af4b8ee","src/error/tz/mod.rs":"22ab0f7a23a828c1cc355ae6a0e6ce09f6b532e0669ad0a00ab5050ee2ce5807","src/error/tz/offset.rs":"986b032a92ba80b41917e91ae89c5d6b946818b4e29c9ed571201e645ba0a582","src/error/tz/posix.rs":"8048897f456cac4a85f06d123c937bedcdee974422c541139069188df452ed0e","src/error/tz/system.rs":"66876a02fefeeb6a750a883058a902934fcc3d9cc0f080feea66300e5231c1b5","src/error/tz/timezone.rs":"b8535574602859f97ef0fdfff530c768228ba3895a9226b81ec6552212f8aa33","src/error/tz/zic.rs":"4f8eb5c1a97e3a5f4b6f6283d83f8eac5d94638f0df0244fb548eb91b2b46a53","src/error/util.rs":"d7b7683b772f30427e9293e0060e541f756a8a0e9e93206590c09f4546a14b4a","src/error/zoned.rs":"c83ac198d6496fb63c801b2bb7ca55648c806a1e3e688f456420d4f3a6bf0193","src/fmt/buffer.rs":"81cc62b698cce06886ad2bd97a396ec93bc6fe83536b8e7a82defda5aa8d0b72","src/fmt/friendly/mod.rs":"c22216f172c5d945f19fbe60a36789d1c071b7365b88ad108a51325386564cfc","src/fmt/friendly/parser.rs":"de1a2835828e2ae4e52299bc9650e6971a0f006b55cc2097551fccbb0e6775f9","src/fmt/friendly/parser_label.rs":"e4a155bccc9968583d69773547160924813aab412b47cbd9c1e09446b0d34305","src/fmt/friendly/printer.rs":"9780b60283cf8d1fae9ede2cc914f95a650ef46a92543e2e4fbff41c9a1c4276","src/fmt/mod.rs":"b14ad051f233111a37233e9b47e63fae557175a7714f3bf94d7562e34458a31c","src/fmt/offset.rs":"af62616ebf562e560b08f4d5797f0aada71700ae2d39735d8a1576b58fbb1dff","src/fmt/rfc2822.rs":"d87cb89c07a317e1dff355890a07c4575ed0cd0ad1ca60bdad1ca31fb4fe2263","src/fmt/rfc9557.rs":"b81fac8bfc859bc1aa4c869b88b433efa355b04a56cfd8b8dcd830ec63b5d535","src/fmt/serde.rs":"293ec7cadce4c98e630e9fe8a6a9971208e678fcde094f3519f59b38a7147e53","src/fmt/strtime/format.rs":"1259c43b75c253f98459431bb840f323747151a2389f1b040e430bc983f0ef4f","src/fmt/strtime/mod.rs":"c7efbcf7c475f70f1da10c757bb8afa3f794294289f17eb4f27d6b66fcb8ebdf","src/fmt/strtime/parse.rs":"96fbbf4164cd69d8a68fdc42286e6b4bdfe06454185e8cb1a60cc133860a55c1","src/fmt/temporal/mod.rs":"9b31f9cb583984460bc59deb0da178b21079e6fae62791c9a03267b617332217","src/fmt/temporal/parser.rs":"a6bce8b9dc46c81fd74ec9088f0e22ace3e31fc483d440f153bfa4c3521de3b5","src/fmt/temporal/pieces.rs":"3691d79e148a9ac44ec9c5218de9e3ddaee65dbcd5afccba2e9fb2f11e0e130f","src/fmt/temporal/printer.rs":"f99b584ccc136b8283b1e50dd2bb427c10ff339efe964a535d2cfcfc9453368e","src/fmt/util.rs":"c59e073789b8d885c24a6ad056bef803b099358a8a3f77c0d029ddbac2359b8f","src/lib.rs":"0a6c1379d729a654d9640193b134a7bf9ac198a048eee2ac67b17735efebfd87","src/logging.rs":"95afe00674ee99240ff3e5cef6b349a1540930d59ce089a429d1d9fa0543a693","src/now.rs":"2ef0cd8830140a063a537279a7ec11b3ab9ff6883ee20edf6dc784ccac5b6dd4","src/shared/crc32/mod.rs":"398b03985ae57a2ede9f173ee3b8f0538b4c715dae98b4b5f043a0b77b86140d","src/shared/crc32/table.rs":"a8552e6af79d33cb5b59ccc078cd1139d0f981f3d27851af3918c87fd1c4df61","src/shared/mod.rs":"82f683de7e824d29091c45a9fe59be4c1d51ed5075189d0444de6adb4a8bc364","src/shared/posix.rs":"e0d99732205ad5b8e5b8c8dfaa7331c65c34ff3705492dc172e3f930b5e5adbb","src/shared/tzif.rs":"9d0f0f84222b689b33f94f6dfb507477a236737e15a394212adc284f208b23a9","src/shared/util/array_str.rs":"9e4f14875cc4e3a713731a0d79614a7dd1e21598826dc9474abd03ce9bc93ba2","src/shared/util/itime.rs":"6bb9732181436f4ba3d65c27478fed55ec9a9de92548a77a5bb55ad5330f3301","src/shared/util/mod.rs":"1c013d21d401a822d94fd2adc4bf59844ac6a84ff32a6c6958b75b5898bc803e","src/signed_duration.rs":"ce599a0de33cc3681c6afea0147c977f47877f435a3c3b3d55d500ab6f7e6d2d","src/span.rs":"44c188f33c9b993c61c499ef849328f178e1a862863b4fcf78fb38ee92f9f938","src/timestamp.rs":"506fea36c3326421fff1b35f3daadd842833d38b7ff7b2e90214f51f43200a89","src/tz/ambiguous.rs":"d3232d13f3b6a795cdc6cb2247a17c53cd2e32de4bd7789910fbdb2bc5f2d05c","src/tz/concatenated.rs":"210ca0deae397e36584acc6b13eccbe89ee1ce1f33d6506ebf6f7ed95f134650","src/tz/db/bundled/disabled.rs":"60d2c796ce12f9468d4cc1cd51713c28eb64bbe60c1a8bdc07c1db63fe82afb5","src/tz/db/bundled/enabled.rs":"8d4150a58638757feff660325c136f44983ace42d2ba5b27ef3776e6c9079039","src/tz/db/bundled/mod.rs":"be47b2a9f1047d5bf43a53d82245d40bfa7c064af133cf4f0a4cd6e430a5c049","src/tz/db/concatenated/disabled.rs":"2bb5ab1200a2fd8de977625e5ed6e6e1b70e4eba5b9f7b7754e2906ced0cd423","src/tz/db/concatenated/enabled.rs":"4825948694e5da3fdb448e672ef4f48e7374fe3dd19f6173b6f3ec70ec0e28e7","src/tz/db/concatenated/mod.rs":"6a57f4796c0a2c9b9f3b13a50050bb0b6946ce6c8590e2de5b0141076960246a","src/tz/db/mod.rs":"6c97f1a6e114d2e57e711d153d1ef0023fe67e7bc23efa4be92e390a2bb56ce7","src/tz/db/zoneinfo/disabled.rs":"aa84ebfa9fbc73c7a29600bc28ac41b5df296d6a36cbc35e0dabbf7542b89488","src/tz/db/zoneinfo/enabled.rs":"0c1d1616ac2a5fe915753661df8db70768f66ca20c0e4dfe08b99d32b37ff864","src/tz/db/zoneinfo/mod.rs":"41653636523558b0c563b6132dd05379e1ca5d77f9ca3ecf6d540ff4a4b84aa3","src/tz/mod.rs":"c25be33649c56fd2f5da9f65eb909e4c5076a833f7bb0c2e201f3cce7f8bf314","src/tz/offset.rs":"25ad99eaeb5ffc4eca2ca821cdc896adb1e44484016d9d6c2f764d774f7b75ad","src/tz/posix.rs":"16717ab6a5f806c63f3b86480a855cf961398a5df21e6dee31556c227385ba51","src/tz/system/android.rs":"79c8ce1d7b368d2ec2eb9a60e302b9d5870c5b8fd5d13f8c779ed30342479375","src/tz/system/mod.rs":"d8a8ed2964f511c3b38cbfef68763e85de6299b1ca133857c1eb1821884d6eac","src/tz/system/unix.rs":"37e440380315e922996514e1c5dbb8f298cc0a34ca8bf4617ab7f2b949f1c57b","src/tz/system/wasm_js.rs":"aac2a1853919f66a851c5dae821ed32f6b10e4a3a74c04694821db4e33d9aec4","src/tz/system/windows/mod.rs":"be38bfaad2998c8f0bff6ec124b64fc93622e110bc3293fd06238fbd158ca62a","src/tz/system/windows/windows_zones.rs":"b29a64e0677dfa6fe11f79a267d0b9401a400c6ee770a619766d78eec1c20445","src/tz/testdata.rs":"414dd0cf2a1b6cc9c28dc065a6443abdb5240a9c46d53fa6d9e8bc875430ee6d","src/tz/timezone.rs":"622a16480f7891fe5859c7718850ec407244b5096a84a643403bb0ed03730d1b","src/tz/tzif.rs":"a1af6ed688ef9ef48ccac22a9157c4f8f2f0694f57f1238cabb2d19184526cce","src/tz/zic.rs":"223f6db1b62b58f6cddc925b6ca967865dbe0e2bb94c8a51f532e93fda9f63fd","src/util/array_str.rs":"9c4ecbaa0ad3754403998e9bddeab85905c61276e9f7b1f8b83ff18f3c9b14ed","src/util/borrow.rs":"ee047a3cfb8658cf1615c0c17fd440bae1b65493e5fa26a7b689bfefc66751c1","src/util/c.rs":"7acebf39cc94dada237e64df9239d24f06ca046b2dd30cd9d6419f895d6732f3","src/util/cache.rs":"b4506e3fd63b6ce0ef2d5272ece73a71894f3c59dd952507f40d22944b9a5b51","src/util/constant.rs":"b6456804d0766792bddc64d7b46a0d083a0ae17b1b0d78c3d5a81259999837d3","src/util/escape.rs":"75c47b446f6c279459a4f6f49d547de485cb17e661419aebd21e7dcefcfb7519","src/util/fs.rs":"9f722129a91aa0d80e60c6171127d4b330cc1f88504d54e5528c4f68f1540ef6","src/util/libm.rs":"76703425eaa94482a88c11940649ae17eebb617f3af2f88b9c3c898b1a00237c","src/util/mod.rs":"7ff97e34742004d7024a1a776464450b1dceca32da5cfcb69527655f8b69f616","src/util/parse.rs":"ea0b475d0ba88fa14aa9a84a5a27c7b5ccab07497f5514d321d943490546065d","src/util/rangeint.rs":"8b9e2688fc96e6125c3f36852399b7059412283dac2c192fa526189f45eb79dc","src/util/round/increment.rs":"2d34acdcda410bb41c7c40777223d83d7e07ab3b35d5780861b69de9e82a8d26","src/util/round/mod.rs":"490e9b6127782373f189e89972e9858784408256310ecaa6109b3603d815c9c5","src/util/round/mode.rs":"43326c97f24da8025c92ff89b8b14a65e1e5cde23bbfb76788579a8b5674a526","src/util/sync.rs":"6c251c21c756851edc6bb5b4642ecea51a4730a2ea1eec9c8970ada508a215a9","src/util/t.rs":"aa0303c157efd773a4afbb3569908670d0a6e751056ed23bc6f862ac0dcce687","src/util/utf8.rs":"c86ee661e9ba9749fdb66640ffc719143e5784eaebc2df94e702c622e85c4fc9","src/zoned.rs":"04d07ba2098a49651a926c739ede70e17f3b0cb4e91135b5bdd7692859cb901d","tests/lib.rs":"0638804205e80a1815b4fe1084074259b95db019c7d8f8b29321788bd0e71c03"},"package":"e67e8da4c49d6d9909fe03361f9b620f58898859f5c7aded68351e85e71ecf50"} \ No newline at end of file diff --git a/tools/vendor/jiff/.cargo_vcs_info.json b/tools/vendor/jiff/.cargo_vcs_info.json new file mode 100644 index 0000000000..5fc921bfe6 --- /dev/null +++ b/tools/vendor/jiff/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "6508ba163bbb6f029c4f79663915ee8b135946b8" + }, + "path_in_vcs": "" +} \ No newline at end of file diff --git a/tools/vendor/jiff/CHANGELOG.md b/tools/vendor/jiff/CHANGELOG.md new file mode 100644 index 0000000000..bc08f4dd97 --- /dev/null +++ b/tools/vendor/jiff/CHANGELOG.md @@ -0,0 +1,1400 @@ +# CHANGELOG + +0.2.18 (2026-01-05) +=================== +This release ships a sizeable refactor to the RFC 2822, RFC 9110, RC +3339, RFC 9557, ISO 8601 and friendly format printers. Specifically, +they are now all monomorphic internally (instead of being generic over +`jiff::fmt::Write`) and write to uninitialized buffers. This improves +runtime performance (sometimes dramatically so), and to a more modest +degree, decreases binary size and improves compile times. + +This release also includes a bug fix where `DateTime::MIN.to_zoned(..)` +could panic. + +Enhancements: + +* [#460](https://github.com/BurntSushi/jiff/pull/460): +Improve runtime performance and binary size of RFC 2822 printer. +* [#461](https://github.com/BurntSushi/jiff/pull/461): +Tweak behavior of printing min/max offsets in RFC 2822 and Temporal printers. +* [#462](https://github.com/BurntSushi/jiff/pull/462): +Export fallible constructors for `jiff::SignedDuration`. +* [#465](https://github.com/BurntSushi/jiff/pull/465): +Improve runtime performance and binary size of the "friendly" duration printer. +* [#468](https://github.com/BurntSushi/jiff/pull/468): +Improve runtime performance and binary size of the Temporal ISO 8601 duration +printer. +* [#470](https://github.com/BurntSushi/jiff/pull/470): +Improve runtime performance and binary size of the Temporal ISO 8601 datetime +printer. +* [#474](https://github.com/BurntSushi/jiff/pull/474): +Improve runtime performance and binary size of Jiff's `strftime` +implementation. +* [#477](https://github.com/BurntSushi/jiff/pull/477): +Fix a bug where time zone lookups for `civil::DateTime::MIN` could panic. + + +0.2.17 (2025-12-24) +=================== +This release contains binary size improvements to Jiff, more succinct error +messages and some new minor APIs. + +While Jiff 1.0 is overdue, I've been doing a lot of experimenting with +improving Jiff's binary size and compile times. In particular, I want to spend +time doing this before Jiff 1.0 so that we don't box ourselves into a corner. +(For example, some binary size improvements may require minor API breaking +changes.) + +In this release, Jiff has switched to structured error handling internally +in an effort to provide error predicates and also hopefully improve binary +sizes and compile times. Overall this didn't have as big of an impact on +binary sizes or compile times as I was hoping. I did take this opportunity to +make Jiff's error messages a bit more succinct. In many cases, this involved +de-duplicating some aspects of error messages and omitting user provided input +in the messages. If you feel like there is a significant decrease in error +message quality that isn't easily amended by callers providing additional +context themselves, please open an issue. + +This release also updates Jiff's bundled copy of the [IANA Time Zone Database] +to `2025c`. See the [`2025c` release announcement] for more details. + +Enhancements: + +* [#412](https://github.com/BurntSushi/jiff/issues/412): +Add `Display`, `FromStr`, `Serialize` and `Deserialize` trait implementations +for `jiff::civil::ISOWeekDate`. These all use the ISO 8601 week date format. +* [#418](https://github.com/BurntSushi/jiff/issues/418): +Add some basic predicates to `jiff::Error` for basic error introspection. +* [#453](https://github.com/BurntSushi/jiff/pull/453), + [#454](https://github.com/BurntSushi/jiff/pull/454): +Switch to structured error handling internally. +* [#456](https://github.com/BurntSushi/jiff/pull/456), + [#457](https://github.com/BurntSushi/jiff/pull/457), + [#458](https://github.com/BurntSushi/jiff/pull/458): +Various improvements to binary size. + +[`2025c` release announcement]: https://lists.iana.org/hyperkitty/list/tz-announce@iana.org/thread/TAGXKYLMAQRZRFTERQ33CEKOW7KRJVAK/ + + +0.2.16 (2025-11-07) +=================== +This release contains a number of enhancements and bug fixes that have accrued +over the last few months. Most are small polishes. A couple of the bug fixes +apply to panics that could occur when parsing invalid `TZ` strings or invalid +`strptime` format strings. + +Also, parsing into a `Span` should now be much faster (for both the ISO 8601 +and "friendly" duration formats). + +Enhancements: + +* [#298](https://github.com/BurntSushi/jiff/issues/298): +Add Serde helpers for (de)serializing `std::time::Duration` values. +* [#396](https://github.com/BurntSushi/jiff/issues/396): +Add `Sub` and `Add` trait implementations for `Zoned` (in addition to the +already existing trait implementations for `&Zoned`). +* [#397](https://github.com/BurntSushi/jiff/pull/397): +Add `BrokenDownTime::set_meridiem` and ensure it overrides the hour when +formatting. +* [#409](https://github.com/BurntSushi/jiff/pull/409): +Switch dependency on `serde` to `serde_core`. This should help speed up +compilation times in some cases. +* [#430](https://github.com/BurntSushi/jiff/pull/430): +Add new `Zoned::series` API, making it consistent with the same API on other +datetime types. +* [#432](https://github.com/BurntSushi/jiff/pull/432): +When `lenient` mode is enabled for `strftime`, Jiff will no longer error when +the formatting string contains invalid UTF-8. +* [#432](https://github.com/BurntSushi/jiff/pull/432): +Formatting of `%y` and `%g` no longer fails based on the specific year value. +* [#432](https://github.com/BurntSushi/jiff/pull/432): +Parsing of `%s` is now a bit more consistent with other fields. Moreover, +`BrokenDownTime::{to_timestamp,to_zoned}` will now prefer timestamps parsed +with `%s` over any other fields that have been parsed. +* [#433](https://github.com/BurntSushi/jiff/pull/433): +Allow parsing just a `%s` into a `Zoned` via the `Etc/Unknown` time zone. + +Bug fixes: + +* [#386](https://github.com/BurntSushi/jiff/issues/386): +Fix a bug where `2087-12-31T23:00:00Z` in the `Africa/Casablanca` time zone +could not be round-tripped (because its offset was calculated incorrectly as +a result of not handling "permanent DST" POSIX time zones). +* [#407](https://github.com/BurntSushi/jiff/issues/407): +Fix a panic that occurred when parsing an empty string as a POSIX time zone. +* [#410](https://github.com/BurntSushi/jiff/issues/410): +Fix a panic that could occur when parsing `%:` via `strptime` APIs. +* [#414](https://github.com/BurntSushi/jiff/pull/414): +Update some parts of the documentation to indicate that `TimeZone::unknown()` +is a fallback for `TimeZone::system()` (instead of the `jiff 0.1` behavior of +using `TimeZone::UTC`). +* [#423](https://github.com/BurntSushi/jiff/issues/423): +Fix a panicking bug when reading malformed TZif data. +* [#426](https://github.com/BurntSushi/jiff/issues/426): +Fix a panicking bug when parsing century (`%C`) via `strptime`. +* [#445](https://github.com/BurntSushi/jiff/pull/445): +Fixed bugs with parsing durations like `-9223372036854775808s` +and `-PT9223372036854775808S`. + +Performance: + +* [#445](https://github.com/BurntSushi/jiff/pull/445): +Parsing into `Span` or `SignedDuration` is now a fair bit faster in some cases. + + +0.2.15 (2025-06-13) +=================== +This release fixes a bug where error values were being constructed during +parsing even in the success case. This was a regression introduced in `0.2.14` +as a result of trying to improve compilation times. Thankfully, fixing this +regression doesn't seem to meaningfully impact the amount of IR generated by +compiling Jiff. + +Bug fixes: + +* [#385](https://github.com/BurntSushi/jiff/pull/385): +Fixes a performance regression for parsing. + + +0.2.14 (2025-05-20) +=================== +This release includes a smattering of bug fixes, and hopefully a small +improvement to the time it takes to compile Jiff. Also, in this release, when +`TZ` is set to a non-empty but invalid value, Jiff will always fall back to +`Etc/Unknown` when using `TimeZone::system()`. This differs from previous +behavior where Jiff would, in this case, attempt to read the system's default +time zone. This change brings Jiff into consistency with existing Unix tooling +like `date`, and is also arguably a better failure mode. That is, when `TZ` is +set but invalid, we shouldn't silently fall back to the default system time +zone, but instead do something that indicates something has potentially gone +wrong. + +Enhancements: + +* [#364](https://github.com/BurntSushi/jiff/issues/364): +Jiff now falls back to `Etc/Unknown` for invalid `TZ` values. +* [#379](https://github.com/BurntSushi/jiff/pull/379): +Improve compilation times. + +Bug fixes: + +* [#365](https://github.com/BurntSushi/jiff/issues/365): +Fixes a compile error in Jiff when only the `tzdb-concatenated` feature was +enabled. +* [#366](https://github.com/BurntSushi/jiff/issues/366): +Fixes slow initial `Zoned::now()` in environments where `/usr/share/zoneinfo` +is on a very slow file system (like CI environments). +* [#376](https://github.com/BurntSushi/jiff/issues/376): +Avoids searching for a tzdb at `/usr/share/zoneinfo` on Windows. + + +0.2.13 (2025-05-05) +=================== +This release fixes a bug in a corner case where `TimeZone::following` could +sometimes omit the last time zone transition. This could only happen in time +zones that once had daylight saving time, but ended it. This did not have an +impact on other Jiff APIs that handle daylight saving time. + +Bug fixes: + +* [#362](https://github.com/BurntSushi/jiff/issues/362): +Fix a bug where `TimeZone::following` could omit the last historical time zone +transition. + + +0.2.12 (2025-05-03) +=================== +This release fixes a couple bugs in Jiff's parser. In particular, a regression +was introduced in `jiff 0.2.11` where its RFC 2822 parser could panic on some +inputs. Previous releases of Jiff are unaffected. + +Bug fixes: + +* [#357](https://github.com/BurntSushi/jiff/issues/357): +Fix a bug where parsing `1970-06-01T00-00:45:00[Africa/Monrovia]` succeeded +but it should fail. +* [#359](https://github.com/BurntSushi/jiff/issues/359): +Fix a bug where the RFC 2822 parser could panic on some inputs. + + +0.2.11 (2025-05-01) +=================== +This release includes new APIs for customizing Jiff's `strtime` behavior along +with a few minor bug fixes. Jiff's `strtime` formatting API has also been +optimized. It's about twice as fast as it was. + +This release also coincides with the publication of `jiff-icu 0.2.0-beta.2`, +which has support for `icu 2.0.0-beta.2`. + +Enhancements: + +* [#338](https://github.com/BurntSushi/jiff/pull/338): +Add support for the `%c`, `%r`, `%X` and `%x` conversion specifiers. +* [#341](https://github.com/BurntSushi/jiff/issues/341): +Add support for `%q` in `jiff::fmt::strtime` (prints quarter of year). +* [#342](https://github.com/BurntSushi/jiff/issues/342): +Add support for `%::z` and `%:::z` in `jiff::fmt::strtime`. +* [#344](https://github.com/BurntSushi/jiff/issues/344): +Add support for `%N` in `jiff::fmt::strtime` (alias for `%9f`). +* [#350](https://github.com/BurntSushi/jiff/issues/350): +Add a "lenient" mode for `strtime` formatting APIs that ignores most errors. + +Bug fixes: + +* [#328](https://github.com/BurntSushi/jiff/issues/328): +Document default precision behavior of `Display` impls for datetime types. +* [#340](https://github.com/BurntSushi/jiff/issues/340): +Allow whitespace in more places in RFC 2822 parser (improves spec compliance). +* [#346](https://github.com/BurntSushi/jiff/issues/346): +`TimeZone::get("UTC")` should now always return `TimeZone::UTC`. + +Performance: + +* [#338](https://github.com/BurntSushi/jiff/pull/338): +Jiff's `strftime` APIs are now approximately twice as fast as they were. +Performance should be comparable to `chrono` and `time`'s prebuilt APIs. + + +0.2.10 (2025-04-21) +=================== +This release includes a bug fix for parsing `Tuesday` when using `%A` via +Jiff's `strptime` APIs. Specifically, it would recognize `Tueday` instead of +`Tuesday`. + +Bug fixes: + +* [#333](https://github.com/BurntSushi/jiff/issues/333): +Fix typo in `strptime` parsing from `Tueday` to `Tuesday`. + + +0.2.9 (2025-04-19) +================== +This release includes a bug fix that, in debug mode, could result in datetime +types having different hashes for the same value. This could cause problems, +for example, if you are using datetimes as keys in a hash map. This problem +didn't exist when Jiff was compiled in release mode. + +This release also improves the panic message shown when the `js` feature isn't +enabled and the current time is requested on `wasm32-unknown-unknown` targets. + +Enhancements: + +* [#296](https://github.com/BurntSushi/jiff/issues/296): +Provide a better panic message when `Zoned::now()` fails on WASM. + +Bug fixes: + +* [#330](https://github.com/BurntSushi/jiff/issues/330): +Fix bug where `Hash` on datetime types could yield different hash values for +the same underlying date/time. + + +0.2.8 (2025-04-13) +================== +This release fixes a bug where the constructors on `SignedDuration` +for floating point durations could panic (in debug mode) or produce +incorrect results (in release mode). This bug only impacts users of +the `try_from_secs_{f32,f64}` and `from_secs_{f32,f64}` methods on +`SignedDuration`. + +Enhancements: + +* [#326](https://github.com/BurntSushi/jiff/pull/326): +Add an alternate `Debug` impl for `SignedDuration` that only shows its second +and nanosecond components (while using only one component when the other is +zero). + +Bug fixes: + +* [#324](https://github.com/BurntSushi/jiff/issues/324): +Fix a bug that could produce a panic or incorrect results in +`SignedDuration::(try_)?from_secs_{f32,f64}`. + + +0.2.7 (2025-04-13) +================== +This release includes a bug fix that changes how an empty but set `TZ` +environment variable is interpreted (as indistinguishable from `TZ=UTC`). +This also includes a new enabled by default create feature, `perf-inline`, +which allows toggling Jiff's use of `inline(always)`. This may help improve +compile times or decrease binary size. + +Enhancements: + +* [#320](https://github.com/BurntSushi/jiff/pull/320): +Remove some internal uses of generics to mildly improve compile times. +* [#321](https://github.com/BurntSushi/jiff/pull/321): +Add `perf-inline` crate feature for controlling `inline(always)` annotations. + +Bug fixes: + +* [#311](https://github.com/BurntSushi/jiff/issues/311): +Make `TZ=` indistinguishable from `TZ=UTC`. + + +0.2.6 (2025-04-07) +================== +This release includes a few bug fixes and support for discovering the IANA Time +Zone Database automatically on Illumos. + +Enhancements: + +* [#315](https://github.com/BurntSushi/jiff/issues/315): +Add support for automatically finding the tzdb on Illumos. + +Bug fixes: + +* [#305](https://github.com/BurntSushi/jiff/issues/305): +Fixed `Zoned` rounding on days with DST time zone transitions. +* [#309](https://github.com/BurntSushi/jiff/issues/309): +Fixed bug where `TimeZone::preceding` could omit historical time zone +transitions for time zones that have eliminated DST in the present. +* [#312](https://github.com/BurntSushi/jiff/issues/312): +Fixed `nth_weekday_in_month`, where it would sometimes incorrectly return an +error. + + +0.2.5 (2025-03-22) +================== +This release updates Jiff's bundled copy of the [IANA Time Zone Database] to +`2025b`. See the [`2025b` release announcement] for more details. + +Enhancements: + +* [#300](https://github.com/BurntSushi/jiff/pull/300): +Update `jiff-tzdb` to `2025b`. + +[`2025b` release announcement]: https://lists.iana.org/hyperkitty/list/tz-announce@iana.org/thread/6JVHNHLB6I2WAYTQ75L6KEPEQHFXAJK3/ + + +0.2.4 (2025-03-10) +================== +This is another small release that fixes a problem where Jiff could break +builds if they relied on inference for integer comparisons. Specifically, Jiff +uses internal trait impls to make comparing its internal ranged integers +more convenient. But Rust the language has no concept of "internal" trait +impls, and thus this can impact type inference. If code was written in a way +that relies on a singular trait impl that is available, then adding Jiff to +the project can cause it to break. + +This isn't arguably Jiff's fault per se, but since these trait impls were just +about internal convenience and not essential to Jiff's design, we adopt a +pragmatic approach and just remove them. + +Bug fixes: + +* [#293](https://github.com/BurntSushi/jiff/issues/293): +Remove internal trait impls that can cause breaks due to inference failures. + + +0.2.3 (2025-03-07) +================== +This is a small release that fixes a bug in the handling of POSIX time zones +in some cases. Specifically, the implementation of `Date::yesterday` was wrong +when the date was the first of the month. This was a regression introduced in +`0.2.2` and was not present in older releases. More test coverage has been +added. + +Bug fixes: + +* [#290](https://github.com/BurntSushi/jiff/issues/290): +Fix bug in implementation of `Date::yesterday`. + + +0.2.2 (2025-03-06) +================== +This release of Jiff includes a new opt-in proc macro for embedding a +`TimeZone` into your binary. Just enable Jiff's `static` feature, and this will +print the current time in the `America/New_York` time zone: + +```rust +use jiff::{ + tz::{self, TimeZone}, + Timestamp, +}; + +fn main() { + static TZ: TimeZone = tz::get!("America/New_York"); + let zdt = Timestamp::now().to_zoned(TZ.clone()); + println!("{zdt}"); +} +``` + +This enables `TimeZone` to be meaningfully used in core-only environments, +even when dynamic memory allocation isn't available. + +This release also features a number of performance improvements for time zone +lookups. In some cases, the improvement is significant (by an order of +magnitude). + +Additionally, the IANA Time Zone Database embedded into `jiff-tzdb` now +uses "rearguard" semantics. This means that the boolean flag indicating +whether daylight saving time is active or not (only accessible via +`TimeZone::to_offset_info`) will respect the actual definition of +daylight saving time. (This is relevant, for example, for time zones like +`Europe/Dublin`, where their summer time is _legally_ known as their standard +time, but is in effect daylight saving time.) + +Enhancements: + +* [#256](https://github.com/BurntSushi/jiff/issues/256): +Add `tz::{get,include}` macros for time zone support in core-only environments. +* [#258](https://github.com/BurntSushi/jiff/issues/258): +Switch to `rearguard` tzdb data in `jiff-tzdb` and document it. +* [#259](https://github.com/BurntSushi/jiff/pull/259): +De-duplicate TZif data in `jiff-tzdb`. +* [#273](https://github.com/BurntSushi/jiff/issues/273): +Add "crate features" documentation to `jiff-sqlx` and `jiff-diesel`. +* [#277](https://github.com/BurntSushi/jiff/issues/277): +Document semver guarantee for error conditions on `Timestamp` constructors. +* [#277](https://github.com/BurntSushi/jiff/pull/287): +Greatly optimize time zone lookups (for both timestamps and civil datetimes). + +Bug fixes: + +* [#261](https://github.com/BurntSushi/jiff/issues/261): +Improve the documentation for `ZonedWith::nanosecond` and +`ZonedWith::subsec_nanosecond`. + + +0.2.1 (2025-02-16) +================== +This release includes a massive number of optimizations that significantly +improves performance in some cases. If you had a workload whose performance +with Jiff was underwhelming, please give it a try. I welcome questions via +[Discussions on GitHub]. + +This release also provides a new API, `Timestamp::constant`, for constructing +`Timestamp` values in a `const` context. + +Enhancements: + +* [#235](https://github.com/BurntSushi/jiff/discussions/235), + [#255](https://github.com/BurntSushi/jiff/discussions/255), + [#266](https://github.com/BurntSushi/jiff/pull/266): +A massive number of performance improvements. Jiff should now generally be +faster than `chrono` and `time`. +* [#263](https://github.com/BurntSushi/jiff/issues/263): +Add `Timestamp::constant` for constructing timestamps in `const` context. + + +0.2.0 (2025-02-10) +================== +This is a new semver incompatible release of Jiff. It contains several breaking +changes. I expect most users of Jiff to be able to upgrade without any changes. +The fundamental API organization of Jiff has not changed. + +Some of the highlights of this release include reducing footguns and better +ecosystem integration. + +For reducing footguns, APIs on `Span` will no longer implicitly assume that +days are always 24 hours long. And `Span` no longer implements `PartialEq` or +`Eq` (instead favoring `span.fieldwise()` to create a value that supports naive +fieldwise comparison). Moreover, when using `TimeZone::system()` (perhaps via +`Zoned::now()`), if the system time zone could not be detected, then a special +`Etc/Unknown` time zone will be used instead. This avoids erroring, but also +surfaces itself to make it clearer that something has (perhaps) gone wrong. + +As for ecosystem integration, this release coincides with the publication +of the [`jiff-icu`], [`jiff-sqlx`] and [`jiff-diesel`] crates. `jiff-icu` +integrates with the [ICU4X project], and is now the recommended way to use +Jiff to work with non-Gregorian calendars or to localize datetimes for end +users. `jiff-sqlx` and `jiff-diesel` provide wrapper types that implement the +necessary traits to make it ergonomic to store and retrieve Jiff values in a +database using [SQLx] or [Diesel], respectively. + +Unless something unexpected happens, my plan is for the next breaking change +release to be Jiff 1.0 in about 6 months. Once Jiff 1.0 is out, I plan to +commit to it indefinitely. + +**BREAKING CHANGES**: + +This is an exhaustive list of breaking changes. Changes with the bolded +**RUNTIME** prefix are changes that will not be caught by the Rust compiler. +That is, they are changes in runtime behavior. + +* [#28](https://github.com/BurntSushi/jiff/issues/28): +The deprecated `intz` routines on `Zoned`, `Timestamp`, `civil::DateTime` and +`civil::Date` have been removed. You can use `in_tz` instead. This change was +made because many found the name `intz` to be unclear. +* [#32](https://github.com/BurntSushi/jiff/issues/32): +The `PartialEq` and `Eq` trait implementations on `Span` have been removed. +Ideally these shouldn't have been used, but if you do need them, please use +`Span::fieldwise` to create a `SpanFieldwise`, which does have the `PartialEq` +and `Eq` traits implemented. These were removed on `Span` itself because they +made it very easy to commit subtle bugs. +* [#36](https://github.com/BurntSushi/jiff/issues/36): +Turn panics during `Timestamp::saturing_add` into errors. Callers adding +spans that are known to contain units of hours or smaller are guaranteed that +this will not return an error. +* **RUNTIME** [#48](https://github.com/BurntSushi/jiff/issues/48): +On `Span` APIs, days are no longer silently assumed to always be 24 hours when +a relative datetime is not provided. Instead, to perform operations on units +of days or bigger, callers must either provide a relative date or opt into +invariant 24-hour days with `SpanRelativeTo::days_are_24_hours`. Shortcuts have +been added to the span builders. For example, `SpanTotal::days_are_24_hours`. +* **RUNTIME** [#147](https://github.com/BurntSushi/jiff/issues/147): +Change the behavior of the deprecated `%V` conversion specifier in +`jiff::fmt::strtime` from formatting an IANA time zone identifier to formatting +an ISO 8601 week number. To format an IANA time zone identifier, use `%Q` or +`%:Q` (which were introduced in `jiff 0.1`). +* **RUNTIME** [#212](https://github.com/BurntSushi/jiff/issues/212): +When parsing into a `Zoned` with a civil time corresponding to a gap, we treat +all offsets as invalid and return an error. Previously, we would accept the +offset as given. This brings us into line with Temporal's behavior. For +example, previously Jiff accepted `2006-04-02T02:30-05[America/Indiana/Vevay]` +but will now return an error. This is desirable for cases where a datetime in +the future is serialized before a change in the daylight saving time rules. +For more examples, see `jiff::fmt::temporal::DateTimeParser::offset_conflict` +for details on how to change Jiff's default behavior. This behavior change also +applies to `tz::OffsetConflict::PreferOffset`. +* **RUNTIME** [#213](https://github.com/BurntSushi/jiff/issues/213): +Tweak the semantics of `tz::TimeZoneDatabase` so that it only initializes one +internal tzdb instead of trying to find as many as possible. It is unlikely +that you'll be impacted by this change, but it's meant to make the semantics +be a bit more sensible. (In `jiff 0.1`, it was in theory possible for one tz +lookup to succeed in the system zoneinfo and then another tz lookup to fail +in zoneinfo but succeed automatically via the bundled copy. But this seems +confusing and possibly not desirable. Hence the change.) +* [#218](https://github.com/BurntSushi/jiff/issues/218): +In order to make naming a little more consistent between `Zoned` +and `civil::Date`, the `civil::Date::to_iso_week_date` and +`civil::ISOWeekDate::to_date` APIs were renamed to `civil::Date::iso_week_date` +and `civil::ISOWeekDate::date`. +* [#220](https://github.com/BurntSushi/jiff/issues/220): +Remove `Span::to_duration` for converting a `Span` to a `std::time::Duration` +and rename `Span::to_jiff_duration` to `Span::to_duration`. This prioritizes +`SignedDuration` as the "primary" non-calendar duration type in Jiff. And makes +it more consistent with APIs like `Zoned::duration_since`. For non-calendar +spans, the `TryFrom for std::time::Duration` still exists. For calendar +durations, use `Span::to_duration` and then convert the `SignedDuration` to +`std::time::Duration`. Additionally, `Timestamp::from_jiff_duration` and +`Timestamp::as_jiff_duration` were renamed to `Timestamp::from_duration` and +`Timestamp::as_duration`, respectively. The old deprecated routines on the +unsigned `std::time::Duration` have been removed. +* [#221](https://github.com/BurntSushi/jiff/issues/221): +Change the type of the value yielded by the `jiff::tz::TimeZoneNameIter` +iterator from `String` to `jiff::tz::TimeZoneName`. This opaque type is more +API evolution friendly. To access the string, either use `TimeZoneName`'s +`Display` trait implementation, or its `as_str` method. +* [#222](https://github.com/BurntSushi/jiff/issues/222): +Split `TimeZone::to_offset` into two methods. One that just returns the +offset, and another, `TimeZone::to_offset_info`, which includes the offset, +DST status and time zone abbreviation. The extra info is rarely needed and +is sometimes more costly to compute. Also, make the lifetime of the time +zone abbreviation returned by `TimeZoneTransition::abbreviation` tied to +the transition instead of the time zone (for future API flexibility, likely +in core-only environments). This change was overall motivated by wanting to +do less work in the common case (where we only need the offset), and for +reducing the size of a `TimeZone` considerably in core-only environments. +Callers previously using `TimeZone::to_offset` to get DST status and time zone +abbreviation should now use `TimeZone::to_offset_info`. +* **RUNTIME** [#230](https://github.com/BurntSushi/jiff/issues/230): +When `TimeZone::system()` cannot find a system configured time zone, `jiff +0.1` would automatically fall back to `TimeZone::UTC` (with a WARN-level log +message). In `jiff 0.2`, the fall back is now to `TimeZone::unknown()`, which +has a special `Etc/Unknown` identifier (as specified by Unicode and reserved by +the IANA time zone database). The fallback otherwise still behaves as if it +were `TimeZone::UTC`. This helps surface error conditions related to finding +the system time zone without causing unrecoverable failure. + +Enhancements: + +* [#136](https://github.com/BurntSushi/jiff/issues/136): +When the special `SpanRelativeTo::days_are_24_hours()` marker is used, weeks +will also be treated as invariant. That is, seven 24-hour days. In all cases, +working with years and months still requires a relative date. +* [#228](https://github.com/BurntSushi/jiff/issues/228): +It is now possible to forcefully use a bundled copy of the IANA time zone +database without relying on disabling crate features. This can be done by +enabling the `tzdb-bundle-always` crate feature and explicitly creating a +`jiff::tz::TimeZoneDatabase::bundled()` database. Once in hand, you must use +APIs like `TimeZoneDatabase::get` to create a `TimeZone` and avoid any APIs +that implicitly use the global time zone database (like `Timestamp::in_tz` or +even `Zoned::from_str`). +* [#238](https://github.com/BurntSushi/jiff/pull/238): +Add integration with the ICU4X project via the [`jiff-icu`] crate. `jiff-icu` +provides traits for easily converting between datetime types defined in Jiff +and datetime types defined in ICU4X. +* [#240](https://github.com/BurntSushi/jiff/pull/240): +Add integration with the SQLx project via the [`jiff-sqlx`] crate. `jiff-sqlx` +provides wrapper types that implement the necessary traits in SQLx for +reasonably ergonomic integration. This includes PostgreSQL and SQLite support, +but not MySQL support. (It's not clear if it's possible at present to provide +MySQL support for SQLx for datetime types outside of SQLx itself.) +* [#241](https://github.com/BurntSushi/jiff/pull/241): +Add integration with the Diesel project via the [`jiff-diesel`] crate. +`jiff-diesel` provides wrapper types that implement the necessary traits in +Diesel for reasonably ergonomic integration. This includes MySQL, PostgreSQL +and SQLite support. + + +0.1.29 (2025-02-02) +=================== +This release includes a few small enhancements and a bug fix. In particular, +there is now Serde support for `TimeZone` and the `ISOWeekDate` API has been +filled out a bit more. + +Unless a serious issue is uncovered, my plan is that this will be the last +release before `jiff 0.2`. + +Enhancements: + +* [#89](https://github.com/BurntSushi/jiff/issues/89): +Opt-in support for using Serde with `jiff::tz::TimeZone` has been added. +* [#227](https://github.com/BurntSushi/jiff/issues/227): +The `civil::ISOWeekDate` API has been beefed up with a few convenience methods. +* [#233](https://github.com/BurntSushi/jiff/issues/233): +Add `tz::Offset::round` for rounding time zone offsets. + +Bug fixes: + +* [#231](https://github.com/BurntSushi/jiff/issues/231): +Use more flexible offset equality when parsing offsets with fractional minutes. + + +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 includes 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: + +```ignore +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 the `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 changes 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-icu`]: https://docs.rs/jiff-icu +[`jiff-sqlx`]: https://docs.rs/jiff-sqlx +[`jiff-diesel`]: https://docs.rs/jiff-diesel +[ICU4X project]: https://github.com/unicode-org/icu4x +[SQLx]: https://github.com/launchbadge/sqlx +[Diesel]: https://github.com/diesel-rs/diesel +[Discussions on GitHub]: https://github.com/BurntSushi/jiff/discussions diff --git a/tools/vendor/jiff/COMPARE.md b/tools/vendor/jiff/COMPARE.md new file mode 100644 index 0000000000..d40ccc7f18 --- /dev/null +++ b/tools/vendor/jiff/COMPARE.md @@ -0,0 +1,1278 @@ +# 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.2.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::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 provide a civil date as +a relative reference point, then Jiff assumes the day is always 24 hours long: + +```rust +use jiff::{civil, SpanRound, ToSpan, Unit}; + +fn main() -> anyhow::Result<()> { + let relative = civil::date(2024, 4, 1); + let span1 = 1.day(); + let span2 = span1.round( + SpanRound::new().largest(Unit::Hour).relative(relative), + )?; + 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(()) +} +``` + +### Jiff is generally faster than Chrono + +There are some cases where Chrono is faster than Jiff, but Jiff should +generally be competitive for equivalent operations. It's generally not +possible for Chrono or Jiff to always be faster than the other, since +they each use different representations for fundamental types. This in turn +makes some operations faster and others slower, depending on what you're +trying to do. With that said, Jiff is uniformly faster than Chrono when it +comes to printing and parsing. + +```text +$ cd bench +$ cargo bench -- --save-baseline base +[.. snip ..] +$ critcmp base -g '(.*)/(?:jiff|chrono)$' +group baseline//chrono baseline//jiff +----- ---------------- -------------- +civil_datetime/add_days/diffyear/duration 1.00 8.5±0.08ns ? ?/sec 1.56 13.3±0.15ns ? ?/sec +civil_datetime/add_days/diffyear/span 1.00 23.9±0.13ns ? ?/sec +civil_datetime/add_days/sameyear/duration 1.00 6.8±0.05ns ? ?/sec 1.96 13.3±0.17ns ? ?/sec +civil_datetime/add_days/sameyear/span 1.00 23.8±0.13ns ? ?/sec +civil_datetime/to_timestamp_static/bundled 2.05 22.3±0.24ns ? ?/sec 1.00 10.9±0.14ns ? ?/sec +civil_datetime/to_timestamp_static/zoneinfo 1.53 16.2±0.15ns ? ?/sec 1.00 10.6±0.09ns ? ?/sec +civil_datetime/to_timestamp_tzdb_lookup/bundled 1.00 32.0±0.15ns ? ?/sec +civil_datetime/to_timestamp_tzdb_lookup/zoneinfo 45.85 1919.7±6.17ns ? ?/sec 1.00 41.9±0.15ns ? ?/sec +date/add_days/diffyear/duration 1.00 5.8±0.05ns ? ?/sec 1.12 6.5±0.04ns ? ?/sec +date/add_days/diffyear/span 1.00 6.5±0.04ns ? ?/sec +date/add_days/one/duration 1.00 2.1±0.02ns ? ?/sec 2.55 5.4±0.05ns ? ?/sec +date/add_days/one/span 1.00 5.8±0.02ns ? ?/sec +date/add_days/sameyear/duration 1.00 2.1±0.01ns ? ?/sec 3.08 6.5±0.05ns ? ?/sec +date/add_days/sameyear/span 1.00 6.5±0.05ns ? ?/sec +date/difference_days/duration 1.18 3.4±0.04ns ? ?/sec 1.00 2.9±0.02ns ? ?/sec +date/difference_days/span 1.00 2.5±0.02ns ? ?/sec +date/tomorrow/diff-month 1.00 0.4±0.00ns ? ?/sec 3.25 1.3±0.01ns ? ?/sec +date/tomorrow/diff-year 1.16 1.7±0.01ns ? ?/sec 1.00 1.4±0.01ns ? ?/sec +date/tomorrow/same-month 1.00 0.4±0.00ns ? ?/sec 1.99 0.8±0.00ns ? ?/sec +date/yesterday/diff-month 1.00 0.4±0.02ns ? ?/sec 3.17 1.3±0.01ns ? ?/sec +date/yesterday/diff-year 1.85 2.0±0.01ns ? ?/sec 1.00 1.1±0.01ns ? ?/sec +date/yesterday/same-month 1.00 0.4±0.03ns ? ?/sec 1.72 0.7±0.02ns ? ?/sec +parse/civil_datetime 2.95 73.6±0.72ns ? ?/sec 1.00 24.9±0.19ns ? ?/sec +parse/rfc2822 3.09 59.9±0.30ns ? ?/sec 1.00 19.3±0.17ns ? ?/sec +parse/strptime/oneshot 3.11 197.8±0.80ns ? ?/sec 1.00 63.6±1.19ns ? ?/sec +parse/strptime/prebuilt 1.00 77.1±1.44ns ? ?/sec +print/civil_datetime 13.10 147.4±0.60ns ? ?/sec 1.00 11.3±0.04ns ? ?/sec +print/iso8601_duration/long-time/duration/buffer 2.38 46.5±0.34ns ? ?/sec 1.00 19.5±0.04ns ? ?/sec +print/iso8601_duration/long-time/duration/to_string 3.13 71.7±0.36ns ? ?/sec 1.00 23.0±0.07ns ? ?/sec +print/iso8601_duration/short/duration/buffer 2.65 32.3±0.30ns ? ?/sec 1.00 12.2±0.04ns ? ?/sec +print/iso8601_duration/short/duration/to_string 2.08 36.8±0.33ns ? ?/sec 1.00 17.7±0.05ns ? ?/sec +print/iso8601_duration/tiny/duration/buffer 2.74 31.6±0.32ns ? ?/sec 1.00 11.6±0.03ns ? ?/sec +print/iso8601_duration/tiny/duration/to_string 2.28 36.5±0.40ns ? ?/sec 1.00 16.0±0.08ns ? ?/sec +print/rfc2822/buffer 4.91 64.6±0.59ns ? ?/sec 1.00 13.2±0.03ns ? ?/sec +print/rfc2822/to_string 5.75 111.0±0.37ns ? ?/sec 1.00 19.3±0.05ns ? ?/sec +print/rfc3339/buffer 3.79 64.2±0.56ns ? ?/sec 1.00 17.0±0.14ns ? ?/sec +print/rfc3339/to_string 4.75 112.4±0.33ns ? ?/sec 1.00 23.6±0.11ns ? ?/sec +print/rfc9557/buffer 1.00 16.2±0.05ns ? ?/sec +print/rfc9557/to_string 1.00 22.2±0.07ns ? ?/sec +print/strftime/oneshot/buffer 3.90 213.9±1.99ns ? ?/sec 1.00 54.8±0.32ns ? ?/sec +print/strftime/oneshot/to_string 3.63 261.3±1.26ns ? ?/sec 1.00 72.1±0.49ns ? ?/sec +print/strftime/oneshot/zoned 1.00 65.5±0.64ns ? ?/sec +print/strftime/prebuilt/buffer 1.00 85.4±0.52ns ? ?/sec +print/strftime/prebuilt/to_string 1.00 147.4±0.76ns ? ?/sec +timestamp/add_time_secs/duration 2.42 6.1±0.08ns ? ?/sec 1.00 2.5±0.03ns ? ?/sec +timestamp/add_time_secs/span 1.00 3.8±0.06ns ? ?/sec +timestamp/add_time_subsec/duration 2.10 6.1±0.04ns ? ?/sec 1.00 2.9±0.02ns ? ?/sec +timestamp/add_time_subsec/span 1.00 8.1±0.09ns ? ?/sec +timestamp/every_hour_in_week/byhand 16.15 1679.6±6.13ns ? ?/sec 1.00 104.0±0.72ns ? ?/sec +timestamp/every_hour_in_week/series 1.00 105.9±0.66ns ? ?/sec +timestamp/from_seconds/integer 14.45 4.7±0.03ns ? ?/sec 1.00 0.3±0.00ns ? ?/sec +timestamp/to_civil_datetime_offset_conversion 1.65 7.3±0.02ns ? ?/sec 1.00 4.4±0.04ns ? ?/sec +timestamp/to_civil_datetime_offset_holistic 1.00 4.4±0.04ns ? ?/sec +timestamp/to_civil_datetime_static/America-New-York/bundled 1.00 16.0±0.16ns ? ?/sec +timestamp/to_civil_datetime_static/America-New-York/zoneinfo 1.42 18.4±0.11ns ? ?/sec 1.00 13.0±0.11ns ? ?/sec +timestamp/to_civil_datetime_static/Asia-Shanghai/bundled 1.00 15.2±0.16ns ? ?/sec +timestamp/to_civil_datetime_static/Asia-Shanghai/zoneinfo 3.16 15.7±0.11ns ? ?/sec 1.00 5.0±0.05ns ? ?/sec +zoned/fixed_offset_add_time/duration 1.00 6.1±0.06ns ? ?/sec 1.56 9.5±0.05ns ? ?/sec +zoned/fixed_offset_add_time/span 1.00 16.6±0.09ns ? ?/sec +zoned/fixed_offset_to_civil_datetime 1.23 5.6±0.01ns ? ?/sec 1.00 4.5±0.01ns ? ?/sec +zoned/fixed_offset_to_timestamp 3.15 1.2±0.01ns ? ?/sec 1.00 0.4±0.00ns ? ?/sec +``` + +Questions about benchmarks are +welcome in +[Discussions on GitHub](https://github.com/BurntSushi/jiff/discussions). + +## [`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.2.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}; + +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}; + +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 generally faster than `time` + +When it comes to parsing and printing, Jiff is generally faster than `time`, +sometimes substantially so. `time` is sometimes faster than Jiff on date +arithmetic, but not by much. (Some of these may be due to faster equality +comparisons on date values, which `time` is legitimately faster at than Jiff at +present.) + +The following results were collected with `time 0.3.44`. + +```text +$ cd bench +$ cargo bench -- --save-baseline baseline +[.. snip ..] +$ critcmp base -g '(.*)/(?:jiff|time)$' +group baseline//time baseline//jiff +----- -------------- -------------- +civil_datetime/add_days/diffyear/duration 1.00 9.0±0.05ns ? ?/sec 1.48 13.3±0.15ns ? ?/sec +civil_datetime/add_days/diffyear/span 1.00 23.9±0.13ns ? ?/sec +civil_datetime/add_days/sameyear/duration 1.00 9.0±0.09ns ? ?/sec 1.48 13.3±0.17ns ? ?/sec +civil_datetime/add_days/sameyear/span 1.00 23.8±0.13ns ? ?/sec +date/add_days/diffyear/duration 1.00 4.3±0.04ns ? ?/sec 1.51 6.5±0.04ns ? ?/sec +date/add_days/diffyear/span 1.00 6.5±0.04ns ? ?/sec +date/add_days/one/duration 1.00 4.3±0.04ns ? ?/sec 1.25 5.4±0.05ns ? ?/sec +date/add_days/one/span 1.00 5.8±0.02ns ? ?/sec +date/add_days/sameyear/duration 1.00 4.3±0.02ns ? ?/sec 1.52 6.5±0.05ns ? ?/sec +date/add_days/sameyear/span 1.00 6.5±0.05ns ? ?/sec +date/days_in_month/leap/feb 2.95 1.2±0.01ns ? ?/sec 1.00 0.4±0.02ns ? ?/sec +date/days_in_month/leap/nofeb 2.74 1.1±0.00ns ? ?/sec 1.00 0.4±0.00ns ? ?/sec +date/days_in_month/noleap/feb 3.02 1.2±0.01ns ? ?/sec 1.00 0.4±0.00ns ? ?/sec +date/days_in_month/noleap/nofeb 2.78 1.1±0.01ns ? ?/sec 1.00 0.4±0.00ns ? ?/sec +date/difference_days/duration 1.00 2.1±0.01ns ? ?/sec 1.40 2.9±0.02ns ? ?/sec +date/difference_days/span 1.00 2.5±0.02ns ? ?/sec +date/tomorrow/diff-month 1.00 0.4±0.00ns ? ?/sec 3.25 1.3±0.01ns ? ?/sec +date/tomorrow/diff-year 1.00 1.1±0.01ns ? ?/sec 1.36 1.4±0.01ns ? ?/sec +date/tomorrow/same-month 1.00 0.4±0.00ns ? ?/sec 2.00 0.8±0.00ns ? ?/sec +date/yesterday/diff-month 1.00 0.3±0.00ns ? ?/sec 4.25 1.3±0.01ns ? ?/sec +date/yesterday/diff-year 1.00 1.0±0.01ns ? ?/sec 1.10 1.1±0.01ns ? ?/sec +date/yesterday/same-month 1.00 0.3±0.00ns ? ?/sec 2.33 0.7±0.02ns ? ?/sec +parse/civil_datetime 1.00 14.5±0.17ns ? ?/sec 1.72 24.9±0.19ns ? ?/sec +parse/rfc2822 4.00 77.4±0.88ns ? ?/sec 1.00 19.3±0.17ns ? ?/sec +parse/strptime/oneshot 1.00 63.6±1.19ns ? ?/sec +parse/strptime/prebuilt 1.00 103.4±0.52ns ? ?/sec +print/civil_datetime 3.11 35.0±0.18ns ? ?/sec 1.00 11.3±0.04ns ? ?/sec +print/rfc2822/buffer 1.70 22.3±0.06ns ? ?/sec 1.00 13.2±0.03ns ? ?/sec +print/rfc2822/to_string 3.44 66.4±0.49ns ? ?/sec 1.00 19.3±0.05ns ? ?/sec +print/rfc3339/buffer 2.61 44.4±0.42ns ? ?/sec 1.00 17.0±0.14ns ? ?/sec +print/rfc3339/to_string 4.03 95.3±0.79ns ? ?/sec 1.00 23.6±0.11ns ? ?/sec +print/strftime/oneshot/buffer 7.89 432.1±1.30ns ? ?/sec 1.00 54.8±0.32ns ? ?/sec +print/strftime/oneshot/to_string 7.60 547.9±1.97ns ? ?/sec 1.00 72.1±0.49ns ? ?/sec +print/strftime/oneshot/zoned 1.00 65.5±0.64ns ? ?/sec +print/strftime/prebuilt/buffer 1.00 107.0±1.34ns ? ?/sec +print/strftime/prebuilt/buffer/bespoke 1.00 108.2±0.83ns ? ?/sec +print/strftime/prebuilt/to_string 1.00 170.2±1.68ns ? ?/sec +print/strftime/prebuilt/to_string/bespoke 1.00 175.1±1.53ns ? ?/sec +timestamp/add_time_secs/duration 4.17 10.5±0.05ns ? ?/sec 1.00 2.5±0.03ns ? ?/sec +timestamp/add_time_secs/span 1.00 3.8±0.06ns ? ?/sec +timestamp/add_time_subsec/duration 3.63 10.5±0.10ns ? ?/sec 1.00 2.9±0.02ns ? ?/sec +timestamp/add_time_subsec/span 1.00 8.1±0.09ns ? ?/sec +timestamp/every_hour_in_week/byhand 16.49 1714.7±17.52ns ? ?/sec 1.00 104.0±0.72ns ? ?/sec +timestamp/every_hour_in_week/series 1.00 105.9±0.66ns ? ?/sec +timestamp/from_seconds/integer 14.43 4.7±0.03ns ? ?/sec 1.00 0.3±0.00ns ? ?/sec +timestamp/to_civil_datetime_offset_conversion 1.00 4.3±0.03ns ? ?/sec 1.05 4.4±0.04ns ? ?/sec +timestamp/to_civil_datetime_offset_holistic 1.13 5.0±0.03ns ? ?/sec 1.00 4.4±0.04ns ? ?/sec +zoned/fixed_offset_add_time/duration 1.13 10.7±0.07ns ? ?/sec 1.00 9.5±0.05ns ? ?/sec +zoned/fixed_offset_add_time/span 1.00 16.6±0.09ns ? ?/sec +zoned/fixed_offset_to_civil_datetime 1.00 0.5±0.02ns ? ?/sec 9.55 4.5±0.01ns ? ?/sec +zoned/fixed_offset_to_timestamp 4.74 1.8±0.02ns ? ?/sec 1.00 0.4±0.00ns ? ?/sec +``` + +Questions about benchmarks are +welcome in +[Discussions on GitHub](https://github.com/BurntSushi/jiff/discussions). + +## [`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.2.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 ICU4X project 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. To facilitate this, the +[`jiff-icu`](https://docs.rs/jiff-icu) crate makes conversions between Jiff +and ICU4X data types seamless. For example, to do localization starting from +a Jiff data type: + +```text +use icu::{ + calendar::{japanese::Japanese, DateTime}, + datetime::TypedDateTimeFormatter, + locid::locale, +}; +use jiff::Timestamp; +use jiff_icu::ConvertFrom as _; + +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::convert_from(zoned.datetime()); + + // 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-icu = { version = "0.1.0" } +``` diff --git a/tools/vendor/jiff/COPYING b/tools/vendor/jiff/COPYING new file mode 100644 index 0000000000..bb9c20a094 --- /dev/null +++ b/tools/vendor/jiff/COPYING @@ -0,0 +1,3 @@ +This project is dual-licensed under the Unlicense and MIT licenses. + +You may use this code under the terms of either license. diff --git a/tools/vendor/jiff/Cargo.lock b/tools/vendor/jiff/Cargo.lock new file mode 100644 index 0000000000..d7a4721821 --- /dev/null +++ b/tools/vendor/jiff/Cargo.lock @@ -0,0 +1,953 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[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 = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "bumpalo" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cc" +version = "1.2.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f50d563227a1c37cc0a263f64eca3334388c01c5e4c4861a9def205c614383c" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "chrono-tz" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6139a8597ed92cf816dfb33f5dd6cf0bb93a6adc938f11039f371bc5bcd26c3" +dependencies = [ + "chrono", + "phf", +] + +[[package]] +name = "console" +version = "0.15.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "windows-sys 0.59.0", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "deranged" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +dependencies = [ + "powerfmt", +] + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "find-msvc-tools" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[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.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" + +[[package]] +name = "iana-time-zone" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "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 = "indexmap" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "insta" +version = "1.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b76866be74d68b1595eb8060cb9191dca9c021db2316558e52ddc5d55d41b66c" +dependencies = [ + "console", + "once_cell", + "similar", + "tempfile", +] + +[[package]] +name = "itoa" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee5b5339afb4c41626dde77b7a611bd4f2c202b897852b4bcf5d03eddc61010" + +[[package]] +name = "jiff" +version = "0.2.18" +dependencies = [ + "anyhow", + "chrono", + "chrono-tz", + "hifitime", + "humantime", + "insta", + "jiff-static", + "jiff-tzdb", + "jiff-tzdb-platform", + "js-sys", + "log", + "portable-atomic", + "portable-atomic-util", + "quickcheck", + "serde", + "serde_core", + "serde_json", + "serde_yaml", + "tabwriter", + "time", + "tzfile", + "walkdir", + "wasm-bindgen", + "windows-sys 0.59.0", +] + +[[package]] +name = "jiff-static" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78" +dependencies = [ + "jiff-tzdb", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68971ebff725b9e2ca27a601c5eb38a4c5d64422c4cbab0c535f248087eda5c2" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] + +[[package]] +name = "js-sys" +version = "0.3.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[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.178" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[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.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "phf" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_shared" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06005508882fb681fd97892ecff4b7fd0fee13ef1aa569f8695dae7ab9099981" +dependencies = [ + "siphasher", +] + +[[package]] +name = "portable-atomic" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f59e70c4aef1e55797c2e8fd94a4f2a973fc972cfde0e0b05f683667b0cd39dd" + +[[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.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +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.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[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 0.2.16", +] + +[[package]] +name = "rustix" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62049b2877bf12821e8f9ad256ee38fdc31db7387ec2d3b3f403024de2034aea" + +[[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.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af14725505314343e673e9ecb7cd7e8a36aa9791eb936235a3567cc31447ae4" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[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.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "syn" +version = "2.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tabwriter" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fce91f2f0ec87dff7e6bcbbeb267439aa1188703003c6055193c821487400432" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "tempfile" +version = "3.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" +dependencies = [ + "fastrand", + "getrandom 0.3.4", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "time" +version = "0.3.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +dependencies = [ + "deranged", + "libc", + "num-conv", + "num_threads", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" + +[[package]] +name = "time-macros" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +dependencies = [ + "num-conv", + "time-core", +] + +[[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.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[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.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[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-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[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 = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "zmij" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dccf46b25b205e4bebe1d5258a991df1cc17801017a845cb5b3fe0269781aa" diff --git a/tools/vendor/jiff/Cargo.toml b/tools/vendor/jiff/Cargo.toml new file mode 100644 index 0000000000..4a4d01a34d --- /dev/null +++ b/tools/vendor/jiff/Cargo.toml @@ -0,0 +1,225 @@ +# 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.2.18" +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_jiff", +] + +[features] +alloc = [ + "serde_core?/alloc", + "portable-atomic-util/alloc", +] +default = [ + "std", + "tz-system", + "tz-fat", + "tzdb-bundle-platform", + "tzdb-zoneinfo", + "tzdb-concatenated", + "perf-inline", +] +js = [ + "dep:wasm-bindgen", + "dep:js-sys", +] +logging = ["dep:log"] +perf-inline = [] +serde = ["dep:serde_core"] +static = [ + "static-tz", + "jiff-static?/tzdb", +] +static-tz = ["dep:jiff-static"] +std = [ + "alloc", + "log?/std", + "serde_core?/std", +] +tz-fat = ["jiff-static?/tz-fat"] +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-static] +version = "0.2" +optional = true + +[dependencies.jiff-tzdb] +version = "0.1.5" +optional = true + +[dependencies.log] +version = "0.4.21" +optional = true +default-features = false + +[dependencies.serde_core] +version = "1.0.221" +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.humantime] +version = "2.1.0" + +[dev-dependencies.insta] +version = "1.39.0" + +[dev-dependencies.log] +version = "0.4.21" + +[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())".dependencies.jiff-static] +version = "=0.2.18" + +[target.'cfg(any(windows, target_family = "wasm"))'.dependencies.jiff-tzdb-platform] +version = "0.1.3" +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.61.*" +features = [ + "Win32_Foundation", + "Win32_System_Time", +] +optional = true +default-features = false + +[lints.rust.unexpected_cfgs] +level = "allow" +priority = 0 +check-cfg = ["cfg(docsrs_jiff)"] + +[profile.testrelease] +debug-assertions = false +inherits = "test" diff --git a/tools/vendor/jiff/Cargo.toml.orig b/tools/vendor/jiff/Cargo.toml.orig new file mode 100644 index 0000000000..69b6a6796d --- /dev/null +++ b/tools/vendor/jiff/Cargo.toml.orig @@ -0,0 +1,293 @@ +[package] +name = "jiff" +version = "0.2.18" #: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 = [ + "crates/jiff-cli", + "crates/jiff-static", + "crates/jiff-tzdb", + "crates/jiff-tzdb-platform", +] + +# Features are documented in the "Crate features" section of the crate docs: +# https://docs.rs/jiff/*/#crate-features +[features] +default = [ + "std", + "tz-system", + "tz-fat", + "tzdb-bundle-platform", + "tzdb-zoneinfo", + "tzdb-concatenated", + "perf-inline", +] +std = ["alloc", "log?/std", "serde_core?/std"] +alloc = ["serde_core?/alloc", "portable-atomic-util/alloc"] +serde = ["dep:serde_core"] +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"] + +# When enabled, Jiff will "fatten" time zone data so that it contains more +# transitions. This uses a little extra heap memory (or binary size, when +# embedding time zone data into your binary) in exchange for generally faster +# time zone lookups. +# +# Why is this a thing? The TZif files that make up the IANA Time Zone Database +# contain both explicit transitions for when offsets change (e.g., twice a +# year for DST) _and_ a more general rule for dealing with offset changes +# that aren't explicitly listed. In the long ago, TZif data only contained the +# explicit transitions. Later, they added support for the general rule +# mechanism, which is only used for "current" transitions. The general rule +# is implemented via, roughly, POSIX time zone strings. +# +# Not all consumers of the IANA Time Zone Database support POSIX time zone +# strings, and so, the TZif files can be built in a "fat" mode that adds extra +# transitions (usually up to the year 2037). This means that if you want to +# find the offset for a timestamp in a particular time zone before 2037, you +# just need to do one very fast binary search on the explicit transitions. +# +# However, these explicit transitions up through 2037 aren't, strictly speaking, +# required. For example, the DST transition rule in the United States is +# perfectly described by a single POSIX time zone string: +# +# EST5EDT,M3.2.0,M11.1.0 +# +# Therefore, it isn't necessary to add any explicit transitions to, e.g., +# `America/New_York` after the year 2007. (It would only become necessary if +# the DST transition rule changed.) +# +# Thus, the TZif data files for the IANA Time Zone Database can _also_ be +# generated in a "slim" fashion, where only the historical transitions are +# included. Some platforms use the slim data by default, while others uses +# the fat data. +# +# The problem is that determining the offset from a POSIX time zone can +# generally be more costly than a simple binary search on explicit transitions. +# That in turn means your time zone lookup performance can vary quite a bit due +# to factors generally beyond your control. In order to mitigate this problem, +# Jiff will automatically "fatten" up slim TZif data to include more explicit +# transitions in memory. This smoothes out those performance differences. +# +# Users may want to disable this if they are sensitive to the extra memory +# used. But generally speaking, the extra memory used is no more than what +# would be used by "fat" TZif data files from `/usr/share/zoneinfo`. +tz-fat = ["jiff-static?/tz-fat"] + +# When enabled, the `jiff::tz::get` and `jiff::tz::include` proc-macros +# become available. These proc macros enable creating `TimeZone` values in a +# `const` context for use in `core`-only environments. +# +# Users should generally prefer using Jiff's default zoneinfo integration at +# runtime. On Unix systems, this will enable applications using Jiff to get +# automatic tzdb updates when `/usr/share/zoneinfo` is updated without needing +# to re-compile the application. +# +# Note that this introduces a build-time dependency on `jiff-tzdb`. +static = ["static-tz", "jiff-static?/tzdb"] + +# When enabled, the `jiff::tz::include` macro becomes available. +# +# This proc-macro parses the TZif data (from a file) at compile time and +# generates a special static structure that can be used by Jiff at runtime +# to do tzdb lookups. This effectively provides a way to use time zones in +# core-only environments without dynamic memory allocation. +# +# This is a subset of the functionality provided by `static`. Namely, this +# doesn't result in a dependency on `jiff-tzdb`. It requires users to include +# the time zone they want as a file, where as enabling `static` (which also +# enables this feature, by necessity) permits using Jiff's bundled tzdb. +static-tz = ["dep:jiff-static"] + +# 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"] + +# When enabled, more aggressive inline annotations are used. This can +# improve performance in some cases, particularly around the areas of parsing +# and formatting. +perf-inline = [] + +[dependencies] +jiff-static = { version = "0.2", path = "crates/jiff-static", optional = true } +jiff-tzdb = { version = "0.1.5", path = "crates/jiff-tzdb", optional = true } +log = { version = "0.4.21", optional = true, default-features = false } +serde_core = { version = "1.0.221", optional = true, default-features = false } + +# This ensures that `jiff-static` is always used with a compatible version +# of `jiff`. Namely, since `jiff-static` emits code that relies on internal +# Jiff APIs that aren't covered by semver, we only guarantee compatibility for +# one version of Jiff for each release of `jiff-static`. +# +# (This is the same pattern that `serde` and `serde_derive` use as of +# 2025-02-22.) +# +# This also helps with compilation, although in Jiff's case, we don't use +# `syn` so this is less of a problem. +# +# See: https://github.com/matklad/macro-dep-test +[target.'cfg(any())'.dependencies] +jiff-static = { version = "=0.2.18", path = "crates/jiff-static" } + +# 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.3", path = "crates/jiff-tzdb-platform", optional = true } + +[target.'cfg(windows)'.dependencies.windows-sys] +version = ">=0.52.0, <=0.61.*" +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" +humantime = "2.1.0" +insta = "1.39.0" +log = "0.4.21" +# We force `serde` to be enabled in dev mode so that the docs render and test +# correctly. We also enable `static` so that we can test our proc macros. +jiff = { path = "./", default-features = false, features = ["serde", "static"] } +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" + +# Uncomment if you want to activate doc tests that import from `jiff_icu` +# (currently only in `COMPARE.md`). Otherwise, this creates a circular +# dependency and causes `jiff-icu` to get re-compiled all the time. +# icu = { version = "1.5.0", features = ["std"] } +# jiff-icu = { path = "./crates/jiff-icu" } + +# 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_jiff`, and when that's +# enabled, we enable the 'doc_cfg' feature. +# +# To test this locally, run: +# +# RUSTDOCFLAGS="--cfg docsrs_jiff" cargo +nightly doc --all-features +# +# Note that we use `docsrs_jiff` instead of the more standard `docsrs` because +# other crates use that same `cfg` knob. And since we are enabling a nightly +# feature, they sometimes break. By using our "own" `cfg` knob, we are closer +# to being masters of our own destiny. +rustdoc-args = ["--cfg", "docsrs_jiff"] + +# This squashes the (AFAIK) erroneous warning that `docsrs_jiff` is not a +# valid `cfg` knob. +[lints.rust] +unexpected_cfgs = { level = "allow", check-cfg = ['cfg(docsrs_jiff)'] } diff --git a/tools/vendor/jiff/DESIGN.md b/tools/vendor/jiff/DESIGN.md new file mode 100644 index 0000000000..96bb51dd8a --- /dev/null +++ b/tools/vendor/jiff/DESIGN.md @@ -0,0 +1,638 @@ +# 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 initial release of `jiff 0.1` originally left out any integration +points with `std::time::Duration`. Since then, `SignedDuration` has +been added to Jiff. And `TryFrom` trait implementations have been added +to both `SignedDuration` and `Span` to make conversions between it and +`std::time::Duration` easier. + + +## 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-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 diff --git a/tools/vendor/jiff/LICENSE-MIT b/tools/vendor/jiff/LICENSE-MIT new file mode 100644 index 0000000000..3b0a5dc09c --- /dev/null +++ b/tools/vendor/jiff/LICENSE-MIT @@ -0,0 +1,21 @@ +The 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. diff --git a/tools/vendor/jiff/PLATFORM.md b/tools/vendor/jiff/PLATFORM.md new file mode 100644 index 0000000000..a078e2028c --- /dev/null +++ b/tools/vendor/jiff/PLATFORM.md @@ -0,0 +1,422 @@ +# 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 at `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 without 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 support 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 a few 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 the entire +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. +* Embed specific time zones into your binary with `jiff::tz::get` or +`jiff::tz::include`. This requires enabling Jiff's `static` feature. + +#### 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 me inside of 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::unknown()`, 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 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 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/issues?q=label%3Aplatform +[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 diff --git a/tools/vendor/jiff/README.md b/tools/vendor/jiff/README.md new file mode 100644 index 0000000000..56c8ce5a28 --- /dev/null +++ b/tools/vendor/jiff/README.md @@ -0,0 +1,194 @@ +Jiff +==== +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.2.0 (/home/andrew/rust/jiff) + Compiling jiff-play v0.2.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 + +With `jiff 0.2` out about 6 months after the `jiff 0.1` initial release, my +plan remains roughly the same as it started. That is, I'd still like to get a +`jiff 1.0` release out this summer 2025 (in about 6 months) and then commit to +it indefinitely. This plan may change if something critically wrong is found +with the current API. + +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. + +Note that performance is still an important goal. Some aspects of Jiff have +had optimization attention paid to them, but many still have not. It is a goal +to improve where we can, but performance will generally come second to API +comprehension and correctness. + +### 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. diff --git a/tools/vendor/jiff/UNLICENSE b/tools/vendor/jiff/UNLICENSE new file mode 100644 index 0000000000..68a49daad8 --- /dev/null +++ b/tools/vendor/jiff/UNLICENSE @@ -0,0 +1,24 @@ +This 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 diff --git a/tools/vendor/jiff/src/civil/date.rs b/tools/vendor/jiff/src/civil/date.rs new file mode 100644 index 0000000000..2dc1a73405 --- /dev/null +++ b/tools/vendor/jiff/src/civil/date.rs @@ -0,0 +1,4079 @@ +use core::time::Duration as UnsignedDuration; + +use crate::{ + civil::{DateTime, Era, ISOWeekDate, Time, Weekday}, + duration::{Duration, SDuration}, + error::{civil::Error as E, Error, ErrorContext}, + fmt::{ + self, + temporal::{DEFAULT_DATETIME_PARSER, DEFAULT_DATETIME_PRINTER}, + }, + shared::util::itime::{self, IDate, IEpochDay}, + tz::TimeZone, + util::{ + rangeint::{self, Composite, RFrom, RInto, TryRFrom}, + t::{self, Day, Month, Sign, UnixEpochDay, 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 > itime::days_in_month(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 + /// 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::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 == C(9999) { + if week >= C(52) { + [week.min(C(52)), weekday.min(C(4))] + } else { + [week, weekday] + } + } else { + [week, weekday] + } + }, + ); + days += (UnixEpochDay::rfrom(week) - C(1)) * C(7); + days += weekday; + Date::from_unix_epoch_day(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 >= C(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_iweekday(self.to_idate_const().weekday()) + } + + /// 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 { + static DAYS_BY_MONTH_NO_LEAP: [i16; 14] = + [0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365]; + static DAYS_BY_MONTH_LEAP: [i16; 14] = + [0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366]; + static TABLES: [[i16; 14]; 2] = + [DAYS_BY_MONTH_NO_LEAP, DAYS_BY_MONTH_LEAP]; + TABLES[self.in_leap_year() as usize][self.month() as usize] + + i16::from(self.day()) + } + + /// 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_unchecked( + self.year_ranged(), + self.month_ranged(), + C(1).rinto(), + ) + } + + /// 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_unchecked( + self.year_ranged(), + self.month_ranged(), + max_day, + ) + } + + /// 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_unchecked( + self.year_ranged(), + C(1).rinto(), + C(1).rinto(), + ) + } + + /// 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_unchecked( + self.year_ranged(), + C(12).rinto(), + C(31).rinto(), + ) + } + + /// 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 { + itime::is_leap_year(self.year_ranged().get()) + } + + /// 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 { + if self.day() >= 28 && self.day() == self.days_in_month() { + if self.month() == 12 { + let year = self.year_ranged().try_checked_add("year", C(1))?; + let month = Month::new_unchecked(1); + let day = Day::new_unchecked(1); + return Ok(Date::new_ranged_unchecked(year, month, day)); + } + let year = self.year_ranged(); + let month = Month::new_unchecked(self.month() + 1); + let day = Day::new_unchecked(1); + return Ok(Date::new_ranged_unchecked(year, month, day)); + } + let year = self.year_ranged(); + let month = self.month_ranged(); + let day = Day::new_unchecked(self.day() + 1); + Ok(Date::new_ranged_unchecked(year, month, day)) + } + + /// 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 { + if self.day() == 1 { + if self.month() == 1 { + let year = self.year_ranged().try_checked_sub("year", C(1))?; + let month = Month::new_unchecked(12); + let day = Day::new_unchecked(31); + return Ok(Date::new_ranged_unchecked(year, month, day)); + } + let year = self.year_ranged(); + let month = Month::new_unchecked(self.month() - 1); + let day = days_in_month(year, month); + return Ok(Date::new_ranged_unchecked(year, month, day)); + } + let year = self.year_ranged(); + let month = self.month_ranged(); + let day = Day::new_unchecked(self.day() - 1); + Ok(Date::new_ranged_unchecked(year, month, day)) + } + + /// 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 { + let weekday = weekday.to_iweekday(); + let idate = self.to_idate_const(); + Ok(Date::from_idate_const( + idate + .nth_weekday_of_month(nth, weekday) + .map_err(Error::itime_range)?, + )) + } + + /// 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 == C(0) { + Err(Error::slim_range("nth weekday")) + } else if nth > C(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_day(); + let end = start.try_checked_add("days", diff)?; + Ok(Date::from_unix_epoch_day(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_day(); + let end = start.try_checked_sub("days", diff)?; + Ok(Date::from_unix_epoch_day(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_day()); + 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.rinto())); + if days < week_start { + week_start = t::NoUnits32::rfrom(iso_week_start_from_year( + (year - C(1)).rinto(), + )); + } else { + let next_year_week_start = t::NoUnits32::rfrom( + iso_week_start_from_year((year + C(1)).rinto()), + ); + if days >= next_year_week_start { + week_start = next_year_week_start; + } + } + week_start + }); + + let weekday = Weekday::from_iweekday( + IEpochDay { epoch_day: days.get() }.weekday(), + ); + let week = ((days - week_start) / C(7)) + C(1); + + let unix_epoch_day = week_start + + t::NoUnits32::rfrom( + Weekday::Thursday.since_ranged(Weekday::Monday), + ); + let year = + Date::from_unix_epoch_day(unix_epoch_day.rinto()).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. + /// + /// # Panics + /// + /// This panics if the provided 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 + /// + /// ``` + /// 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 { + if span.is_zero() { + return Ok(self); + } + if span.units().contains_only(Unit::Day) { + let span_days = span.get_days_ranged(); + return if span_days == C(-1) { + self.yesterday() + } else if span_days == C(1) { + self.tomorrow() + } else { + let epoch_days = self.to_unix_epoch_day(); + let days = epoch_days.try_checked_add( + "days", + UnixEpochDay::rfrom(span.get_days_ranged()), + )?; + Ok(Date::from_unix_epoch_day(days)) + }; + } + + 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 epoch_days = date.to_unix_epoch_day(); + let mut days = epoch_days + .try_checked_add( + "days", + C(7) * UnixEpochDay::rfrom(span.get_weeks_ranged()), + )? + .try_checked_add( + "days", + UnixEpochDay::rfrom(span.get_days_ranged()), + )?; + if !span.units().only_time().is_empty() { + let time_days = span + .only_lower(Unit::Day) + .to_invariant_nanoseconds() + .div_ceil(t::NANOS_PER_CIVIL_DAY); + days = days.try_checked_add("time", time_days)?; + } + Ok(Date::from_unix_epoch_day(days)) + } + + #[inline] + fn checked_add_duration( + self, + duration: SignedDuration, + ) -> Result { + // OK because 24!={-1,0}. + match duration.as_hours() / 24 { + 0 => Ok(self), + -1 => self.yesterday(), + 1 => self.tomorrow(), + days => { + let days = UnixEpochDay::try_new("days", days) + .context(E::OverflowDaysDuration)?; + let days = + self.to_unix_epoch_day().try_checked_add("days", days)?; + Ok(Date::from_unix_epoch_day(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.since_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.since_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, months or days without a + /// relative date. + /// + /// ``` + /// 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() } + } +} + +/// Internal APIs using ranged integers. +impl Date { + #[inline] + pub(crate) fn new_ranged( + year: Year, + month: Month, + day: Day, + ) -> Result { + if day > C(28) { + 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::new_ranged_unchecked(year, month, day)) + } + + #[inline] + pub(crate) fn new_ranged_unchecked( + year: Year, + month: Month, + day: Day, + ) -> Date { + Date { year, month, day } + } + + #[inline] + fn constrain_ranged(year: Year, month: Month, day: Day) -> 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 until_days_ranged(self, other: Date) -> t::SpanDays { + if self == other { + return C(0).rinto(); + } + let start = self.to_unix_epoch_day(); + let end = other.to_unix_epoch_day(); + (end - start).rinto() + } + + #[cfg_attr(feature = "perf-inline", inline(always))] + pub(crate) fn to_unix_epoch_day(self) -> UnixEpochDay { + self.to_idate().map(|x| x.to_epoch_day().epoch_day).to_rint() + } + + #[cfg_attr(feature = "perf-inline", inline(always))] + pub(crate) fn from_unix_epoch_day(epoch_day: UnixEpochDay) -> Date { + let epoch_day = rangeint::composite!((epoch_day) => { + IEpochDay { epoch_day } + }); + Date::from_idate(epoch_day.map(|x| x.to_date())) + } + + #[inline] + pub(crate) fn to_idate(&self) -> Composite { + rangeint::composite! { + (year = self.year, month = self.month, day = self.day) => { + IDate { year, month, day } + } + } + } + + #[inline] + pub(crate) fn from_idate(idate: Composite) -> Date { + let (year, month, day) = + rangeint::uncomposite!(idate, c => (c.year, c.month, c.day)); + Date { + year: year.to_rint(), + month: month.to_rint(), + day: day.to_rint(), + } + } + + #[inline] + pub(crate) const fn to_idate_const(self) -> IDate { + IDate { + year: self.year.get_unchecked(), + month: self.month.get_unchecked(), + day: self.day.get_unchecked(), + } + } + + #[inline] + pub(crate) const fn from_idate_const(idate: IDate) -> Date { + Date { + year: Year::new_unchecked(idate.year), + month: Month::new_unchecked(idate.month), + day: Day::new_unchecked(idate.day), + } + } +} + +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_core::Serialize for Date { + #[inline] + fn serialize( + &self, + serializer: S, + ) -> Result { + serializer.collect_str(self) + } +} + +#[cfg(feature = "serde")] +impl<'de> serde_core::Deserialize<'de> for Date { + #[inline] + fn deserialize>( + deserializer: D, + ) -> Result { + use serde_core::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 the limits of a [`Span`] +/// or [`Date`] value. +/// +/// This iterator is created by [`Date::series`]. +#[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) + } +} + +impl core::iter::FusedIterator for DateSeries {} + +/// 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 since `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 since_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(Error::from(E::RoundMustUseDaysOrBigger { + unit: largest, + })); + } + 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 != C(0) || months != C(0) { + let sign = if years != C(0) { + Sign::rfrom(years.signum()) + } else { + Sign::rfrom(months.signum()) + }; + let mut days_in_month2 = + 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_month2; + 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_month2 = days_in_month(year2, month2).rinto(); + day_correct = if sign < C(0) { + -original_days_in_month1 + } else { + days_in_month2 + }; + } + + let day0_trunc = t::SpanDays::rfrom(day1.min(days_in_month2)); + days = t::SpanDays::rfrom(day2) - day0_trunc + day_correct; + + if years != C(0) { + months = t::SpanMonths::rfrom(month2) + - t::SpanMonths::rfrom(month1); + if months.signum() == -sign { + let month_correct = if sign < C(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 != C(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)) => { + let year = year.get_unchecked(); + let idate = IDate::from_day_of_year(year, day) + .map_err(Error::itime_range)?; + return Ok(Date::from_idate_const(idate)); + } + Some(DateWithDay::OfYearNoLeap(day)) => { + let year = year.get_unchecked(); + let idate = IDate::from_day_of_year_no_leap(year, day) + .map_err(Error::itime_range)?; + return Ok(Date::from_idate_const(idate)); + } + }; + 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: t::ISOYear) -> UnixEpochDay { + // 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.rinto(), C(1).rinto(), C(4).rinto()) + .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_day() - diff_from_monday +} + +/// 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( + mut year: Year, + mut month: Month, + delta: Sign, +) -> Result<(Year, Month), Error> { + month += delta; + if month < C(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: t::Month, + span: t::SpanMonths, +) -> (t::Month, t::SpanYears) { + let month = t::SpanMonths::rfrom(month); + let total = month - C(1) + span; + let years = total / C(12); + let month = (total % C(12)) + C(1); + (month.rinto(), years.rinto()) +} + +/// Saturates the given day in the month. +/// +/// That is, if the day exceeds the maximum number of days in the given year +/// and month, then this returns the maximum. Otherwise, it returns the day +/// given. +#[inline] +fn saturate_day_in_month(year: Year, month: Month, day: Day) -> Day { + day.min(days_in_month(year, month)) +} + +/// Returns the number of days in the given year and month. +/// +/// This correctly returns `29` when the year is a leap year and the month is +/// February. +#[inline] +fn days_in_month(year: Year, month: Month) -> Day { + let c = rangeint::composite!((year, month) => { + itime::days_in_month(year, month) + }); + c.to_rint() +} + +#[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] + #[cfg(not(miri))] + fn all_days_to_date_roundtrip() { + for rd in -100_000..=100_000 { + let rd = UnixEpochDay::new(rd).unwrap(); + let date = Date::from_unix_epoch_day(rd); + let got = date.to_unix_epoch_day(); + assert_eq!(rd, got, "for date {date:?}"); + } + } + + #[test] + #[cfg(not(miri))] + fn all_date_to_days_roundtrip() { + let year_range = 2000..=2500; + // let year_range = -9999..=9999; + 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_day(); + let got = Date::from_unix_epoch_day(rd); + assert_eq!(date, got, "for date {date:?}"); + } + } + } + } + + #[test] + #[cfg(not(miri))] + fn all_date_to_iso_week_date_roundtrip() { + 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::()); + } + } + + #[cfg(not(miri))] + 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); + } + + /// Regression test where converting to `IDate` and back to do the + /// calculation was FUBAR. + #[test] + fn nth_weekday_of_month() { + let d1 = date(1998, 1, 1); + let d2 = d1.nth_weekday_of_month(5, Weekday::Saturday).unwrap(); + assert_eq!(d2, date(1998, 1, 31)); + } +} diff --git a/tools/vendor/jiff/src/civil/datetime.rs b/tools/vendor/jiff/src/civil/datetime.rs new file mode 100644 index 0000000000..f44d1ec9cb --- /dev/null +++ b/tools/vendor/jiff/src/civil/datetime.rs @@ -0,0 +1,4556 @@ +use core::time::Duration as UnsignedDuration; + +use crate::{ + civil::{ + datetime, Date, DateWith, Era, ISOWeekDate, Time, TimeWith, Weekday, + }, + duration::{Duration, SDuration}, + error::{civil::Error as E, Error, ErrorContext}, + fmt::{ + self, + temporal::{self, DEFAULT_DATETIME_PARSER}, + }, + shared::util::itime::IDateTime, + tz::TimeZone, + util::{ + rangeint::{Composite, 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::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 { + use crate::tz::AmbiguousOffset; + + // It's pretty disappointing that we do this instead of the + // simpler: + // + // tz.into_ambiguous_zoned(self).compatible() + // + // Below, in the common case of an unambiguous datetime, + // we avoid doing the work to re-derive the datetime *and* + // offset from the timestamp we find from tzdb. In particular, + // `Zoned::new` does this work given a timestamp and a time + // zone. But we circumvent `Zoned::new` and use a special + // `Zoned::from_parts` crate-internal constructor to handle + // this case. + // + // Ideally we could do this in `AmbiguousZoned::compatible` + // itself, but it turns out that it doesn't always work. + // Namely, that API supports providing an unambiguous + // offset even when the civil datetime is within a + // DST transition. In that case, once the timestamp + // is resolved, the offset given might actually + // change. See `2024-03-11T02:02[America/New_York]` + // example for `AlwaysOffset` conflict resolution on + // `ZonedWith::disambiguation`. + // + // But the optimization works here because if we get an + // unambiguous offset from tzdb, then we know it isn't in a DST + // transition and that it won't change with the timestamp. + // + // This ends up saving a fair bit of cycles re-computing + // the offset (which requires another tzdb lookup) and + // re-generating the civil datetime from the timestamp for the + // re-computed offset. This helps the + // `civil_datetime_to_timestamp_tzdb_lookup/zoneinfo/jiff` + // micro-benchmark quite a bit. + let dt = self; + let amb_ts = tz.to_ambiguous_timestamp(dt); + let (offset, ts, dt) = match amb_ts.offset() { + AmbiguousOffset::Unambiguous { offset } => { + let ts = offset.to_timestamp(dt)?; + (offset, ts, dt) + } + AmbiguousOffset::Gap { before, .. } => { + let ts = before.to_timestamp(dt)?; + let offset = tz.to_offset(ts); + let dt = offset.to_datetime(ts); + (offset, ts, dt) + } + AmbiguousOffset::Fold { before, .. } => { + let ts = before.to_timestamp(dt)?; + let offset = tz.to_offset(ts); + let dt = offset.to_datetime(ts); + (offset, ts, dt) + } + }; + Ok(Zoned::from_parts(ts, tz, offset, dt)) + } + + /// 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 (old_date, old_time) = (self.date(), self.time()); + let units = span.units(); + match (units.only_calendar().is_empty(), units.only_time().is_empty()) + { + (true, true) => Ok(self), + (false, true) => { + let new_date = old_date + .checked_add(span) + .context(E::FailedAddSpanDate)?; + Ok(DateTime::from_parts(new_date, old_time)) + } + (true, false) => { + let (new_time, leftovers) = old_time + .overflowing_add(span) + .context(E::FailedAddSpanTime)?; + let new_date = old_date + .checked_add(leftovers) + .context(E::FailedAddSpanOverflowing)?; + Ok(DateTime::from_parts(new_date, new_time)) + } + (false, false) => self.checked_add_span_general(&span), + } + } + + #[inline(never)] + #[cold] + fn checked_add_span_general(self, span: &Span) -> Result { + let (old_date, old_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) = old_time + .overflowing_add(span_time) + .context(E::FailedAddSpanTime)?; + let new_date = + old_date.checked_add(span_date).context(E::FailedAddSpanDate)?; + let new_date = new_date + .checked_add(leftovers) + .context(E::FailedAddSpanOverflowing)?; + 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) + .context(E::FailedAddDurationOverflowing)?; + 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 default is an API guarantee. Users can rely on the default not + /// returning any calendar units bigger than days in the default + /// configuration. + /// + /// 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(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_day(); + let time_nano = self.time().to_nanosecond(); + (t::NoUnits128::rfrom(day_nano) * t::NANOS_PER_CIVIL_DAY) + time_nano + } + + #[inline] + pub(crate) fn to_idatetime(&self) -> Composite { + let idate = self.date().to_idate(); + let itime = self.time().to_itime(); + idate.zip2(itime).map(|(date, time)| IDateTime { date, time }) + } + + #[inline] + pub(crate) fn from_idatetime(idt: Composite) -> DateTime { + let (idate, itime) = idt.map(|idt| (idt.date, idt.time)).unzip2(); + DateTime::from_parts(Date::from_idate(idate), Time::from_itime(itime)) + } + + #[inline] + pub(crate) const fn to_idatetime_const(&self) -> IDateTime { + IDateTime { + date: self.date.to_idate_const(), + time: self.time.to_itime_const(), + } + } +} + +/// 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() } + } +} + +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. +/// +/// # Formatting options supported +/// +/// * [`std::fmt::Formatter::precision`] can be set to control the precision +/// of the fractional second component. When not set, the minimum precision +/// required to losslessly render the value is used. +/// +/// # Example +/// +/// This shows the default rendering: +/// +/// ``` +/// use jiff::civil::date; +/// +/// // No fractional seconds: +/// let dt = date(2024, 6, 15).at(7, 0, 0, 0); +/// assert_eq!(format!("{dt}"), "2024-06-15T07:00:00"); +/// +/// // With fractional seconds: +/// let dt = date(2024, 6, 15).at(7, 0, 0, 123_000_000); +/// assert_eq!(format!("{dt}"), "2024-06-15T07:00:00.123"); +/// +/// # Ok::<(), Box>(()) +/// ``` +/// +/// # Example: setting the precision +/// +/// ``` +/// 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. It is guaranteed that the largest non-zero +/// unit in the `Span` returned will be days. +/// +/// 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_core::Serialize for DateTime { + #[inline] + fn serialize( + &self, + serializer: S, + ) -> Result { + serializer.collect_str(self) + } +} + +#[cfg(feature = "serde")] +impl<'de> serde_core::Deserialize<'de> for DateTime { + #[inline] + fn deserialize>( + deserializer: D, + ) -> Result { + use serde_core::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 the limits of a [`Span`] +/// or [`DateTime`] value. +/// +/// This iterator is created by [`DateTime::series`]. +#[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) + } +} + +impl core::iter::FusedIterator for DateTimeSeries {} + +/// 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 > C(0) { + d2 = d2.yesterday().unwrap(); + } else if sign < C(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.rinto()) + // 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, dt: DateTime) -> Result { + // ref: https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.round + + 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(Error::from( + crate::error::util::RoundingIncrementError::Unsupported { + unit: self.smallest, + }, + )); + } + // We don't do any rounding in this case, so just bail now. + Unit::Nanosecond if increment == C(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(t::NANOS_PER_CIVIL_DAY); + let time_nanos = time_rounded.rem_ceil(t::NANOS_PER_CIVIL_DAY); + let time = Time::from_nanosecond(time_nanos.rinto()); + + 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)) + .context(E::FailedAddDays)?; + Ok(DateTime::from_parts(end, time)) + } + + pub(crate) fn get_smallest(&self) -> Unit { + self.smallest + } + + pub(crate) fn get_mode(&self) -> RoundMode { + self.mode + } + + pub(crate) fn get_increment(&self) -> i64 { + self.increment + } +} + +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. + /// + /// Note that this only sets the millisecond component. It does + /// not change the microsecond or nanosecond components. To set + /// the fractional second component to nanosecond precision, use + /// [`DateTimeWith::subsec_nanosecond`]. + /// + /// # 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. + /// + /// Note that this only sets the microsecond component. It does + /// not change the millisecond or nanosecond components. To set + /// the fractional second component to nanosecond precision, use + /// [`DateTimeWith::subsec_nanosecond`]. + /// + /// # 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. + /// + /// Note that this only sets the nanosecond component. It does + /// not change the millisecond or microsecond components. To set + /// the fractional second component to nanosecond precision, use + /// [`DateTimeWith::subsec_nanosecond`]. + /// + /// # 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. + /// + /// Note that this sets the entire fractional second component to + /// nanosecond precision, and overrides any individual millisecond, + /// microsecond or nanosecond settings. To set individual components, + /// use [`DateTimeWith::millisecond`], [`DateTimeWith::microsecond`] or + /// [`DateTimeWith::nanosecond`]. + /// + /// # 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); + } +} diff --git a/tools/vendor/jiff/src/civil/iso_week_date.rs b/tools/vendor/jiff/src/civil/iso_week_date.rs new file mode 100644 index 0000000000..bea792ae60 --- /dev/null +++ b/tools/vendor/jiff/src/civil/iso_week_date.rs @@ -0,0 +1,991 @@ +use crate::{ + civil::{Date, DateTime, Weekday}, + error::{civil::Error as E, Error}, + fmt::temporal::{DEFAULT_DATETIME_PARSER, DEFAULT_DATETIME_PRINTER}, + 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. +/// +/// # Parsing and printing +/// +/// The `ISOWeekDate` type provides convenient trait implementations of +/// [`std::str::FromStr`] and [`std::fmt::Display`]. These use the format +/// specified by ISO 8601 for week dates: +/// +/// ``` +/// use jiff::civil::ISOWeekDate; +/// +/// let week_date: ISOWeekDate = "2024-W24-7".parse()?; +/// assert_eq!(week_date.to_string(), "2024-W24-7"); +/// assert_eq!(week_date.date().to_string(), "2024-06-16"); +/// +/// # Ok::<(), Box>(()) +/// ``` +/// +/// ISO 8601 allows the `-` separator to be absent: +/// +/// ``` +/// use jiff::civil::ISOWeekDate; +/// +/// let week_date: ISOWeekDate = "2024W241".parse()?; +/// assert_eq!(week_date.to_string(), "2024-W24-1"); +/// assert_eq!(week_date.date().to_string(), "2024-06-10"); +/// +/// // But you cannot mix and match. Either `-` separates +/// // both the year and week, or neither. +/// assert!("2024W24-1".parse::().is_err()); +/// assert!("2024-W241".parse::().is_err()); +/// +/// # Ok::<(), Box>(()) +/// ``` +/// +/// And the `W` may also be lowercase: +/// +/// ``` +/// use jiff::civil::ISOWeekDate; +/// +/// let week_date: ISOWeekDate = "2024-w24-2".parse()?; +/// assert_eq!(week_date.to_string(), "2024-W24-2"); +/// assert_eq!(week_date.date().to_string(), "2024-06-11"); +/// +/// # Ok::<(), Box>(()) +/// ``` +/// +/// # 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.iso_week_date(), weekdate); +/// +/// let d = date(2024, 3, 9); +/// let weekdate = ISOWeekDate::new(2024, 10, Weekday::Saturday).unwrap(); +/// assert_eq!(d.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.iso_week_date().in_long_year() { +/// overlapping.push(year); +/// } +/// } +/// assert_eq!(overlapping, vec![ +/// 1904, 1908, 1920, 1932, 1936, 1948, 1960, 1964, 1976, 1988, 1992, +/// ]); +/// ``` +/// +/// # Example: printing all weeks in a year +/// +/// The ISO 8601 week calendar can be useful when you want to categorize +/// things into buckets of weeks where all weeks are exactly 7 days, _and_ +/// you don't care as much about the precise Gregorian year. Here's an example +/// that prints all of the ISO 8601 weeks in one ISO 8601 week year: +/// +/// ``` +/// use jiff::{civil::{ISOWeekDate, Weekday}, ToSpan}; +/// +/// let target_year = 2024; +/// let iso_week_date = ISOWeekDate::new(target_year, 1, Weekday::Monday)?; +/// // Create a series of dates via the Gregorian calendar. But since a +/// // Gregorian week and an ISO 8601 week calendar week are both 7 days, +/// // this works fine. +/// let weeks = iso_week_date +/// .date() +/// .series(1.week()) +/// .map(|d| d.iso_week_date()) +/// .take_while(|wd| wd.year() == target_year); +/// for start_of_week in weeks { +/// let end_of_week = start_of_week.last_of_week()?; +/// println!( +/// "ISO week {}: {} - {}", +/// start_of_week.week(), +/// start_of_week.date(), +/// end_of_week.date() +/// ); +/// } +/// # Ok::<(), Box>(()) +/// ``` +#[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::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).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).iso_week_date(); + /// assert_eq!(weekdate.year(), 2020); + /// assert_eq!(weekdate.week(), 1); + /// + /// let weekdate = date(1948, 12, 31).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).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 the ISO 8601 week date corresponding to the first day in the + /// week of this week date. The date returned is guaranteed to have a + /// weekday of [`Weekday::Monday`]. + /// + /// # Errors + /// + /// Since `-9999-01-01` falls on a Monday, it follows that the minimum + /// support Gregorian date is exactly equivalent to the minimum supported + /// ISO 8601 week date. This means that this routine can never actually + /// fail, but only insomuch as the minimums line up. For that reason, and + /// for consistency with [`ISOWeekDate::last_of_week`], the API is + /// fallible. + /// + /// # Example + /// + /// ``` + /// use jiff::civil::{ISOWeekDate, Weekday, date}; + /// + /// let wd = ISOWeekDate::new(2025, 5, Weekday::Wednesday).unwrap(); + /// assert_eq!(wd.date(), date(2025, 1, 29)); + /// assert_eq!( + /// wd.first_of_week()?, + /// ISOWeekDate::new(2025, 5, Weekday::Monday).unwrap(), + /// ); + /// + /// // Works even for the minimum date. + /// assert_eq!( + /// ISOWeekDate::MIN.first_of_week()?, + /// ISOWeekDate::new(-9999, 1, Weekday::Monday).unwrap(), + /// ); + /// + /// # Ok::<(), Box>(()) + /// ``` + #[inline] + pub fn first_of_week(self) -> Result { + // I believe this can never return an error because `Monday` is in + // bounds for all possible year-and-week combinations. This is *only* + // because -9999-01-01 corresponds to -9999-W01-Monday. Which is kinda + // lucky. And I guess if we ever change the ranges, this could become + // fallible. + ISOWeekDate::new_ranged( + self.year_ranged(), + self.week_ranged(), + Weekday::Monday, + ) + } + + /// Returns the ISO 8601 week date corresponding to the last day in the + /// week of this week date. The date returned is guaranteed to have a + /// weekday of [`Weekday::Sunday`]. + /// + /// # Errors + /// + /// This can return an error if the last day of the week exceeds Jiff's + /// maximum Gregorian date of `9999-12-31`. It turns out this can happen + /// since `9999-12-31` falls on a Friday. + /// + /// # Example + /// + /// ``` + /// use jiff::civil::{ISOWeekDate, Weekday, date}; + /// + /// let wd = ISOWeekDate::new(2025, 5, Weekday::Wednesday).unwrap(); + /// assert_eq!(wd.date(), date(2025, 1, 29)); + /// assert_eq!( + /// wd.last_of_week()?, + /// ISOWeekDate::new(2025, 5, Weekday::Sunday).unwrap(), + /// ); + /// + /// // Unlike `first_of_week`, this routine can actually fail on real + /// // values, although, only when close to the maximum supported date. + /// assert_eq!( + /// ISOWeekDate::MAX.last_of_week().unwrap_err().to_string(), + /// "parameter 'weekday' with value 7 is not \ + /// in the required range of 1..=5", + /// ); + /// + /// # Ok::<(), Box>(()) + /// ``` + #[inline] + pub fn last_of_week(self) -> Result { + // This can return an error when in the last week of the maximum year + // supported by Jiff. That's because the Saturday and Sunday of that + // week are actually in Gregorian year 10,000. + ISOWeekDate::new_ranged( + self.year_ranged(), + self.week_ranged(), + Weekday::Sunday, + ) + } + + /// Returns the ISO 8601 week date corresponding to the first day in the + /// year of this week date. The date returned is guaranteed to have a + /// weekday of [`Weekday::Monday`]. + /// + /// # Errors + /// + /// Since `-9999-01-01` falls on a Monday, it follows that the minimum + /// support Gregorian date is exactly equivalent to the minimum supported + /// ISO 8601 week date. This means that this routine can never actually + /// fail, but only insomuch as the minimums line up. For that reason, and + /// for consistency with [`ISOWeekDate::last_of_year`], the API is + /// fallible. + /// + /// # Example + /// + /// ``` + /// use jiff::civil::{ISOWeekDate, Weekday, date}; + /// + /// let wd = ISOWeekDate::new(2025, 5, Weekday::Wednesday).unwrap(); + /// assert_eq!(wd.date(), date(2025, 1, 29)); + /// assert_eq!( + /// wd.first_of_year()?, + /// ISOWeekDate::new(2025, 1, Weekday::Monday).unwrap(), + /// ); + /// + /// // Works even for the minimum date. + /// assert_eq!( + /// ISOWeekDate::MIN.first_of_year()?, + /// ISOWeekDate::new(-9999, 1, Weekday::Monday).unwrap(), + /// ); + /// + /// # Ok::<(), Box>(()) + /// ``` + #[inline] + pub fn first_of_year(self) -> Result { + // I believe this can never return an error because `Monday` is in + // bounds for all possible years. This is *only* because -9999-01-01 + // corresponds to -9999-W01-Monday. Which is kinda lucky. And I guess + // if we ever change the ranges, this could become fallible. + ISOWeekDate::new_ranged(self.year_ranged(), C(1), Weekday::Monday) + } + + /// Returns the ISO 8601 week date corresponding to the last day in the + /// year of this week date. The date returned is guaranteed to have a + /// weekday of [`Weekday::Sunday`]. + /// + /// # Errors + /// + /// This can return an error if the last day of the year exceeds Jiff's + /// maximum Gregorian date of `9999-12-31`. It turns out this can happen + /// since `9999-12-31` falls on a Friday. + /// + /// # Example + /// + /// ``` + /// use jiff::civil::{ISOWeekDate, Weekday, date}; + /// + /// let wd = ISOWeekDate::new(2025, 5, Weekday::Wednesday).unwrap(); + /// assert_eq!(wd.date(), date(2025, 1, 29)); + /// assert_eq!( + /// wd.last_of_year()?, + /// ISOWeekDate::new(2025, 52, Weekday::Sunday).unwrap(), + /// ); + /// + /// // Works correctly for "long" years. + /// let wd = ISOWeekDate::new(2026, 5, Weekday::Wednesday).unwrap(); + /// assert_eq!(wd.date(), date(2026, 1, 28)); + /// assert_eq!( + /// wd.last_of_year()?, + /// ISOWeekDate::new(2026, 53, Weekday::Sunday).unwrap(), + /// ); + /// + /// // Unlike `first_of_year`, this routine can actually fail on real + /// // values, although, only when close to the maximum supported date. + /// assert_eq!( + /// ISOWeekDate::MAX.last_of_year().unwrap_err().to_string(), + /// "parameter 'weekday' with value 7 is not \ + /// in the required range of 1..=5", + /// ); + /// + /// # Ok::<(), Box>(()) + /// ``` + #[inline] + pub fn last_of_year(self) -> Result { + // This can return an error when in the maximum year supported by + // Jiff. That's because the last Saturday and Sunday of that year are + // actually in Gregorian year 10,000. + let week = if self.in_long_year() { + ISOWeek::V::<53, 52, 53>() + } else { + ISOWeek::V::<52, 52, 53>() + }; + ISOWeekDate::new_ranged(self.year_ranged(), week, Weekday::Sunday) + } + + /// Returns the total number of days in the year of this ISO 8601 week + /// date. + /// + /// It is guaranteed that the value returned is either 364 or 371. The + /// latter case occurs precisely when [`ISOWeekDate::in_long_year`] + /// returns `true`. + /// + /// # Example + /// + /// ``` + /// use jiff::civil::{ISOWeekDate, Weekday}; + /// + /// let weekdate = ISOWeekDate::new(2025, 7, Weekday::Monday).unwrap(); + /// assert_eq!(weekdate.days_in_year(), 364); + /// let weekdate = ISOWeekDate::new(2026, 7, Weekday::Monday).unwrap(); + /// assert_eq!(weekdate.days_in_year(), 371); + /// ``` + #[inline] + pub fn days_in_year(self) -> i16 { + if self.in_long_year() { + 371 + } else { + 364 + } + } + + /// Returns the total number of weeks in the year of this ISO 8601 week + /// date. + /// + /// It is guaranteed that the value returned is either 52 or 53. The + /// latter case occurs precisely when [`ISOWeekDate::in_long_year`] + /// returns `true`. + /// + /// # Example + /// + /// ``` + /// use jiff::civil::{ISOWeekDate, Weekday}; + /// + /// let weekdate = ISOWeekDate::new(2025, 7, Weekday::Monday).unwrap(); + /// assert_eq!(weekdate.weeks_in_year(), 52); + /// let weekdate = ISOWeekDate::new(2026, 7, Weekday::Monday).unwrap(); + /// assert_eq!(weekdate.weeks_in_year(), 53); + /// ``` + #[inline] + pub fn weeks_in_year(self) -> i8 { + if self.in_long_year() { + 53 + } else { + 52 + } + } + + /// 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()) + } + + /// Returns the ISO 8601 date immediately following this one. + /// + /// # Errors + /// + /// This returns an error when this date is the maximum value. + /// + /// # Example + /// + /// ``` + /// use jiff::civil::{ISOWeekDate, Weekday}; + /// + /// let wd = ISOWeekDate::new(2025, 5, Weekday::Wednesday).unwrap(); + /// assert_eq!( + /// wd.tomorrow()?, + /// ISOWeekDate::new(2025, 5, Weekday::Thursday).unwrap(), + /// ); + /// + /// // The max doesn't have a tomorrow. + /// assert!(ISOWeekDate::MAX.tomorrow().is_err()); + /// + /// # Ok::<(), Box>(()) + /// ``` + #[inline] + pub fn tomorrow(self) -> Result { + // I suppose we could probably implement this in a more efficient + // manner but avoiding the roundtrip through Gregorian dates. + self.date().tomorrow().map(|d| d.iso_week_date()) + } + + /// Returns the ISO 8601 week date immediately preceding this one. + /// + /// # Errors + /// + /// This returns an error when this date is the minimum value. + /// + /// # Example + /// + /// ``` + /// use jiff::civil::{ISOWeekDate, Weekday}; + /// + /// let wd = ISOWeekDate::new(2025, 5, Weekday::Wednesday).unwrap(); + /// assert_eq!( + /// wd.yesterday()?, + /// ISOWeekDate::new(2025, 5, Weekday::Tuesday).unwrap(), + /// ); + /// + /// // The min doesn't have a yesterday. + /// assert!(ISOWeekDate::MIN.yesterday().is_err()); + /// + /// # Ok::<(), Box>(()) + /// ``` + #[inline] + pub fn yesterday(self) -> Result { + // I suppose we could probably implement this in a more efficient + // manner but avoiding the roundtrip through Gregorian dates. + self.date().yesterday().map(|d| d.iso_week_date()) + } + + /// 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) + } +} + +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 == C(53) && !is_long_year(year) { + return Err(Error::from(E::InvalidISOWeekNumber)); + } + // 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 == C(52) + && weekday.to_monday_zero_offset() + > Weekday::Friday.to_monday_zero_offset() + { + return Err(Error::range( + "weekday", + weekday.to_monday_one_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 == C(53) && !is_long_year(year) { + week = ISOWeek::new(52).unwrap(); + } + if year == ISOYear::MAX_SELF + && week == C(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 core::fmt::Display for ISOWeekDate { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + use crate::fmt::StdFmtWrite; + + DEFAULT_DATETIME_PRINTER + .print_iso_week_date(self, StdFmtWrite(f)) + .map_err(|_| core::fmt::Error) + } +} + +impl core::str::FromStr for ISOWeekDate { + type Err = Error; + + fn from_str(string: &str) -> Result { + DEFAULT_DATETIME_PARSER.parse_iso_week_date(string) + } +} + +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(feature = "serde")] +impl serde_core::Serialize for ISOWeekDate { + #[inline] + fn serialize( + &self, + serializer: S, + ) -> Result { + serializer.collect_str(self) + } +} + +#[cfg(feature = "serde")] +impl<'de> serde_core::Deserialize<'de> for ISOWeekDate { + #[inline] + fn deserialize>( + deserializer: D, + ) -> Result { + use serde_core::de; + + struct ISOWeekDateVisitor; + + impl<'de> de::Visitor<'de> for ISOWeekDateVisitor { + type Value = ISOWeekDate; + + fn expecting( + &self, + f: &mut core::fmt::Formatter, + ) -> core::fmt::Result { + f.write_str("an ISO 8601 week date string") + } + + #[inline] + fn visit_bytes( + self, + value: &[u8], + ) -> Result { + DEFAULT_DATETIME_PARSER + .parse_iso_week_date(value) + .map_err(de::Error::custom) + } + + #[inline] + fn visit_str( + self, + value: &str, + ) -> Result { + self.visit_bytes(value.as_bytes()) + } + } + + deserializer.deserialize_str(ISOWeekDateVisitor) + } +} + +#[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.rinto(), C(12).rinto(), C(31).rinto()) + .expect("last day of year is always valid"); + let weekday = last.weekday(); + weekday == Weekday::Thursday + || (last.in_leap_year() && weekday == Weekday::Friday) +} + +#[cfg(not(miri))] +#[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()) + } + } +} diff --git a/tools/vendor/jiff/src/civil/mod.rs b/tools/vendor/jiff/src/civil/mod.rs new file mode 100644 index 0000000000..468793d025 --- /dev/null +++ b/tools/vendor/jiff/src/civil/mod.rs @@ -0,0 +1,290 @@ +/*! +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) +} diff --git a/tools/vendor/jiff/src/civil/time.rs b/tools/vendor/jiff/src/civil/time.rs new file mode 100644 index 0000000000..dad74efb24 --- /dev/null +++ b/tools/vendor/jiff/src/civil/time.rs @@ -0,0 +1,3524 @@ +use core::time::Duration as UnsignedDuration; + +use crate::{ + civil::{Date, DateTime}, + duration::{Duration, SDuration}, + error::{civil::Error as E, Error, ErrorContext}, + fmt::{ + self, + temporal::{self, DEFAULT_DATETIME_PARSER}, + }, + shared::util::itime::{ITime, ITimeNanosecond, ITimeSecond}, + util::{ + rangeint::{self, Composite, RFrom, RInto, TryRFrom}, + round::increment, + t::{ + self, CivilDayNanosecond, CivilDaySecond, 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::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. + /// + /// # Panics + /// + /// This routine panics when [`Date::new`] with the given inputs 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::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.rinto()) + } + + #[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.rinto()) + } + + #[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.rinto()) + } + + /// 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.rinto()) + } + + /// 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 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) + .context(E::OverflowTimeNanoseconds)?; + 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.rinto()); + 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> { + if self.subsec_nanosecond() != 0 || duration.subsec_nanos() != 0 { + return self.overflowing_add_duration_general(duration); + } + let start = t::NoUnits::rfrom(self.to_second()); + let duration_secs = t::NoUnits::new_unchecked(duration.as_secs()); + // This can fail if the duration is near its min or max values, and + // thus we fall back to the more general (but slower) implementation + // that uses 128-bit integers. + let Some(sum) = start.checked_add(duration_secs) else { + return self.overflowing_add_duration_general(duration); + }; + let days = t::SpanDays::try_new( + "overflowing-days", + sum.div_floor(t::SECONDS_PER_CIVIL_DAY), + )?; + let time_secs = sum.rem_floor(t::SECONDS_PER_CIVIL_DAY); + let time = Time::from_second(time_secs.rinto()); + // OK because of the constraint imposed by t::SpanDays. + let hours = i64::from(days).checked_mul(24).unwrap(); + Ok((time, SignedDuration::from_hours(hours))) + } + + /// 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(never)] + #[cold] + fn overflowing_add_duration_general( + 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.rinto()); + // 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