From 858786b3a9a0910ba999b0d72ec5e9ddec740a32 Mon Sep 17 00:00:00 2001 From: "github-action[bot]" Date: Sun, 20 Apr 2025 20:22:02 +0200 Subject: [PATCH] Update On Sun Apr 20 20:22:02 CEST 2025 --- Cargo.lock | 150 +- Cargo.toml | 21 +- aclocal.m4 | 14 - browser/app/macbuild/Contents/MacOS-files.in | 3 + .../places/content/clearDataForSite.css | 9 + .../places/content/clearDataForSite.js | 35 + .../places/content/clearDataForSite.xhtml | 56 + .../components/places/content/controller.js | 43 +- browser/components/places/jar.mn | 3 + .../tests/browser/browser_forgetthissite.js | 37 +- .../components/sidebar/SidebarState.sys.mjs | 20 +- browser/components/sidebar/docs/index.rst | 92 +- browser/components/sidebar/sidebar-page.mjs | 35 +- .../tests/browser/browser_history_sidebar.js | 39 +- browser/components/tests/unit/xpcshell.toml | 5 +- .../browser/browser_quicksuggest_yelp.js | 2 +- .../unit/test_quicksuggest_yelp.js | 9 +- .../unit/test_quicksuggest_yelp_ml.js | 5 +- browser/config/mozconfigs/linux32/devedition | 2 +- .../mozconfigs/linux32/nightly-fuzzing-asan | 2 +- .../config/mozconfigs/linux64/add-on-devel | 2 +- .../mozconfigs/linux64/debug-searchfox-clang | 4 +- .../linux64/debug-static-analysis-clang | 2 +- browser/config/mozconfigs/linux64/devedition | 2 +- .../mozconfigs/linux64/nightly-fuzzing-asan | 2 +- .../linux64/nightly-fuzzing-asan-noopt | 2 +- .../linux64/nightly-fuzzing-asan-nyx | 2 +- browser/config/mozconfigs/linux64/non-unified | 2 +- browser/config/mozconfigs/linux64/valgrind | 2 +- .../mozconfigs/macosx64-aarch64/add-on-devel | 2 +- .../macosx64-aarch64/cross-noopt-debug | 2 +- .../mozconfigs/macosx64-aarch64/devedition | 2 +- .../config/mozconfigs/macosx64/add-on-devel | 2 +- .../mozconfigs/macosx64/cross-noopt-debug | 2 +- .../mozconfigs/macosx64/debug-searchfox | 4 +- .../mozconfigs/macosx64/debug-static-analysis | 2 +- browser/config/mozconfigs/macosx64/devedition | 2 +- .../config/mozconfigs/macosx64/non-unified | 2 +- .../mozconfigs/macosx64/opt-static-analysis | 2 +- browser/config/mozconfigs/win32/add-on-devel | 2 +- .../mozconfigs/win32/debug-static-analysis | 2 +- browser/config/mozconfigs/win32/devedition | 2 +- browser/config/mozconfigs/win32/mingwclang | 18 +- browser/config/mozconfigs/win32/noopt-debug | 2 +- .../mozconfigs/win64-aarch64/devedition | 2 +- browser/config/mozconfigs/win64/add-on-devel | 2 +- .../config/mozconfigs/win64/debug-searchfox | 4 +- browser/config/mozconfigs/win64/devedition | 2 +- browser/config/mozconfigs/win64/mingwclang | 18 +- browser/config/mozconfigs/win64/non-unified | 2 +- browser/config/mozconfigs/win64/noopt-debug | 2 +- .../formautofill/test/browser/browser.toml | 1 + .../browser_dynamic_form_autocompletion.js | 66 + .../test/browser/browser_form_changes.js | 10 +- .../test/browser/browser_remoteiframe.js | 18 + .../browser_creditCard_doorhanger_display.js | 17 + ...wser_creditCard_telemetry_submit_update.js | 6 + .../creditCard/browser_telemetry_utils.js | 10 + .../formautofill/test/browser/head.js | 2 + .../dynamic_form_replacing_all_fields.html | 51 + browser/extensions/newtab/common/Actions.mjs | 1 + .../extensions/newtab/common/Reducers.sys.mjs | 3 + .../content-src/components/Base/Base.jsx | 10 +- .../SectionsMgmtPanel/SectionsMgmtPanel.jsx | 112 +- .../AdBannerContextMenu.jsx | 29 +- .../CardSections/CardSections.jsx | 48 +- .../_DownloadMobilePromoHighlight.scss | 10 +- .../InterestPicker/InterestPicker.jsx | 24 +- .../SectionContextMenu/SectionContextMenu.jsx | 6 +- .../DownloadModalToggle.jsx | 7 +- .../_DownloadModalToggle.scss | 36 +- .../components/Search/_Search.scss | 15 + .../components/Weather/_Weather.scss | 35 +- .../content-src/lib/link-menu-options.mjs | 24 +- .../extensions/newtab/css/activity-stream.css | 74 +- .../data/content/activity-stream.bundle.js | 237 +- browser/extensions/newtab/karma.mc.config.js | 13 +- .../newtab/lib/DiscoveryStreamFeed.sys.mjs | 75 +- .../AdBannerContextMenu.test.jsx | 17 + .../CardSections.test.jsx | 37 +- browser/installer/package-manifest.in | 6 + browser/installer/windows/nsis/shared.nsh | 1 + .../en-US/browser/clearDataForSite.ftl | 15 + browser/locales/en-US/browser/places.ftl | 10 - browser/locales/l10n-changesets.json | 226 +- build/autoconf/acgeneral.m4 | 2607 ---------------- build/autoconf/acoldnames.m4 | 80 - build/autoconf/acspecific.m4 | 2758 ----------------- build/autoconf/altoptions.m4 | 72 - build/autoconf/autoconf.m4 | 28 - build/autoconf/autoconf.sh | 158 - build/autoconf/config.status.m4 | 162 - build/autoconf/hooks.m4 | 31 - build/docs/snap.rst | 17 + build/moz.configure/init.configure | 50 +- build/moz.configure/old.configure | 395 --- build/mozconfig.artifact.automation | 2 +- build/rust/mozbuild/generate_buildconfig.py | 2 + configure.py | 44 +- .../debugger/src/components/QuickOpenModal.js | 50 +- .../inspector/rules/models/element-style.js | 74 +- .../client/inspector/rules/models/rule.js | 5 +- devtools/client/inspector/rules/rules.js | 75 +- .../inspector/rules/test/browser_part2.toml | 7 + ...nherited-element-backed-pseudo-elements.js | 313 ++ .../test/browser_rules_pseudo-element_01.js | 23 +- devtools/server/actors/page-style.js | 74 +- devtools/shared/inspector/css-logic.js | 5 + dom/events/Event.cpp | 10 +- dom/events/Event.h | 2 +- dom/events/EventStateManager.cpp | 45 +- dom/events/PointerEventHandler.cpp | 3 +- dom/ipc/BrowserParent.cpp | 16 +- dom/media/ipc/MFCDMParent.cpp | 44 +- dom/media/platforms/wmf/DXVA2Manager.cpp | 5 +- dom/quota/test/xpcshell/xpcshell.toml | 1 + dom/webgpu/Instance.cpp | 82 +- dom/webgpu/tests/cts/vendor/src/main.rs | 97 +- dom/webidl/WebGPU.webidl | 1 + gfx/layers/D3D11ShareHandleImage.cpp | 5 +- gfx/layers/d3d11/TextureD3D11.cpp | 2 - gfx/layers/ipc/CompositorBridgeParent.cpp | 4 +- gfx/layers/ipc/CompositorBridgeParent.h | 3 +- gfx/layers/wr/WebRenderBridgeParent.cpp | 2 +- gfx/layers/wr/WebRenderBridgeParent.h | 3 +- gfx/thebes/gfxFont.cpp | 6 +- gfx/webrender_bindings/RenderThread.cpp | 78 +- gfx/webrender_bindings/RenderThread.h | 69 +- gfx/webrender_bindings/RendererOGL.cpp | 117 +- gfx/webrender_bindings/RendererOGL.h | 4 +- gfx/webrender_bindings/WebRenderAPI.cpp | 9 +- gfx/webrender_bindings/src/bindings.rs | 17 +- .../example-compositor/compositor/src/main.rs | 2 +- gfx/wr/examples/common/boilerplate.rs | 7 +- gfx/wr/examples/multiwindow.rs | 7 +- gfx/wr/webrender/src/clip.rs | 16 + gfx/wr/webrender/src/render_backend.rs | 14 +- gfx/wr/webrender/src/renderer/init.rs | 2 +- gfx/wr/webrender/src/renderer/mod.rs | 10 +- gfx/wr/webrender/src/scene_builder_thread.rs | 10 +- gfx/wr/webrender/src/scene_building.rs | 53 +- gfx/wr/webrender_api/src/lib.rs | 9 +- gfx/wr/wrench/reftests/image/reftest.list | 1 + .../image/snapshot-complex-clip-ref.yaml | 53 + .../reftests/image/snapshot-complex-clip.yaml | 40 + gfx/wr/wrench/src/main.rs | 7 +- gfx/wr/wrench/src/wrench.rs | 7 +- gradle/libs.versions.toml | 2 +- ipc/glue/GeckoChildProcessHost.cpp | 15 +- js/src/aclocal.m4 | 23 - js/src/configure | 1 - js/src/devtools/rootAnalysis/mozconfig.common | 6 +- js/src/make-source-package.py | 29 - js/src/old-configure.in | 23 - js/sub.configure | 89 - js/xpconnect/src/XPCShellImpl.cpp | 17 +- layout/base/PresShell.cpp | 25 +- layout/base/TouchManager.cpp | 7 +- layout/generic/nsCanvasFrame.cpp | 18 +- layout/generic/nsCanvasFrame.h | 4 +- layout/generic/nsGridContainerFrame.cpp | 101 + layout/generic/nsGridContainerFrame.h | 131 +- layout/generic/nsIFrame.cpp | 33 +- layout/generic/nsIFrame.h | 3 +- layout/generic/nsImageFrame.cpp | 44 +- layout/generic/nsImageFrame.h | 5 +- media/libopus/celt/arch.h | 2 + media/libopus/celt/arm/arm_celt_map.c | 8 + media/libopus/celt/arm/celt_neon_intr.c | 77 + media/libopus/celt/arm/mathops_arm.h | 38 + media/libopus/celt/celt_decoder.c | 30 +- media/libopus/celt/mathops.c | 20 + media/libopus/celt/mathops.h | 6 + media/libopus/moz.build | 2 +- media/libopus/moz.yaml | 4 +- media/libopus/src/opus.c | 50 +- media/libopus/src/opus_decoder.c | 2 +- media/libopus/src/opus_encoder.c | 13 +- media/libopus/src/opus_private.h | 2 + .../base/src/main/res/values-eu/strings.xml | 11 + .../src/main/res/values-hy-rAM/strings.xml | 11 + .../base/src/main/res/values-rm/strings.xml | 11 + .../src/main/res/values-fa/strings.xml | 5 + .../src/main/res/values-th/strings.xml | 2 + .../src/main/res/values-zh-rCN/strings.xml | 22 + .../components/lib/push-firebase/build.gradle | 6 + .../src/main/java/ApplicationServices.kt | 2 +- .../dependencies/src/main/java/moz.yaml | 4 +- .../android-arm-gradle-dependencies/base | 2 +- .../android-arm/nightly-android-lints | 2 +- .../android-arm/nightly-android-lints-lite | 2 +- .../android/config/mozconfigs/common.override | 2 +- mobile/android/fenix/app/build.gradle | 2 +- .../org/mozilla/fenix/home/HomeFragment.kt | 5 + .../mozilla/fenix/home/store/HomepageState.kt | 19 +- .../fenix/home/toolbar/FenixHomeToolbar.kt | 7 + .../home/toolbar/HomeToolbarComposable.kt | 4 + .../fenix/home/toolbar/HomeToolbarView.kt | 11 + .../org/mozilla/fenix/home/ui/Homepage.kt | 44 +- .../org/mozilla/fenix/home/ui/SearchBar.kt | 4 +- .../bookmarks/ui/BookmarksMiddleware.kt | 24 +- .../src/main/res/values-en-rGB/strings.xml | 72 + .../app/src/main/res/values-eu/strings.xml | 76 + .../app/src/main/res/values-fa/strings.xml | 48 + .../src/main/res/values-hy-rAM/strings.xml | 78 +- .../app/src/main/res/values-is/strings.xml | 4 + .../app/src/main/res/values-ja/strings.xml | 56 + .../app/src/main/res/values-kk/strings.xml | 2 + .../app/src/main/res/values-ml/strings.xml | 8 + .../src/main/res/values-pt-rBR/strings.xml | 4 + .../app/src/main/res/values-rm/strings.xml | 122 + .../app/src/main/res/values-sl/strings.xml | 47 + .../app/src/main/res/values-th/strings.xml | 32 + .../app/src/main/res/values-tr/strings.xml | 4 + .../app/src/main/res/values-ug/strings.xml | 4 + .../app/src/main/res/values-vi/strings.xml | 24 + .../src/main/res/values-zh-rCN/strings.xml | 42 + .../app/src/main/res/values-eo/strings.xml | 1 + .../src/main/res/values-zh-rCN/strings.xml | 2 + mobile/android/geckoview/api.txt | 2 + .../geckoview/test/TestRuntimeService.java | 6 +- .../test/crash/RuntimeCrashTestService.kt | 4 +- .../geckoview/src/main/AndroidManifest.xml | 8 + .../gecko/crashhelper/ICrashHelper.aidl | 9 + .../java/org/mozilla/gecko/GeckoThread.java | 69 +- .../gecko/crashhelper/CrashHelper.java | 185 ++ .../mozilla/gecko/mozglue/GeckoLoader.java | 7 +- .../gecko/process/GeckoProcessManager.java | 9 +- .../process/GeckoServiceChildProcess.java | 17 +- .../org/mozilla/geckoview/GeckoRuntime.java | 36 + .../geckoview/GeckoRuntimeSettings.java | 25 + .../mozilla/geckoview/doc-files/CHANGELOG.md | 5 +- mobile/android/installer/package-manifest.in | 1 + .../test_runner/TestRunnerActivity.java | 3 +- .../XpcshellTestRunnerService.java | 1 + .../ios/config/mozconfigs/ios/debug-searchfox | 4 +- mobile/locales/l10n-changesets.json | 196 +- .../test/xpcshell/test_ChildCrashHandler.js | 4 +- modules/libpref/init/StaticPrefList.yaml | 2 +- moz.build | 3 - moz.configure | 10 - mozglue/android/APKOpen.cpp | 35 +- netwerk/test/unit/test_socks.js | 14 +- old-configure.in | 18 - python/mozboot/mozboot/android.py | 8 +- python/mozboot/mozboot/mozconfig.py | 3 + .../mozbuild/mozbuild/action/check_binary.py | 16 +- python/mozbuild/mozbuild/artifacts.py | 2 + .../mozbuild/test/configure/common.py | 1 - .../mozbuild/mozbuild/test/configure/lint.py | 1 - .../test/configure/test_checks_configure.py | 2 +- .../hardenedruntime/v2/developer/utility.xml | 3 +- supply-chain/audits.toml | 23 + supply-chain/imports.lock | 12 + taskcluster/config.yml | 17 +- .../docker/snap-coreXX-build/Dockerfile | 8 +- taskcluster/docker/snap-coreXX-build/parse.py | 8 +- taskcluster/docker/snap-coreXX-build/run.sh | 20 +- .../docker/snap-coreXX-build/system-setup.sh | 42 + .../gecko_taskgraph/transforms/snap_test.py | 30 +- taskcluster/kinds/fetch/toolchains.yml | 6 +- .../kinds/snap-upstream-build/kind.yml | 234 +- taskcluster/kinds/snap-upstream-test/kind.yml | 19 +- .../kinds/test/browsertime-desktop.yml | 4 + taskcluster/kinds/toolchain/android.yml | 2 +- testing/mozbase/mozcrash/mozcrash/mozcrash.py | 2 +- testing/perfdocs/generated/raptor-metrics.rst | 2 +- testing/perfdocs/generated/raptor.rst | 822 ++++- .../android-resource/parse_resource_usage.py | 6 +- testing/raptor/browsertime/indexeddb_open.js | 119 + testing/raptor/raptor/perfdocs/config.yml | 4 + .../tests/custom/browsertime-indexeddb.toml | 20 + .../content-with-clip.html.ini | 2 + ...f-viewport-partially-onscreen-old.html.ini | 4 +- ...f-viewport-partially-onscreen-old.html.ini | 4 +- ...w-content-captures-different-size.html.ini | 3 +- .../old-content-captures-clip-path.html.ini | 2 - .../adapter/requestAdapter/cts.https.html.ini | 5 - .../display-contents-pseudo-click-target.html | 43 + .../caret/move-around-generated-content.html | 260 ++ testing/xpcshell/runxpcshelltests.py | 1 + testing/xpcshell/selftest.py | 4 +- .../rust/android-tzdata/.cargo-checksum.json | 1 + third_party/rust/android-tzdata/Cargo.toml | 34 + .../rust/android-tzdata/LICENSE-APACHE | 201 ++ third_party/rust/android-tzdata/LICENSE-MIT | 21 + third_party/rust/android-tzdata/README.md | 20 + third_party/rust/android-tzdata/src/lib.rs | 29 + third_party/rust/android-tzdata/src/tzdata.rs | 166 + third_party/rust/chrono/.cargo-checksum.json | 2 +- third_party/rust/chrono/AUTHORS.txt | 41 - third_party/rust/chrono/CHANGELOG.md | 740 ----- third_party/rust/chrono/CITATION.cff | 33 + third_party/rust/chrono/Cargo.lock | 808 +++++ third_party/rust/chrono/Cargo.toml | 186 +- third_party/rust/chrono/LICENSE.txt | 2 +- third_party/rust/chrono/README.md | 419 --- third_party/rust/chrono/benches/chrono.rs | 116 - third_party/rust/chrono/benches/serde.rs | 30 - third_party/rust/chrono/rustfmt.toml | 1 - third_party/rust/chrono/src/date.rs | 293 +- third_party/rust/chrono/src/datetime.rs | 2589 ---------------- third_party/rust/chrono/src/datetime/mod.rs | 1940 ++++++++++++ third_party/rust/chrono/src/datetime/serde.rs | 1359 ++++++++ third_party/rust/chrono/src/datetime/tests.rs | 1902 ++++++++++++ third_party/rust/chrono/src/div.rs | 41 - .../rust/chrono/src/format/formatting.rs | 945 ++++++ third_party/rust/chrono/src/format/locales.rs | 126 +- third_party/rust/chrono/src/format/mod.rs | 745 ++--- third_party/rust/chrono/src/format/parse.rs | 1916 +++++++++--- third_party/rust/chrono/src/format/parsed.rs | 1277 ++++++-- third_party/rust/chrono/src/format/scan.rs | 350 ++- .../rust/chrono/src/format/strftime.rs | 1319 +++++--- third_party/rust/chrono/src/lib.rs | 1883 ++++------- third_party/rust/chrono/src/month.rs | 472 +++ third_party/rust/chrono/src/naive/date.rs | 2392 -------------- third_party/rust/chrono/src/naive/date/mod.rs | 2534 +++++++++++++++ .../rust/chrono/src/naive/date/tests.rs | 879 ++++++ third_party/rust/chrono/src/naive/datetime.rs | 2507 --------------- .../rust/chrono/src/naive/datetime/mod.rs | 2151 +++++++++++++ .../rust/chrono/src/naive/datetime/serde.rs | 1304 ++++++++ .../rust/chrono/src/naive/datetime/tests.rs | 408 +++ .../rust/chrono/src/naive/internals.rs | 622 ++-- third_party/rust/chrono/src/naive/isoweek.rs | 206 +- third_party/rust/chrono/src/naive/mod.rs | 281 ++ third_party/rust/chrono/src/naive/time.rs | 1814 ----------- third_party/rust/chrono/src/naive/time/mod.rs | 1643 ++++++++++ .../rust/chrono/src/naive/time/serde.rs | 143 + .../rust/chrono/src/naive/time/tests.rs | 393 +++ third_party/rust/chrono/src/offset/fixed.rs | 220 +- third_party/rust/chrono/src/offset/local.rs | 227 -- .../rust/chrono/src/offset/local/mod.rs | 541 ++++ .../chrono/src/offset/local/tz_info/mod.rs | 116 + .../chrono/src/offset/local/tz_info/parser.rs | 348 +++ .../chrono/src/offset/local/tz_info/rule.rs | 1037 +++++++ .../src/offset/local/tz_info/timezone.rs | 1007 ++++++ .../rust/chrono/src/offset/local/unix.rs | 171 + .../chrono/src/offset/local/win_bindings.rs | 49 + .../chrono/src/offset/local/win_bindings.txt | 7 + .../rust/chrono/src/offset/local/windows.rs | 293 ++ third_party/rust/chrono/src/offset/mod.rs | 508 ++- third_party/rust/chrono/src/offset/utc.rs | 104 +- third_party/rust/chrono/src/oldtime.rs | 684 ---- third_party/rust/chrono/src/round.rs | 928 +++++- third_party/rust/chrono/src/sys.rs | 126 - third_party/rust/chrono/src/sys/stub.rs | 80 - third_party/rust/chrono/src/sys/unix.rs | 126 - third_party/rust/chrono/src/sys/windows.rs | 131 - third_party/rust/chrono/src/time_delta.rs | 1354 ++++++++ third_party/rust/chrono/src/traits.rs | 393 +++ third_party/rust/chrono/src/weekday.rs | 408 +++ third_party/rust/chrono/tests/dateutils.rs | 165 + third_party/rust/chrono/tests/wasm.rs | 142 +- third_party/rust/chrono/tests/win_bindings.rs | 28 + .../iana-time-zone-haiku/.cargo-checksum.json | 1 + .../rust/iana-time-zone-haiku/Cargo.toml | 34 + .../rust/iana-time-zone-haiku/LICENSE-APACHE | 201 ++ .../rust/iana-time-zone-haiku/LICENSE-MIT | 25 + .../rust/iana-time-zone-haiku/README.md | 8 + .../rust/iana-time-zone-haiku/build.rs | 22 + .../src/implementation.cc | 67 + .../rust/iana-time-zone-haiku/src/lib.rs | 71 + .../rust/iana-time-zone/.cargo-checksum.json | 1 + third_party/rust/iana-time-zone/CHANGELOG.md | 353 +++ third_party/rust/iana-time-zone/Cargo.lock | 590 ++++ third_party/rust/iana-time-zone/Cargo.toml | 93 + .../rust/iana-time-zone/LICENSE-APACHE | 201 ++ third_party/rust/iana-time-zone/LICENSE-MIT | 25 + third_party/rust/iana-time-zone/README.md | 51 + third_party/rust/iana-time-zone/bindings.txt | 4 + third_party/rust/iana-time-zone/deny.toml | 4 + .../iana-time-zone/examples/get_timezone.rs | 6 + .../examples/get_timezone_loop.rs | 13 + .../iana-time-zone/examples/stress-test.rs | 25 + .../rust/iana-time-zone/src/ffi_utils.rs | 718 +++++ third_party/rust/iana-time-zone/src/lib.rs | 119 + .../rust/iana-time-zone/src/platform.rs | 9 + third_party/rust/iana-time-zone/src/tz_aix.rs | 7 + .../rust/iana-time-zone/src/tz_android.rs | 27 + .../rust/iana-time-zone/src/tz_darwin.rs | 179 ++ .../rust/iana-time-zone/src/tz_freebsd.rs | 7 + .../rust/iana-time-zone/src/tz_haiku.rs | 3 + .../rust/iana-time-zone/src/tz_illumos.rs | 22 + .../rust/iana-time-zone/src/tz_linux.rs | 184 ++ .../rust/iana-time-zone/src/tz_netbsd.rs | 25 + .../rust/iana-time-zone/src/tz_ohos.rs | 47 + .../iana-time-zone/src/tz_wasm32_unknown.rs | 21 + .../rust/iana-time-zone/src/tz_windows.rs | 16 + .../iana-time-zone/src/windows_bindings.rs | 1630 ++++++++++ third_party/rust/suggest/.cargo-checksum.json | 2 +- third_party/rust/suggest/src/db.rs | 5 + third_party/rust/suggest/src/rs.rs | 21 +- third_party/rust/suggest/src/schema.rs | 13 +- third_party/rust/suggest/src/store.rs | 67 +- third_party/rust/suggest/src/testing/data.rs | 6 +- third_party/rust/suggest/src/yelp.rs | 418 ++- .../rust/windows-link/.cargo-checksum.json | 1 + third_party/rust/windows-link/Cargo.lock | 7 + third_party/rust/windows-link/Cargo.toml | 44 + .../rust/windows-link/license-apache-2.0 | 201 ++ third_party/rust/windows-link/license-mit | 21 + third_party/rust/windows-link/readme.md | 26 + third_party/rust/windows-link/src/lib.rs | 39 + .../formautofill/FormAutofillChild.sys.mjs | 85 +- .../shared/AutofillFormFactory.sys.mjs | 4 + .../shared/FormAutofillHandler.sys.mjs | 10 +- .../shared/FormAutofillUtils.sys.mjs | 4 +- .../glean/api/src/private/datetime.rs | 3 + .../components/glean/api/src/private/mod.rs | 2 + .../data/suggest/yelp_val_keywords_data.json | 9 +- .../components/nimbus/ExperimentAPI.sys.mjs | 25 +- .../nimbus/lib/ExperimentManager.sys.mjs | 54 +- .../nimbus/lib/ExperimentStore.sys.mjs | 26 +- .../nimbus/test/browser/browser.toml | 2 - ...er_experiment_single_feature_enrollment.js | 83 - .../unit/test_ExperimentAPI_NimbusFeatures.js | 54 - .../nimbus/test/unit/test_ExperimentStore.js | 6 +- .../unit/test_TelemetryControllerShutdown.js | 8 + toolkit/crashreporter/ExtraFileParser.cpp | 97 + toolkit/crashreporter/ExtraFileParser.h | 56 + .../crashreporter/bionic_missing_funcs.cpp | 23 +- .../crash_generation_server.cc | 55 +- .../crash_generation_server.h | 46 +- .../crash_generation_server.cc | 11 + .../crash_generation_server.h | 4 + .../crashreporter/breakpad-client/moz.build | 10 +- .../crash_generation_server.cc | 13 +- .../crash_generation_server.h | 10 +- .../breakpad_wrapper/breakpad_wrapper.cpp | 193 ++ .../crashreporter/breakpad_wrapper/moz.build | 37 + toolkit/crashreporter/crash_annotations.rs.in | 81 + .../crash_helper/crashhelper.cpp | 67 + .../crash_helper/crashhelper_android.cpp | 51 + toolkit/crashreporter/crash_helper/moz.build | 42 + .../crash_helper_client/Cargo.toml | 22 + .../cbindgen.toml | 14 +- .../moz.build | 10 +- .../crash_helper_client/src/lib.rs | 317 ++ .../crash_helper_client/src/platform.rs | 12 + .../src/platform/android.rs | 19 + .../crash_helper_client/src/platform/unix.rs | 74 + .../src/platform/windows.rs | 160 + .../crash_helper_common/Cargo.toml | 30 + .../crash_helper_common/src/breakpad.rs | 23 + .../crash_helper_common/src/breakpad/linux.rs | 35 + .../crash_helper_common/src/breakpad/macos.rs | 48 + .../src/breakpad/unix_strings.rs | 63 + .../src/breakpad/windows.rs | 44 + .../src/breakpad/windows_strings.rs | 86 + .../crash_helper_common/src/errors.rs | 56 + .../crash_helper_common/src/ipc_channel.rs | 23 + .../src/ipc_channel/unix.rs | 45 + .../src/ipc_channel/windows.rs | 38 + .../crash_helper_common/src/ipc_connector.rs | 31 + .../src/ipc_connector/unix.rs | 195 ++ .../src/ipc_connector/windows.rs | 391 +++ .../crash_helper_common/src/ipc_listener.rs | 23 + .../src/ipc_listener/unix.rs | 78 + .../src/ipc_listener/windows.rs | 205 ++ .../crash_helper_common/src/ipc_poller.rs | 23 + .../src/ipc_poller/unix.rs | 65 + .../src/ipc_poller/windows.rs | 85 + .../crash_helper_common/src/lib.rs | 59 + .../crash_helper_common/src/messages.rs | 622 ++++ .../crash_helper_common/src/platform.rs | 25 + .../crash_helper_common/src/platform/linux.rs | 120 + .../crash_helper_common/src/platform/macos.rs | 147 + .../src/platform/windows.rs | 57 + .../crash_helper_server/Cargo.toml | 56 + .../crash_helper_server/build.rs | 168 + .../crash_helper_server/cbindgen.toml | 28 + .../crash_helper_server/moz.build | 25 + .../src/breakpad_crash_generator.rs | 135 + .../src/crash_generation.rs | 454 +++ .../src/crash_generation/windows.rs | 166 + .../crash_helper_server/src/ipc_server.rs | 100 + .../crash_helper_server/src/lib.rs | 144 + .../crash_helper_server/src/logging.rs | 15 + .../src/logging/android.rs | 12 + .../crash_helper_server/src/logging/env.rs | 58 + .../crash_helper_server/src/phc.rs | 100 + .../src/common/linux/file_id.cc | 1 + toolkit/crashreporter/moz.build | 22 +- .../mozannotation_client/Cargo.toml | 1 - .../mozannotation_client/cbindgen.toml | 4 +- .../mozannotation_client/src/lib.rs | 32 +- .../mozannotation_server/Cargo.toml | 5 +- .../mozannotation_server/src/lib.rs | 98 +- toolkit/crashreporter/mozwer-rust/Cargo.toml | 4 +- toolkit/crashreporter/mozwer-rust/lib.rs | 151 +- toolkit/crashreporter/mozwer-rust/moz.build | 2 +- .../crashreporter/nsDummyExceptionHandler.cpp | 23 +- toolkit/crashreporter/nsExceptionHandler.cpp | 839 ++--- toolkit/crashreporter/nsExceptionHandler.h | 36 +- .../crashreporter/process_reader/Cargo.toml | 2 +- .../crashreporter/process_reader/src/lib.rs | 4 +- .../process_reader/src/platform/linux.rs | 7 +- .../process_reader/src/platform/macos.rs | 2 +- .../process_reader/src/platform/windows.rs | 5 +- toolkit/library/rust/shared/Cargo.toml | 3 +- toolkit/library/rust/shared/lib.rs | 3 +- toolkit/moz.configure | 4 +- toolkit/mozapps/extensions/moz.build | 16 + .../update/tests/data/xpcshellUtilsAUS.js | 3 - toolkit/xre/Bootstrap.h | 6 + toolkit/xre/GeckoArgs.h | 13 +- toolkit/xre/nsAppRunner.cpp | 5 + toolkit/xre/nsEmbedFunctions.cpp | 10 +- toolkit/xre/test/test_install_hash.js | 4 +- tools/rusttests/config/mozconfigs/common | 4 +- .../config/mozconfigs/linux64/rusttests | 3 + widget/gtk/nsDragService.cpp | 2 + widget/gtk/nsWindow.cpp | 5 +- xpcom/build/XREShellData.h | 2 + 514 files changed, 47320 insertions(+), 25959 deletions(-) delete mode 100644 aclocal.m4 create mode 100644 browser/components/places/content/clearDataForSite.css create mode 100644 browser/components/places/content/clearDataForSite.js create mode 100644 browser/components/places/content/clearDataForSite.xhtml create mode 100644 browser/extensions/formautofill/test/fixtures/dynamic_form_replacing_all_fields.html create mode 100644 browser/locales/en-US/browser/clearDataForSite.ftl delete mode 100644 build/autoconf/acgeneral.m4 delete mode 100644 build/autoconf/acoldnames.m4 delete mode 100644 build/autoconf/acspecific.m4 delete mode 100644 build/autoconf/altoptions.m4 delete mode 100644 build/autoconf/autoconf.m4 delete mode 100644 build/autoconf/autoconf.sh delete mode 100644 build/autoconf/config.status.m4 delete mode 100644 build/autoconf/hooks.m4 delete mode 100644 build/moz.configure/old.configure create mode 100644 devtools/client/inspector/rules/test/browser_rules_inherited-element-backed-pseudo-elements.js create mode 100644 gfx/wr/wrench/reftests/image/snapshot-complex-clip-ref.yaml create mode 100644 gfx/wr/wrench/reftests/image/snapshot-complex-clip.yaml delete mode 100644 js/src/aclocal.m4 delete mode 100644 js/src/old-configure.in delete mode 100644 js/sub.configure create mode 100644 mobile/android/android-components/components/compose/base/src/main/res/values-eu/strings.xml create mode 100644 mobile/android/android-components/components/compose/base/src/main/res/values-hy-rAM/strings.xml create mode 100644 mobile/android/android-components/components/compose/base/src/main/res/values-rm/strings.xml create mode 100644 mobile/android/geckoview/src/main/aidl/org/mozilla/gecko/crashhelper/ICrashHelper.aidl create mode 100644 mobile/android/geckoview/src/main/java/org/mozilla/gecko/crashhelper/CrashHelper.java delete mode 100644 old-configure.in create mode 100755 taskcluster/docker/snap-coreXX-build/system-setup.sh create mode 100644 testing/raptor/browsertime/indexeddb_open.js create mode 100644 testing/web-platform/meta/css/css-view-transitions/content-with-clip.html.ini delete mode 100644 testing/web-platform/meta/css/css-view-transitions/old-content-captures-clip-path.html.ini create mode 100644 testing/web-platform/tests/css/css-display/display-contents-pseudo-click-target.html create mode 100644 testing/web-platform/tests/selection/caret/move-around-generated-content.html create mode 100644 third_party/rust/android-tzdata/.cargo-checksum.json create mode 100644 third_party/rust/android-tzdata/Cargo.toml create mode 100644 third_party/rust/android-tzdata/LICENSE-APACHE create mode 100644 third_party/rust/android-tzdata/LICENSE-MIT create mode 100644 third_party/rust/android-tzdata/README.md create mode 100644 third_party/rust/android-tzdata/src/lib.rs create mode 100644 third_party/rust/android-tzdata/src/tzdata.rs delete mode 100644 third_party/rust/chrono/AUTHORS.txt delete mode 100644 third_party/rust/chrono/CHANGELOG.md create mode 100644 third_party/rust/chrono/CITATION.cff create mode 100644 third_party/rust/chrono/Cargo.lock delete mode 100644 third_party/rust/chrono/README.md delete mode 100644 third_party/rust/chrono/benches/chrono.rs delete mode 100644 third_party/rust/chrono/benches/serde.rs delete mode 100644 third_party/rust/chrono/rustfmt.toml delete mode 100644 third_party/rust/chrono/src/datetime.rs create mode 100644 third_party/rust/chrono/src/datetime/mod.rs create mode 100644 third_party/rust/chrono/src/datetime/serde.rs create mode 100644 third_party/rust/chrono/src/datetime/tests.rs delete mode 100644 third_party/rust/chrono/src/div.rs create mode 100644 third_party/rust/chrono/src/format/formatting.rs create mode 100644 third_party/rust/chrono/src/month.rs delete mode 100644 third_party/rust/chrono/src/naive/date.rs create mode 100644 third_party/rust/chrono/src/naive/date/mod.rs create mode 100644 third_party/rust/chrono/src/naive/date/tests.rs delete mode 100644 third_party/rust/chrono/src/naive/datetime.rs create mode 100644 third_party/rust/chrono/src/naive/datetime/mod.rs create mode 100644 third_party/rust/chrono/src/naive/datetime/serde.rs create mode 100644 third_party/rust/chrono/src/naive/datetime/tests.rs create mode 100644 third_party/rust/chrono/src/naive/mod.rs delete mode 100644 third_party/rust/chrono/src/naive/time.rs create mode 100644 third_party/rust/chrono/src/naive/time/mod.rs create mode 100644 third_party/rust/chrono/src/naive/time/serde.rs create mode 100644 third_party/rust/chrono/src/naive/time/tests.rs delete mode 100644 third_party/rust/chrono/src/offset/local.rs create mode 100644 third_party/rust/chrono/src/offset/local/mod.rs create mode 100644 third_party/rust/chrono/src/offset/local/tz_info/mod.rs create mode 100644 third_party/rust/chrono/src/offset/local/tz_info/parser.rs create mode 100644 third_party/rust/chrono/src/offset/local/tz_info/rule.rs create mode 100644 third_party/rust/chrono/src/offset/local/tz_info/timezone.rs create mode 100644 third_party/rust/chrono/src/offset/local/unix.rs create mode 100644 third_party/rust/chrono/src/offset/local/win_bindings.rs create mode 100644 third_party/rust/chrono/src/offset/local/win_bindings.txt create mode 100644 third_party/rust/chrono/src/offset/local/windows.rs delete mode 100644 third_party/rust/chrono/src/oldtime.rs delete mode 100644 third_party/rust/chrono/src/sys.rs delete mode 100644 third_party/rust/chrono/src/sys/stub.rs delete mode 100644 third_party/rust/chrono/src/sys/unix.rs delete mode 100644 third_party/rust/chrono/src/sys/windows.rs create mode 100644 third_party/rust/chrono/src/time_delta.rs create mode 100644 third_party/rust/chrono/src/traits.rs create mode 100644 third_party/rust/chrono/src/weekday.rs create mode 100644 third_party/rust/chrono/tests/dateutils.rs create mode 100644 third_party/rust/chrono/tests/win_bindings.rs create mode 100644 third_party/rust/iana-time-zone-haiku/.cargo-checksum.json create mode 100644 third_party/rust/iana-time-zone-haiku/Cargo.toml create mode 100644 third_party/rust/iana-time-zone-haiku/LICENSE-APACHE create mode 100644 third_party/rust/iana-time-zone-haiku/LICENSE-MIT create mode 100644 third_party/rust/iana-time-zone-haiku/README.md create mode 100644 third_party/rust/iana-time-zone-haiku/build.rs create mode 100644 third_party/rust/iana-time-zone-haiku/src/implementation.cc create mode 100644 third_party/rust/iana-time-zone-haiku/src/lib.rs create mode 100644 third_party/rust/iana-time-zone/.cargo-checksum.json create mode 100644 third_party/rust/iana-time-zone/CHANGELOG.md create mode 100644 third_party/rust/iana-time-zone/Cargo.lock create mode 100644 third_party/rust/iana-time-zone/Cargo.toml create mode 100644 third_party/rust/iana-time-zone/LICENSE-APACHE create mode 100644 third_party/rust/iana-time-zone/LICENSE-MIT create mode 100644 third_party/rust/iana-time-zone/README.md create mode 100644 third_party/rust/iana-time-zone/bindings.txt create mode 100644 third_party/rust/iana-time-zone/deny.toml create mode 100644 third_party/rust/iana-time-zone/examples/get_timezone.rs create mode 100644 third_party/rust/iana-time-zone/examples/get_timezone_loop.rs create mode 100644 third_party/rust/iana-time-zone/examples/stress-test.rs create mode 100644 third_party/rust/iana-time-zone/src/ffi_utils.rs create mode 100644 third_party/rust/iana-time-zone/src/lib.rs create mode 100644 third_party/rust/iana-time-zone/src/platform.rs create mode 100644 third_party/rust/iana-time-zone/src/tz_aix.rs create mode 100644 third_party/rust/iana-time-zone/src/tz_android.rs create mode 100644 third_party/rust/iana-time-zone/src/tz_darwin.rs create mode 100644 third_party/rust/iana-time-zone/src/tz_freebsd.rs create mode 100644 third_party/rust/iana-time-zone/src/tz_haiku.rs create mode 100644 third_party/rust/iana-time-zone/src/tz_illumos.rs create mode 100644 third_party/rust/iana-time-zone/src/tz_linux.rs create mode 100644 third_party/rust/iana-time-zone/src/tz_netbsd.rs create mode 100644 third_party/rust/iana-time-zone/src/tz_ohos.rs create mode 100644 third_party/rust/iana-time-zone/src/tz_wasm32_unknown.rs create mode 100644 third_party/rust/iana-time-zone/src/tz_windows.rs create mode 100644 third_party/rust/iana-time-zone/src/windows_bindings.rs create mode 100644 third_party/rust/windows-link/.cargo-checksum.json create mode 100644 third_party/rust/windows-link/Cargo.lock create mode 100644 third_party/rust/windows-link/Cargo.toml create mode 100644 third_party/rust/windows-link/license-apache-2.0 create mode 100644 third_party/rust/windows-link/license-mit create mode 100644 third_party/rust/windows-link/readme.md create mode 100644 third_party/rust/windows-link/src/lib.rs delete mode 100644 toolkit/components/nimbus/test/browser/browser_experiment_single_feature_enrollment.js create mode 100644 toolkit/crashreporter/ExtraFileParser.cpp create mode 100644 toolkit/crashreporter/ExtraFileParser.h create mode 100644 toolkit/crashreporter/breakpad_wrapper/breakpad_wrapper.cpp create mode 100644 toolkit/crashreporter/breakpad_wrapper/moz.build create mode 100644 toolkit/crashreporter/crash_annotations.rs.in create mode 100644 toolkit/crashreporter/crash_helper/crashhelper.cpp create mode 100644 toolkit/crashreporter/crash_helper/crashhelper_android.cpp create mode 100644 toolkit/crashreporter/crash_helper/moz.build create mode 100644 toolkit/crashreporter/crash_helper_client/Cargo.toml rename toolkit/crashreporter/{mozannotation_server => crash_helper_client}/cbindgen.toml (61%) rename toolkit/crashreporter/{mozannotation_server => crash_helper_client}/moz.build (58%) create mode 100644 toolkit/crashreporter/crash_helper_client/src/lib.rs create mode 100644 toolkit/crashreporter/crash_helper_client/src/platform.rs create mode 100644 toolkit/crashreporter/crash_helper_client/src/platform/android.rs create mode 100644 toolkit/crashreporter/crash_helper_client/src/platform/unix.rs create mode 100644 toolkit/crashreporter/crash_helper_client/src/platform/windows.rs create mode 100644 toolkit/crashreporter/crash_helper_common/Cargo.toml create mode 100644 toolkit/crashreporter/crash_helper_common/src/breakpad.rs create mode 100644 toolkit/crashreporter/crash_helper_common/src/breakpad/linux.rs create mode 100644 toolkit/crashreporter/crash_helper_common/src/breakpad/macos.rs create mode 100644 toolkit/crashreporter/crash_helper_common/src/breakpad/unix_strings.rs create mode 100644 toolkit/crashreporter/crash_helper_common/src/breakpad/windows.rs create mode 100644 toolkit/crashreporter/crash_helper_common/src/breakpad/windows_strings.rs create mode 100644 toolkit/crashreporter/crash_helper_common/src/errors.rs create mode 100644 toolkit/crashreporter/crash_helper_common/src/ipc_channel.rs create mode 100644 toolkit/crashreporter/crash_helper_common/src/ipc_channel/unix.rs create mode 100644 toolkit/crashreporter/crash_helper_common/src/ipc_channel/windows.rs create mode 100644 toolkit/crashreporter/crash_helper_common/src/ipc_connector.rs create mode 100644 toolkit/crashreporter/crash_helper_common/src/ipc_connector/unix.rs create mode 100644 toolkit/crashreporter/crash_helper_common/src/ipc_connector/windows.rs create mode 100644 toolkit/crashreporter/crash_helper_common/src/ipc_listener.rs create mode 100644 toolkit/crashreporter/crash_helper_common/src/ipc_listener/unix.rs create mode 100644 toolkit/crashreporter/crash_helper_common/src/ipc_listener/windows.rs create mode 100644 toolkit/crashreporter/crash_helper_common/src/ipc_poller.rs create mode 100644 toolkit/crashreporter/crash_helper_common/src/ipc_poller/unix.rs create mode 100644 toolkit/crashreporter/crash_helper_common/src/ipc_poller/windows.rs create mode 100644 toolkit/crashreporter/crash_helper_common/src/lib.rs create mode 100644 toolkit/crashreporter/crash_helper_common/src/messages.rs create mode 100644 toolkit/crashreporter/crash_helper_common/src/platform.rs create mode 100644 toolkit/crashreporter/crash_helper_common/src/platform/linux.rs create mode 100644 toolkit/crashreporter/crash_helper_common/src/platform/macos.rs create mode 100644 toolkit/crashreporter/crash_helper_common/src/platform/windows.rs create mode 100644 toolkit/crashreporter/crash_helper_server/Cargo.toml create mode 100644 toolkit/crashreporter/crash_helper_server/build.rs create mode 100644 toolkit/crashreporter/crash_helper_server/cbindgen.toml create mode 100644 toolkit/crashreporter/crash_helper_server/moz.build create mode 100644 toolkit/crashreporter/crash_helper_server/src/breakpad_crash_generator.rs create mode 100644 toolkit/crashreporter/crash_helper_server/src/crash_generation.rs create mode 100644 toolkit/crashreporter/crash_helper_server/src/crash_generation/windows.rs create mode 100644 toolkit/crashreporter/crash_helper_server/src/ipc_server.rs create mode 100644 toolkit/crashreporter/crash_helper_server/src/lib.rs create mode 100644 toolkit/crashreporter/crash_helper_server/src/logging.rs create mode 100644 toolkit/crashreporter/crash_helper_server/src/logging/android.rs create mode 100644 toolkit/crashreporter/crash_helper_server/src/logging/env.rs create mode 100644 toolkit/crashreporter/crash_helper_server/src/phc.rs diff --git a/Cargo.lock b/Cargo.lock index 12d1c711575..a6489d29e07 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -54,6 +54,12 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + [[package]] name = "android_log-sys" version = "0.2.0" @@ -782,16 +788,17 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.19" +version = "0.4.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" dependencies = [ - "libc", - "num-integer", + "android-tzdata", + "iana-time-zone", + "js-sys", "num-traits", "serde", - "time 0.1.45", - "winapi", + "wasm-bindgen", + "windows-link", ] [[package]] @@ -1082,6 +1089,60 @@ dependencies = [ "mach2", ] +[[package]] +name = "crash_helper_client" +version = "0.1.0" +dependencies = [ + "anyhow", + "crash_helper_common", + "minidump-writer", + "nix 0.29.0", + "num-derive", + "num-traits", + "rust_minidump_writer_linux", + "windows-sys", +] + +[[package]] +name = "crash_helper_common" +version = "0.1.0" +dependencies = [ + "minidump-writer", + "nix 0.29.0", + "num-derive", + "num-traits", + "thiserror 2.0.9", + "windows-sys", +] + +[[package]] +name = "crash_helper_server" +version = "0.1.0" +dependencies = [ + "android_logger", + "anyhow", + "cc", + "cfg-if", + "crash_helper_common", + "dirs", + "env_logger", + "linked-hash-map", + "log", + "minidump-writer", + "mozannotation_server", + "mozbuild", + "mozilla-central-workspace-hack", + "nix 0.29.0", + "num-derive", + "num-traits", + "once_cell", + "rust_minidump_writer_linux", + "thiserror 2.0.9", + "uuid", + "windows-sys", + "yaml-rust", +] + [[package]] name = "crashreporter" version = "1.0.0" @@ -1752,6 +1813,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" dependencies = [ "log", + "regex", "termcolor", ] @@ -1792,7 +1854,7 @@ dependencies = [ [[package]] name = "error-support" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=6a007c98292fa72965d36389ce32d7609e399217#6a007c98292fa72965d36389ce32d7609e399217" +source = "git+https://github.com/mozilla/application-services?rev=e1b42aa3292a71e788bc0f95fb5ab5fbe533996f#e1b42aa3292a71e788bc0f95fb5ab5fbe533996f" dependencies = [ "error-support-macros", "lazy_static", @@ -1804,7 +1866,7 @@ dependencies = [ [[package]] name = "error-support-macros" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=6a007c98292fa72965d36389ce32d7609e399217#6a007c98292fa72965d36389ce32d7609e399217" +source = "git+https://github.com/mozilla/application-services?rev=e1b42aa3292a71e788bc0f95fb5ab5fbe533996f#e1b42aa3292a71e788bc0f95fb5ab5fbe533996f" dependencies = [ "proc-macro2", "quote", @@ -1921,7 +1983,7 @@ dependencies = [ [[package]] name = "firefox-versioning" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=6a007c98292fa72965d36389ce32d7609e399217#6a007c98292fa72965d36389ce32d7609e399217" +source = "git+https://github.com/mozilla/application-services?rev=e1b42aa3292a71e788bc0f95fb5ab5fbe533996f#e1b42aa3292a71e788bc0f95fb5ab5fbe533996f" dependencies = [ "serde_json", "thiserror 1.999.999", @@ -2437,6 +2499,7 @@ dependencies = [ "cert_storage", "chardetng_c", "cose-c", + "crash_helper_client", "crypto_hash", "cubeb-coreaudio", "cubeb-pulse", @@ -2973,6 +3036,30 @@ dependencies = [ "want", ] +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +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 = "icu_calendar" version = "1.5.2" @@ -3233,7 +3320,7 @@ dependencies = [ [[package]] name = "interrupt-support" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=6a007c98292fa72965d36389ce32d7609e399217#6a007c98292fa72965d36389ce32d7609e399217" +source = "git+https://github.com/mozilla/application-services?rev=e1b42aa3292a71e788bc0f95fb5ab5fbe533996f#e1b42aa3292a71e788bc0f95fb5ab5fbe533996f" dependencies = [ "lazy_static", "parking_lot", @@ -4261,19 +4348,15 @@ dependencies = [ [[package]] name = "mozannotation_client" version = "0.1.0" -dependencies = [ - "nsstring", -] [[package]] name = "mozannotation_server" version = "0.1.0" dependencies = [ - "memoffset 0.8.999", + "memoffset 0.9.0", "mozannotation_client", "process_reader", - "thin-vec", - "thiserror 1.999.999", + "thiserror 2.0.9", ] [[package]] @@ -4435,9 +4518,9 @@ dependencies = [ name = "mozwer_s" version = "0.1.0" dependencies = [ + "crash_helper_client", "libc", "mozilla-central-workspace-hack", - "process_reader", "rust-ini", "serde", "serde_json", @@ -4685,6 +4768,7 @@ dependencies = [ "cfg-if", "cfg_aliases", "libc", + "memoffset 0.9.0", ] [[package]] @@ -4944,7 +5028,7 @@ checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba" [[package]] name = "payload-support" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=6a007c98292fa72965d36389ce32d7609e399217#6a007c98292fa72965d36389ce32d7609e399217" +source = "git+https://github.com/mozilla/application-services?rev=e1b42aa3292a71e788bc0f95fb5ab5fbe533996f#e1b42aa3292a71e788bc0f95fb5ab5fbe533996f" dependencies = [ "serde", "serde_derive", @@ -5186,7 +5270,7 @@ dependencies = [ "memoffset 0.9.0", "mozilla-central-workspace-hack", "scroll", - "thiserror 1.999.999", + "thiserror 2.0.9", "windows-sys", ] @@ -5447,7 +5531,7 @@ checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" [[package]] name = "relevancy" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=6a007c98292fa72965d36389ce32d7609e399217#6a007c98292fa72965d36389ce32d7609e399217" +source = "git+https://github.com/mozilla/application-services?rev=e1b42aa3292a71e788bc0f95fb5ab5fbe533996f#e1b42aa3292a71e788bc0f95fb5ab5fbe533996f" dependencies = [ "anyhow", "base64 0.21.999", @@ -5472,7 +5556,7 @@ dependencies = [ [[package]] name = "remote_settings" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=6a007c98292fa72965d36389ce32d7609e399217#6a007c98292fa72965d36389ce32d7609e399217" +source = "git+https://github.com/mozilla/application-services?rev=e1b42aa3292a71e788bc0f95fb5ab5fbe533996f#e1b42aa3292a71e788bc0f95fb5ab5fbe533996f" dependencies = [ "anyhow", "camino", @@ -5820,7 +5904,7 @@ dependencies = [ [[package]] name = "search" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=6a007c98292fa72965d36389ce32d7609e399217#6a007c98292fa72965d36389ce32d7609e399217" +source = "git+https://github.com/mozilla/application-services?rev=e1b42aa3292a71e788bc0f95fb5ab5fbe533996f#e1b42aa3292a71e788bc0f95fb5ab5fbe533996f" dependencies = [ "error-support", "firefox-versioning", @@ -6111,7 +6195,7 @@ dependencies = [ [[package]] name = "sql-support" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=6a007c98292fa72965d36389ce32d7609e399217#6a007c98292fa72965d36389ce32d7609e399217" +source = "git+https://github.com/mozilla/application-services?rev=e1b42aa3292a71e788bc0f95fb5ab5fbe533996f#e1b42aa3292a71e788bc0f95fb5ab5fbe533996f" dependencies = [ "interrupt-support", "lazy_static", @@ -6317,7 +6401,7 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "suggest" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=6a007c98292fa72965d36389ce32d7609e399217#6a007c98292fa72965d36389ce32d7609e399217" +source = "git+https://github.com/mozilla/application-services?rev=e1b42aa3292a71e788bc0f95fb5ab5fbe533996f#e1b42aa3292a71e788bc0f95fb5ab5fbe533996f" dependencies = [ "anyhow", "chrono", @@ -6369,7 +6453,7 @@ dependencies = [ [[package]] name = "sync-guid" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=6a007c98292fa72965d36389ce32d7609e399217#6a007c98292fa72965d36389ce32d7609e399217" +source = "git+https://github.com/mozilla/application-services?rev=e1b42aa3292a71e788bc0f95fb5ab5fbe533996f#e1b42aa3292a71e788bc0f95fb5ab5fbe533996f" dependencies = [ "base64 0.21.999", "rand", @@ -6380,7 +6464,7 @@ dependencies = [ [[package]] name = "sync15" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=6a007c98292fa72965d36389ce32d7609e399217#6a007c98292fa72965d36389ce32d7609e399217" +source = "git+https://github.com/mozilla/application-services?rev=e1b42aa3292a71e788bc0f95fb5ab5fbe533996f#e1b42aa3292a71e788bc0f95fb5ab5fbe533996f" dependencies = [ "anyhow", "error-support", @@ -6420,7 +6504,7 @@ dependencies = [ [[package]] name = "tabs" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=6a007c98292fa72965d36389ce32d7609e399217#6a007c98292fa72965d36389ce32d7609e399217" +source = "git+https://github.com/mozilla/application-services?rev=e1b42aa3292a71e788bc0f95fb5ab5fbe533996f#e1b42aa3292a71e788bc0f95fb5ab5fbe533996f" dependencies = [ "anyhow", "error-support", @@ -6764,7 +6848,7 @@ checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "types" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=6a007c98292fa72965d36389ce32d7609e399217#6a007c98292fa72965d36389ce32d7609e399217" +source = "git+https://github.com/mozilla/application-services?rev=e1b42aa3292a71e788bc0f95fb5ab5fbe533996f#e1b42aa3292a71e788bc0f95fb5ab5fbe533996f" dependencies = [ "rusqlite 0.33.0", "serde", @@ -7146,7 +7230,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "viaduct" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=6a007c98292fa72965d36389ce32d7609e399217#6a007c98292fa72965d36389ce32d7609e399217" +source = "git+https://github.com/mozilla/application-services?rev=e1b42aa3292a71e788bc0f95fb5ab5fbe533996f#e1b42aa3292a71e788bc0f95fb5ab5fbe533996f" dependencies = [ "ffi-support", "log", @@ -7316,7 +7400,7 @@ dependencies = [ [[package]] name = "webext-storage" version = "0.1.0" -source = "git+https://github.com/mozilla/application-services?rev=6a007c98292fa72965d36389ce32d7609e399217#6a007c98292fa72965d36389ce32d7609e399217" +source = "git+https://github.com/mozilla/application-services?rev=e1b42aa3292a71e788bc0f95fb5ab5fbe533996f#e1b42aa3292a71e788bc0f95fb5ab5fbe533996f" dependencies = [ "anyhow", "error-support", @@ -7655,6 +7739,12 @@ dependencies = [ "syn", ] +[[package]] +name = "windows-link" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" + [[package]] name = "windows-result" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index 72d438d6af1..a10ecb2377c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,9 +18,10 @@ members = [ "testing/geckodriver", "toolkit/components/uniffi-bindgen-gecko-js", "toolkit/crashreporter/client/app", + "toolkit/crashreporter/crash_helper_server", + "toolkit/crashreporter/crash_helper_client", "toolkit/crashreporter/minidump-analyzer/android/export", "toolkit/crashreporter/mozwer-rust", - "toolkit/crashreporter/rust_minidump_writer_linux", "toolkit/library/gtest/rust", "toolkit/library/rust/", ] @@ -259,14 +260,14 @@ malloc_size_of_derive = { path = "xpcom/rust/malloc_size_of_derive" } objc = { git = "https://github.com/glandium/rust-objc", rev = "4de89f5aa9851ceca4d40e7ac1e2759410c04324" } # application-services overrides to make updating them all simpler. -interrupt-support = { git = "https://github.com/mozilla/application-services", rev = "6a007c98292fa72965d36389ce32d7609e399217" } -relevancy = { git = "https://github.com/mozilla/application-services", rev = "6a007c98292fa72965d36389ce32d7609e399217" } -search = { git = "https://github.com/mozilla/application-services", rev = "6a007c98292fa72965d36389ce32d7609e399217" } -sql-support = { git = "https://github.com/mozilla/application-services", rev = "6a007c98292fa72965d36389ce32d7609e399217" } -suggest = { git = "https://github.com/mozilla/application-services", rev = "6a007c98292fa72965d36389ce32d7609e399217" } -sync15 = { git = "https://github.com/mozilla/application-services", rev = "6a007c98292fa72965d36389ce32d7609e399217" } -tabs = { git = "https://github.com/mozilla/application-services", rev = "6a007c98292fa72965d36389ce32d7609e399217" } -viaduct = { git = "https://github.com/mozilla/application-services", rev = "6a007c98292fa72965d36389ce32d7609e399217" } -webext-storage = { git = "https://github.com/mozilla/application-services", rev = "6a007c98292fa72965d36389ce32d7609e399217" } +interrupt-support = { git = "https://github.com/mozilla/application-services", rev = "e1b42aa3292a71e788bc0f95fb5ab5fbe533996f" } +relevancy = { git = "https://github.com/mozilla/application-services", rev = "e1b42aa3292a71e788bc0f95fb5ab5fbe533996f" } +search = { git = "https://github.com/mozilla/application-services", rev = "e1b42aa3292a71e788bc0f95fb5ab5fbe533996f" } +sql-support = { git = "https://github.com/mozilla/application-services", rev = "e1b42aa3292a71e788bc0f95fb5ab5fbe533996f" } +suggest = { git = "https://github.com/mozilla/application-services", rev = "e1b42aa3292a71e788bc0f95fb5ab5fbe533996f" } +sync15 = { git = "https://github.com/mozilla/application-services", rev = "e1b42aa3292a71e788bc0f95fb5ab5fbe533996f" } +tabs = { git = "https://github.com/mozilla/application-services", rev = "e1b42aa3292a71e788bc0f95fb5ab5fbe533996f" } +viaduct = { git = "https://github.com/mozilla/application-services", rev = "e1b42aa3292a71e788bc0f95fb5ab5fbe533996f" } +webext-storage = { git = "https://github.com/mozilla/application-services", rev = "e1b42aa3292a71e788bc0f95fb5ab5fbe533996f" } allocator-api2 = { path = "third_party/rust/allocator-api2" } diff --git a/aclocal.m4 b/aclocal.m4 deleted file mode 100644 index e365a9a22b9..00000000000 --- a/aclocal.m4 +++ /dev/null @@ -1,14 +0,0 @@ -dnl -dnl Local autoconf macros used with mozilla -dnl The contents of this file are under the Public Domain. -dnl - -builtin(include, build/autoconf/hooks.m4)dnl -builtin(include, build/autoconf/config.status.m4)dnl -builtin(include, build/autoconf/altoptions.m4)dnl - -# Read the user's .mozconfig script. We can't do this in -# configure.in: autoconf puts the argument parsing code above anything -# expanded from configure.in, and we need to get the configure options -# from .mozconfig in place before that argument parsing code. -MOZ_READ_MOZCONFIG(.) diff --git a/browser/app/macbuild/Contents/MacOS-files.in b/browser/app/macbuild/Contents/MacOS-files.in index 7517c5b42ac..2f420f77361 100644 --- a/browser/app/macbuild/Contents/MacOS-files.in +++ b/browser/app/macbuild/Contents/MacOS-files.in @@ -13,6 +13,9 @@ #if defined(MOZ_ASAN) || defined(MOZ_TSAN) || defined(FUZZING) /llvm-symbolizer #endif +#if defined(MOZ_CRASHREPORTER) +/crashhelper +#endif /nmhproxy /pingsender /pk12util diff --git a/browser/components/places/content/clearDataForSite.css b/browser/components/places/content/clearDataForSite.css new file mode 100644 index 00000000000..521dcd5b9ad --- /dev/null +++ b/browser/components/places/content/clearDataForSite.css @@ -0,0 +1,9 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#clearDataForSiteDialog::part(dialog-button) { + min-height: 24px; + align-items: center; + justify-content: center; +} diff --git a/browser/components/places/content/clearDataForSite.js b/browser/components/places/content/clearDataForSite.js new file mode 100644 index 00000000000..01afa56d1ea --- /dev/null +++ b/browser/components/places/content/clearDataForSite.js @@ -0,0 +1,35 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +let lazy = {}; +ChromeUtils.defineESModuleGetters(lazy, { + ForgetAboutSite: "resource://gre/modules/ForgetAboutSite.sys.mjs", +}); + +window.addEventListener("load", () => { + let retVals = window.arguments[0]; + + document.addEventListener("dialogaccept", e => { + e.preventDefault(); + lazy.ForgetAboutSite.removeDataFromBaseDomain(retVals.host).catch( + console.error + ); + window.close(); + }); + + document.addEventListener("dialogcancel", e => { + e.preventDefault(); + window.close(); + }); + + document.l10n.setAttributes( + document.getElementById("clear-data-for-site-list"), + "clear-data-for-site-list", + { + site: retVals.hostOrBaseDomain, + } + ); +}); diff --git a/browser/components/places/content/clearDataForSite.xhtml b/browser/components/places/content/clearDataForSite.xhtml new file mode 100644 index 00000000000..04797ea8a1b --- /dev/null +++ b/browser/components/places/content/clearDataForSite.xhtml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + +
+

+ +

+
    +
  • +
  • +
  • +
  • +
+

+
+ + + diff --git a/browser/extensions/newtab/common/Actions.mjs b/browser/extensions/newtab/common/Actions.mjs index 15be359c508..9ff96dc760f 100644 --- a/browser/extensions/newtab/common/Actions.mjs +++ b/browser/extensions/newtab/common/Actions.mjs @@ -147,6 +147,7 @@ for (const type of [ "SAVE_SESSION_PERF_DATA", "SAVE_TO_POCKET", "SCREENSHOT_UPDATED", + "SECTION_DATA_UPDATE", "SECTION_DEREGISTER", "SECTION_DISABLE", "SECTION_ENABLE", diff --git a/browser/extensions/newtab/common/Reducers.sys.mjs b/browser/extensions/newtab/common/Reducers.sys.mjs index 39798bfca97..5c874eeafa5 100644 --- a/browser/extensions/newtab/common/Reducers.sys.mjs +++ b/browser/extensions/newtab/common/Reducers.sys.mjs @@ -106,6 +106,7 @@ export const INITIAL_STATE = { visible: false, data: {}, }, + sectionData: {}, }, // Messages received from ASRouter to render in newtab Messages: { @@ -938,6 +939,8 @@ function DiscoveryStream(prevState = INITIAL_STATE.DiscoveryStream, action) { visible: false, }, }; + case at.SECTION_DATA_UPDATE: + return { ...prevState, sectionData: action.data }; default: return prevState; } diff --git a/browser/extensions/newtab/content-src/components/Base/Base.jsx b/browser/extensions/newtab/content-src/components/Base/Base.jsx index f6018876060..d6380ffae4c 100644 --- a/browser/extensions/newtab/content-src/components/Base/Base.jsx +++ b/browser/extensions/newtab/content-src/components/Base/Base.jsx @@ -583,7 +583,7 @@ export class BaseContent extends React.PureComponent { } } - // eslint-disable-next-line max-statements + // eslint-disable-next-line max-statements, complexity render() { const { props } = this; const { App, DiscoveryStream } = props; @@ -765,7 +765,10 @@ export class BaseContent extends React.PureComponent { > {mobileDownloadPromoEnabled && mobileDownloadPromoVariantABorC && ( - + {this.state.showDownloadHighlight && ( diff --git a/browser/extensions/newtab/content-src/components/CustomizeMenu/SectionsMgmtPanel/SectionsMgmtPanel.jsx b/browser/extensions/newtab/content-src/components/CustomizeMenu/SectionsMgmtPanel/SectionsMgmtPanel.jsx index 330dd32cf70..f5d6f10ea1e 100644 --- a/browser/extensions/newtab/content-src/components/CustomizeMenu/SectionsMgmtPanel/SectionsMgmtPanel.jsx +++ b/browser/extensions/newtab/content-src/components/CustomizeMenu/SectionsMgmtPanel/SectionsMgmtPanel.jsx @@ -4,31 +4,13 @@ import React, { useState, useCallback, useEffect } from "react"; import { useDispatch, useSelector } from "react-redux"; -import { actionCreators as ac } from "common/Actions.mjs"; +import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs"; // eslint-disable-next-line no-shadow import { CSSTransition } from "react-transition-group"; -const PREF_FOLLOWED_SECTIONS = "discoverystream.sections.following"; -const PREF_BLOCKED_SECTIONS = "discoverystream.sections.blocked"; - -/** - * Transforms a comma-separated string of topics in user preferences - * into a cleaned-up array. - * - * @param pref - * @returns string[] - */ -// TODO: DRY Issue: Import function from CardSections.jsx? -const getTopics = pref => { - return pref - .split(",") - .map(item => item.trim()) - .filter(item => item); -}; - function SectionsMgmtPanel({ exitEventFired }) { const [showPanel, setShowPanel] = useState(false); // State management with useState - const prefs = useSelector(state => state.Prefs.values); + const { sectionData } = useSelector(state => state.DiscoveryStream); const layoutComponents = useSelector( state => state.DiscoveryStream.layout[0].components ); @@ -50,45 +32,43 @@ function SectionsMgmtPanel({ exitEventFired }) { sectionsList = sections[sectionsFeedName].data.sections; } - const followedSectionsPref = prefs[PREF_FOLLOWED_SECTIONS] || ""; - const blockedSectionsPref = prefs[PREF_BLOCKED_SECTIONS] || ""; - const followedSections = getTopics(followedSectionsPref); - const blockedSections = getTopics(blockedSectionsPref); + const [sectionsState, setSectionState] = useState(sectionData); // State management with useState - const [followedSectionsState, setFollowedSectionsState] = - useState(followedSectionsPref); // State management with useState - const [blockedSectionsState, setBlockedSectionsState] = - useState(blockedSectionsPref); // State management with useState - - let followedSectionsData = sectionsList.filter(item => - followedSectionsState.includes(item.sectionKey) + let followedSectionsData = sectionsList.filter( + item => sectionsState[item.sectionKey]?.isFollowed ); - let blockedSectionsData = sectionsList.filter(item => - blockedSectionsState.includes(item.sectionKey) + let blockedSectionsData = sectionsList.filter( + item => sectionsState[item.sectionKey]?.isBlocked ); function updateCachedData() { // Reset cached followed/blocked list data while panel is open - setFollowedSectionsState(followedSectionsPref); - setBlockedSectionsState(blockedSectionsPref); + setSectionState(sectionData); - followedSectionsData = sectionsList.filter(item => - followedSectionsState.includes(item.sectionKey) + followedSectionsData = sectionsList.filter( + item => sectionsState[item.sectionKey]?.isFollowed ); - blockedSectionsData = sectionsList.filter(item => - blockedSectionsState.includes(item.sectionKey) + blockedSectionsData = sectionsList.filter( + item => sectionsState[item.sectionKey]?.isBlocked ); } const onFollowClick = useCallback( (sectionKey, receivedRank) => { dispatch( - ac.SetPref( - PREF_FOLLOWED_SECTIONS, - [...followedSections, sectionKey].join(", ") - ) + ac.AlsoToMain({ + type: at.SECTION_DATA_UPDATE, + data: { + ...sectionData, + [sectionKey]: { + isFollowed: true, + isBlocked: false, + followedAt: new Date().toISOString(), + }, + }, + }) ); // Telemetry Event Dispatch dispatch( @@ -102,16 +82,22 @@ function SectionsMgmtPanel({ exitEventFired }) { }) ); }, - [dispatch, followedSections] + [dispatch, sectionData] ); const onBlockClick = useCallback( (sectionKey, receivedRank) => { dispatch( - ac.SetPref( - PREF_BLOCKED_SECTIONS, - [...blockedSections, sectionKey].join(", ") - ) + ac.AlsoToMain({ + type: at.SECTION_DATA_UPDATE, + data: { + ...sectionData, + [sectionKey]: { + isFollowed: false, + isBlocked: true, + }, + }, + }) ); // Telemetry Event Dispatch @@ -126,16 +112,18 @@ function SectionsMgmtPanel({ exitEventFired }) { }) ); }, - [dispatch, blockedSections] + [dispatch, sectionData] ); const onUnblockClick = useCallback( (sectionKey, receivedRank) => { + const updatedSectionData = { ...sectionData }; + delete updatedSectionData[sectionKey]; dispatch( - ac.SetPref( - PREF_BLOCKED_SECTIONS, - [...blockedSections.filter(item => item !== sectionKey)].join(", ") - ) + ac.AlsoToMain({ + type: at.SECTION_DATA_UPDATE, + data: updatedSectionData, + }) ); // Telemetry Event Dispatch dispatch( @@ -149,16 +137,18 @@ function SectionsMgmtPanel({ exitEventFired }) { }) ); }, - [dispatch, blockedSections] + [dispatch, sectionData] ); const onUnfollowClick = useCallback( (sectionKey, receivedRank) => { + const updatedSectionData = { ...sectionData }; + delete updatedSectionData[sectionKey]; dispatch( - ac.SetPref( - PREF_FOLLOWED_SECTIONS, - [...followedSections.filter(item => item !== sectionKey)].join(", ") - ) + ac.AlsoToMain({ + type: at.SECTION_DATA_UPDATE, + data: updatedSectionData, + }) ); // Telemetry Event Dispatch dispatch( @@ -172,7 +162,7 @@ function SectionsMgmtPanel({ exitEventFired }) { }) ); }, - [dispatch, followedSections] + [dispatch, sectionData] ); // Close followed/blocked topic subpanel when parent menu is closed @@ -193,7 +183,7 @@ function SectionsMgmtPanel({ exitEventFired }) { const followedSectionsList = followedSectionsData.map( ({ sectionKey, title, receivedRank }) => { - const following = followedSections.includes(sectionKey); + const following = sectionData[sectionKey]?.isFollowed; return (
  • @@ -235,7 +225,7 @@ function SectionsMgmtPanel({ exitEventFired }) { const blockedSectionsList = blockedSectionsData.map( ({ sectionKey, title, receivedRank }) => { - const blocked = blockedSections.includes(sectionKey); + const blocked = sectionData[sectionKey]?.isBlocked; return (
  • diff --git a/browser/extensions/newtab/content-src/components/DiscoveryStreamComponents/AdBannerContextMenu/AdBannerContextMenu.jsx b/browser/extensions/newtab/content-src/components/DiscoveryStreamComponents/AdBannerContextMenu/AdBannerContextMenu.jsx index 445ef2fe87d..04e951496be 100644 --- a/browser/extensions/newtab/content-src/components/DiscoveryStreamComponents/AdBannerContextMenu/AdBannerContextMenu.jsx +++ b/browser/extensions/newtab/content-src/components/DiscoveryStreamComponents/AdBannerContextMenu/AdBannerContextMenu.jsx @@ -39,6 +39,10 @@ export function AdBannerContextMenu({ const [contextMenuClassNames, setContextMenuClassNames] = useState("ads-context-menu"); + // The keyboard access parameter is passed down to LinkMenu component + // that uses it to focus on the first context menu option for accessibility. + const [isKeyboardAccess, setIsKeyboardAccess] = useState(false); + /** * Toggles the style fix for context menu hover/active styles. * This allows us to have unobtrusive, transparent button background by default, @@ -54,11 +58,28 @@ export function AdBannerContextMenu({ } }; - const onClick = e => { - e.preventDefault(); - + /** + * Toggles the context menu to open or close. Sets state depending on whether + * the context menu is accessed by mouse or keyboard. + * + * @param isKeyBoard + */ + const toggleContextMenu = isKeyBoard => { toggleContextMenuStyleSwitch(!showContextMenu); setShowContextMenu(!showContextMenu); + setIsKeyboardAccess(isKeyBoard); + }; + + const onClick = e => { + e.preventDefault(); + toggleContextMenu(false); + }; + + const onKeyDown = e => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + toggleContextMenu(true); + } }; const onUpdate = () => { @@ -74,11 +95,13 @@ export function AdBannerContextMenu({ size="default" iconsrc="chrome://global/skin/icons/more.svg" onClick={onClick} + onKeyDown={onKeyDown} /> {showContextMenu && ( state.Prefs.values); + const { sectionData } = useSelector(state => state.DiscoveryStream); const showTopics = prefs[PREF_TOPICS_ENABLED]; const mayHaveSectionsCards = prefs[PREF_SECTIONS_CARDS_ENABLED]; const mayHaveSectionsCardsThumbsUpDown = @@ -121,8 +120,6 @@ function CardSection({ const mayHaveThumbsUpDown = prefs[PREF_THUMBS_UP_DOWN_ENABLED]; const selectedTopics = prefs[PREF_TOPICS_SELECTED]; const availableTopics = prefs[PREF_TOPICS_AVAILABLE]; - const followedSectionsPref = prefs[PREF_FOLLOWED_SECTIONS] || ""; - const blockedSectionsPref = prefs[PREF_BLOCKED_SECTIONS] || ""; const { saveToPocketCard } = useSelector(state => state.DiscoveryStream); const mayHaveSectionsPersonalization = @@ -131,9 +128,7 @@ function CardSection({ const { sectionKey, title, subtitle } = section; const { responsiveLayouts } = section.layout; - const followedSections = prefToArray(followedSectionsPref); - const following = followedSections.includes(sectionKey); - const blockedSections = prefToArray(blockedSectionsPref); + const following = sectionData[sectionKey]?.isFollowed; const handleIntersection = useCallback(() => { dispatch( @@ -156,11 +151,19 @@ function CardSection({ mayHaveSectionsCardsThumbsUpDown && mayHaveThumbsUpDown; const onFollowClick = useCallback(() => { + const updatedSectionData = { + ...sectionData, + [sectionKey]: { + isFollowed: true, + isBlocked: false, + followedAt: new Date().toISOString(), + }, + }; dispatch( - ac.SetPref( - PREF_FOLLOWED_SECTIONS, - [...followedSections, sectionKey].join(", ") - ) + ac.AlsoToMain({ + type: at.SECTION_DATA_UPDATE, + data: updatedSectionData, + }) ); // Telemetry Event Dispatch dispatch( @@ -173,15 +176,18 @@ function CardSection({ }, }) ); - }, [dispatch, followedSections, sectionKey, sectionPosition]); + }, [dispatch, sectionData, sectionKey, sectionPosition]); const onUnfollowClick = useCallback(() => { + const updatedSectionData = { ...sectionData }; + delete updatedSectionData[sectionKey]; dispatch( - ac.SetPref( - PREF_FOLLOWED_SECTIONS, - [...followedSections.filter(item => item !== sectionKey)].join(", ") - ) + ac.AlsoToMain({ + type: at.SECTION_DATA_UPDATE, + data: updatedSectionData, + }) ); + // Telemetry Event Dispatch dispatch( ac.OnlyToMain({ @@ -193,7 +199,7 @@ function CardSection({ }, }) ); - }, [dispatch, followedSections, sectionKey, sectionPosition]); + }, [dispatch, sectionData, sectionKey, sectionPosition]); const { maxTile } = getMaxTiles(responsiveLayouts); const displaySections = section.data.slice(0, maxTile); @@ -233,8 +239,7 @@ function CardSection({ dispatch={dispatch} index={sectionPosition} following={following} - followedSections={followedSections} - blockedSections={blockedSections} + sectionData={sectionData} sectionKey={sectionKey} title={title} type={type} @@ -349,7 +354,7 @@ function CardSections({ ctaButtonSponsors, }) { const prefs = useSelector(state => state.Prefs.values); - const { spocs } = useSelector(state => state.DiscoveryStream); + const { spocs, sectionData } = useSelector(state => state.DiscoveryStream); const personalizationEnabled = prefs[PREF_SECTIONS_PERSONALIZATION_ENABLED]; const interestPickerEnabled = prefs[PREF_INTEREST_PICKER_ENABLED]; @@ -359,11 +364,10 @@ function CardSections({ } const visibleSections = prefToArray(prefs[PREF_VISIBLE_SECTIONS]); - const blockedSections = prefToArray(prefs[PREF_BLOCKED_SECTIONS] || ""); const { interestPicker } = data; let filteredSections = data.sections.filter( - section => !blockedSections.includes(section.sectionKey) + section => !sectionData[section.sectionKey]?.isBlocked ); if (interestPickerEnabled && visibleSections.length) { diff --git a/browser/extensions/newtab/content-src/components/DiscoveryStreamComponents/FeatureHighlight/_DownloadMobilePromoHighlight.scss b/browser/extensions/newtab/content-src/components/DiscoveryStreamComponents/FeatureHighlight/_DownloadMobilePromoHighlight.scss index e62bb49b3c1..5aa6ab311ae 100644 --- a/browser/extensions/newtab/content-src/components/DiscoveryStreamComponents/FeatureHighlight/_DownloadMobilePromoHighlight.scss +++ b/browser/extensions/newtab/content-src/components/DiscoveryStreamComponents/FeatureHighlight/_DownloadMobilePromoHighlight.scss @@ -46,22 +46,22 @@ &.inset-block-end { margin-top: var(--space-xxlarge); + } + + &.inset-inline-end { + inset-inline-start: calc(var(--arrow-size) * -2); &::after { - // inset-block-start: -12px; - // transform: rotate(225deg); + inset-inline-start: calc(var(--space-xxlarge) - 14px); } } &.inset-inline-start { - // inset-inline-end: 0; inset-inline-end: calc(calc(calc(var(--arrow-size) / 2) * -1) - 6px); - // inset-inline-end: calc(calc(var(--arrow-size) / 2) * -1); &::after { inset-inline-end: calc(var(--space-xxlarge) - 12px); } - } // Message Arrow Pointer diff --git a/browser/extensions/newtab/content-src/components/DiscoveryStreamComponents/InterestPicker/InterestPicker.jsx b/browser/extensions/newtab/content-src/components/DiscoveryStreamComponents/InterestPicker/InterestPicker.jsx index 05205b2fa7e..6d1748a7d13 100644 --- a/browser/extensions/newtab/content-src/components/DiscoveryStreamComponents/InterestPicker/InterestPicker.jsx +++ b/browser/extensions/newtab/content-src/components/DiscoveryStreamComponents/InterestPicker/InterestPicker.jsx @@ -6,7 +6,6 @@ import React, { useState, useRef, useCallback } from "react"; import { useDispatch, useSelector } from "react-redux"; import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs"; import { useIntersectionObserver } from "../../../lib/utils"; -const PREF_FOLLOWED_SECTIONS = "discoverystream.sections.following"; const PREF_VISIBLE_SECTIONS = "discoverystream.sections.interestPicker.visibleSections"; @@ -23,13 +22,10 @@ function InterestPicker({ title, subtitle, interests, receivedFeedRank }) { const focusRef = useRef(null); const [focusedIndex, setFocusedIndex] = useState(0); const prefs = useSelector(state => state.Prefs.values); + const { sectionData } = useSelector(state => state.DiscoveryStream); const visibleSections = prefs[PREF_VISIBLE_SECTIONS]?.split(",") .map(item => item.trim()) .filter(item => item); - const following = - prefs[PREF_FOLLOWED_SECTIONS]?.split(",") - .map(item => item.trim()) - .filter(item => item) || []; const handleIntersection = useCallback(() => { dispatch( @@ -80,11 +76,13 @@ function InterestPicker({ title, subtitle, interests, receivedFeedRank }) { // by selecting them from the list function handleChange(e, index) { const { name: topic, checked } = e.target; - let updatedTopics = following; + let updatedSections = { ...sectionData }; if (checked) { - updatedTopics = updatedTopics.length - ? [...updatedTopics, topic] - : [topic]; + updatedSections[topic] = { + isFollowed: true, + isBlocked: false, + followedAt: new Date().toISOString(), + }; if (!visibleSections.includes(topic)) { // add section to visible sections and place after the inline picker // subtract 1 from the rank so that it is normalized with array index @@ -92,7 +90,7 @@ function InterestPicker({ title, subtitle, interests, receivedFeedRank }) { dispatch(ac.SetPref(PREF_VISIBLE_SECTIONS, visibleSections.join(", "))); } } else { - updatedTopics = updatedTopics.filter(t => t !== topic); + delete updatedSections[topic]; } dispatch( ac.OnlyToMain({ @@ -105,7 +103,9 @@ function InterestPicker({ title, subtitle, interests, receivedFeedRank }) { }, }) ); - dispatch(ac.SetPref(PREF_FOLLOWED_SECTIONS, updatedTopics.join(", "))); + dispatch( + ac.AlsoToMain({ type: at.SECTION_DATA_UPDATE, data: updatedSections }) + ); } return (
    {interests.map((interest, index) => { - const checked = following.includes(interest.sectionId); + const checked = sectionData[interest.sectionId]?.isFollowed; return (
  • + ); diff --git a/browser/extensions/newtab/content-src/components/DownloadModalToggle/_DownloadModalToggle.scss b/browser/extensions/newtab/content-src/components/DownloadModalToggle/_DownloadModalToggle.scss index 59f8481b88f..8a3eca63625 100644 --- a/browser/extensions/newtab/content-src/components/DownloadModalToggle/_DownloadModalToggle.scss +++ b/browser/extensions/newtab/content-src/components/DownloadModalToggle/_DownloadModalToggle.scss @@ -5,10 +5,12 @@ // This class is applied when the weather widget is active and has // a display mode set to "detailed" &.is-tall { - height: 74px; + @media (min-width: $break-point-widest) { + height: 74px; + } } - @media (min-width: $break-point-widest) { + @media (min-width: $break-point-medium) { display: flex; align-items: center; justify-content: center; @@ -20,18 +22,37 @@ z-index: 1; } + // Variant B uses 40px spacing .layout-variant-b & { - @media (min-width: $break-point-widest) { + @media (min-width: $break-point-medium) { + inset-block-start: 40px; + inset-inline-start: auto; + // On smallest break point visible, make additional room for weather widget + inset-inline-end: var(--space-medium); + } + + @media (min-width: $break-point-layout-variant) { + // Reset horizontal spacing back to 40px + inset-inline-end: 40px; + } + } + + // Variant B / No search bar: delay showing the mobile icon until the next breakpoint + .layout-variant-b.no-search & { + display: none; + + @media (min-width: $break-point-large) { + display: flex; + inset-block-start: 40px; inset-inline-start: auto; inset-inline-end: 40px; - inset-block-start: 40px; } } // Variant A uses 24px spacing .layout-variant-a & { - @media (min-width: $break-point-widest) { + @media (min-width: $break-point-medium) { inset-inline-start: 24px; inset-block-start: 24px; } @@ -55,6 +76,11 @@ } } + // Active state for the toggle button while the modal is open + &.is-active { + background-color: var(--button-background-color-ghost-active); + } + // Bug 1908010 - This overwrites the design system color because of a // known transparency issue with color-mix syntax when a wallpaper is set .lightWallpaper &, diff --git a/browser/extensions/newtab/content-src/components/Search/_Search.scss b/browser/extensions/newtab/content-src/components/Search/_Search.scss index c5b1f1376ba..3b28cf37975 100644 --- a/browser/extensions/newtab/content-src/components/Search/_Search.scss +++ b/browser/extensions/newtab/content-src/components/Search/_Search.scss @@ -251,6 +251,21 @@ $glyph-forward: url('chrome://browser/skin/forward.svg'); } } + // Bug 1960519 - Custom override for mobile icon next to weather + &.has-mobile-download-promo { + .search-inner-wrapper { + @media (min-width: $break-point-widest) { + // Set to smaller breakpoint to fit weather + width: 497px; + } + + @media (min-width: $break-point-weather) { + // Reset back to default length + width: 720px; + } + } + } + .logo { width: 52px; height: 52px; diff --git a/browser/extensions/newtab/content-src/components/Weather/_Weather.scss b/browser/extensions/newtab/content-src/components/Weather/_Weather.scss index 2a3969ee875..57f2ed51fbf 100644 --- a/browser/extensions/newtab/content-src/components/Weather/_Weather.scss +++ b/browser/extensions/newtab/content-src/components/Weather/_Weather.scss @@ -183,26 +183,33 @@ // Bug 1959189 - Show mobile QR code modal toggle next to weather widget .has-mobile-download-promo { - // No Variant Layout - .weather { + $mobile-download-promo-width: 32px; + // No Variant Layout, Var A Layout + .weather, &.layout-variant-a .weather { // Default spacing (--space-xlarge) + width of .mobile-download-promo (32px) + gap - @media (min-width: $break-point-widest) { - inset-inline-start: calc(var(--space-xlarge) + 32px + var(--space-large)); - } - } - - &.layout-variant-a .weather { - // Var A offset (24px) + width of .mobile-download-promo (32px) + gap - @media (min-width: $break-point-widest) { - inset-inline-start: calc(24px + 32px + var(--space-medium)); + @media (min-width: $break-point-medium) { + inset-inline-start: calc($mobile-download-promo-width + var(--space-xxlarge)); } } &.layout-variant-b .weather { - // Var B offset (40px) + width of .mobile-download-promo (32px) + gap - @media (min-width: $break-point-widest) { + // Var B: width of .mobile-download-promo (32px) + gap + @media (min-width: $break-point-medium) { inset-inline-start: auto; - inset-inline-end: calc(40px + 32px + var(--space-medium)); + inset-inline-end: calc($mobile-download-promo-width + var(--space-medium)); + } + + // Var B offset (40px) + width of .mobile-download-promo (32px) + gap + @media (min-width: $break-point-layout-variant) { + inset-inline-end: calc(40px + $mobile-download-promo-width + var(--space-medium)); + } + } + + // Layout when search has been turned off. It will show one breakpoint later than `has-search` + &.layout-variant-b.no-search .weather { + // Var B offset (40px) + width of .mobile-download-promo (32px) + gap + @media (min-width: $break-point-large) { + inset-inline-end: calc(40px + $mobile-download-promo-width + var(--space-medium)); } } } diff --git a/browser/extensions/newtab/content-src/lib/link-menu-options.mjs b/browser/extensions/newtab/content-src/lib/link-menu-options.mjs index 019e2c49d09..08842f96c00 100644 --- a/browser/extensions/newtab/content-src/lib/link-menu-options.mjs +++ b/browser/extensions/newtab/content-src/lib/link-menu-options.mjs @@ -453,7 +453,7 @@ export const LinkMenuOptions = { type: at.OPEN_ABOUT_FAKESPOT, }), }), - SectionBlock: ({ blockedSections, sectionKey, sectionPosition, title }) => ({ + SectionBlock: ({ sectionData, sectionKey, sectionPosition, title }) => ({ id: "newtab-menu-section-block", icon: "delete", action: { @@ -464,10 +464,13 @@ export const LinkMenuOptions = { // Once the user confirmed their intention to block this section, // update their preferences. ac.AlsoToMain({ - type: at.SET_PREF, + type: at.SECTION_DATA_UPDATE, data: { - name: "discoverystream.sections.blocked", - value: [...blockedSections, sectionKey].join(", "), + ...sectionData, + [sectionKey]: { + isBlocked: true, + isFollowed: false, + }, }, }), // Telemetry @@ -498,16 +501,13 @@ export const LinkMenuOptions = { }, userEvent: "DIALOG_OPEN", }), - SectionUnfollow: ({ followedSections, sectionKey, sectionPosition }) => ({ + SectionUnfollow: ({ sectionData, sectionKey, sectionPosition }) => ({ id: "newtab-menu-section-unfollow", action: ac.AlsoToMain({ - type: at.SET_PREF, - data: { - name: "discoverystream.sections.following", - value: [...followedSections.filter(item => item !== sectionKey)].join( - ", " - ), - }, + type: at.SECTION_DATA_UPDATE, + data: (({ sectionKey: _sectionKey, ...remaining }) => remaining)( + sectionData + ), }), impression: ac.OnlyToMain({ type: at.UNFOLLOW_SECTION, diff --git a/browser/extensions/newtab/css/activity-stream.css b/browser/extensions/newtab/css/activity-stream.css index 74c0a9f38aa..20a68c6339b 100644 --- a/browser/extensions/newtab/css/activity-stream.css +++ b/browser/extensions/newtab/css/activity-stream.css @@ -1810,6 +1810,16 @@ main section { width: 720px; } } +@media (min-width: 1122px) { + .layout-variant-b.has-recommended-stories.has-mobile-download-promo .search-inner-wrapper { + width: 497px; + } +} +@media (min-width: 1506px) { + .layout-variant-b.has-recommended-stories.has-mobile-download-promo .search-inner-wrapper { + width: 720px; + } +} .layout-variant-b.has-recommended-stories .logo { width: 52px; height: 52px; @@ -2973,19 +2983,24 @@ main section { margin-inline-end: 5px; } -@media (min-width: 1122px) { - .has-mobile-download-promo .weather { - inset-inline-start: calc(var(--space-xlarge) + 32px + var(--space-large)); +@media (min-width: 610px) { + .has-mobile-download-promo .weather, .has-mobile-download-promo.layout-variant-a .weather { + inset-inline-start: calc(32px + var(--space-xxlarge)); } } -@media (min-width: 1122px) { - .has-mobile-download-promo.layout-variant-a .weather { - inset-inline-start: calc(56px + var(--space-medium)); - } -} -@media (min-width: 1122px) { +@media (min-width: 610px) { .has-mobile-download-promo.layout-variant-b .weather { inset-inline-start: auto; + inset-inline-end: calc(32px + var(--space-medium)); + } +} +@media (min-width: 724px) { + .has-mobile-download-promo.layout-variant-b .weather { + inset-inline-end: calc(72px + var(--space-medium)); + } +} +@media (min-width: 866px) { + .has-mobile-download-promo.layout-variant-b.no-search .weather { inset-inline-end: calc(72px + var(--space-medium)); } } @@ -3562,10 +3577,12 @@ main section { display: none; height: 55px; } -.mobileDownloadPromoWrapper.is-tall { - height: 74px; -} @media (min-width: 1122px) { + .mobileDownloadPromoWrapper.is-tall { + height: 74px; + } +} +@media (min-width: 610px) { .mobileDownloadPromoWrapper { display: flex; align-items: center; @@ -3578,14 +3595,30 @@ main section { z-index: 1; } } -@media (min-width: 1122px) { +@media (min-width: 610px) { .layout-variant-b .mobileDownloadPromoWrapper { - inset-inline-start: auto; - inset-inline-end: 40px; inset-block-start: 40px; + inset-inline-start: auto; + inset-inline-end: var(--space-medium); } } -@media (min-width: 1122px) { +@media (min-width: 724px) { + .layout-variant-b .mobileDownloadPromoWrapper { + inset-inline-end: 40px; + } +} +.layout-variant-b.no-search .mobileDownloadPromoWrapper { + display: none; +} +@media (min-width: 866px) { + .layout-variant-b.no-search .mobileDownloadPromoWrapper { + display: flex; + inset-block-start: 40px; + inset-inline-start: auto; + inset-inline-end: 40px; + } +} +@media (min-width: 610px) { .layout-variant-a .mobileDownloadPromoWrapper { inset-inline-start: 24px; inset-block-start: 24px; @@ -3607,6 +3640,9 @@ main section { .mobile-download-promo:hover:active { background-color: var(--button-background-color-ghost-active); } +.mobile-download-promo.is-active { + background-color: var(--button-background-color-ghost-active); +} .lightWallpaper .mobile-download-promo, .darkWallpaper .mobile-download-promo { background-color: var(--newtab-weather-background-color); } @@ -7508,6 +7544,12 @@ main section { .download-firefox-feature-highlight .feature-highlight .feature-highlight-modal.inset-block-end { margin-top: var(--space-xxlarge); } +.download-firefox-feature-highlight .feature-highlight .feature-highlight-modal.inset-inline-end { + inset-inline-start: calc(var(--arrow-size) * -2); +} +.download-firefox-feature-highlight .feature-highlight .feature-highlight-modal.inset-inline-end::after { + inset-inline-start: calc(var(--space-xxlarge) - 14px); +} .download-firefox-feature-highlight .feature-highlight .feature-highlight-modal.inset-inline-start { inset-inline-end: calc(var(--arrow-size) / 2 * -1 - 6px); } diff --git a/browser/extensions/newtab/data/content/activity-stream.bundle.js b/browser/extensions/newtab/data/content/activity-stream.bundle.js index 0736e74e444..2bfdbe4e5ff 100644 --- a/browser/extensions/newtab/data/content/activity-stream.bundle.js +++ b/browser/extensions/newtab/data/content/activity-stream.bundle.js @@ -220,6 +220,7 @@ for (const type of [ "SAVE_SESSION_PERF_DATA", "SAVE_TO_POCKET", "SCREENSHOT_UPDATED", + "SECTION_DATA_UPDATE", "SECTION_DEREGISTER", "SECTION_DISABLE", "SECTION_ENABLE", @@ -2141,7 +2142,7 @@ const LinkMenuOptions = { type: actionTypes.OPEN_ABOUT_FAKESPOT, }), }), - SectionBlock: ({ blockedSections, sectionKey, sectionPosition, title }) => ({ + SectionBlock: ({ sectionData, sectionKey, sectionPosition, title }) => ({ id: "newtab-menu-section-block", icon: "delete", action: { @@ -2152,10 +2153,13 @@ const LinkMenuOptions = { // Once the user confirmed their intention to block this section, // update their preferences. actionCreators.AlsoToMain({ - type: actionTypes.SET_PREF, + type: actionTypes.SECTION_DATA_UPDATE, data: { - name: "discoverystream.sections.blocked", - value: [...blockedSections, sectionKey].join(", "), + ...sectionData, + [sectionKey]: { + isBlocked: true, + isFollowed: false, + }, }, }), // Telemetry @@ -2186,16 +2190,13 @@ const LinkMenuOptions = { }, userEvent: "DIALOG_OPEN", }), - SectionUnfollow: ({ followedSections, sectionKey, sectionPosition }) => ({ + SectionUnfollow: ({ sectionData, sectionKey, sectionPosition }) => ({ id: "newtab-menu-section-unfollow", action: actionCreators.AlsoToMain({ - type: actionTypes.SET_PREF, - data: { - name: "discoverystream.sections.following", - value: [...followedSections.filter(item => item !== sectionKey)].join( - ", " - ), - }, + type: actionTypes.SECTION_DATA_UPDATE, + data: (({ sectionKey: _sectionKey, ...remaining }) => remaining)( + sectionData + ), }), impression: actionCreators.OnlyToMain({ type: actionTypes.UNFOLLOW_SECTION, @@ -4571,6 +4572,10 @@ function AdBannerContextMenu({ const [showContextMenu, setShowContextMenu] = (0,external_React_namespaceObject.useState)(false); const [contextMenuClassNames, setContextMenuClassNames] = (0,external_React_namespaceObject.useState)("ads-context-menu"); + // The keyboard access parameter is passed down to LinkMenu component + // that uses it to focus on the first context menu option for accessibility. + const [isKeyboardAccess, setIsKeyboardAccess] = (0,external_React_namespaceObject.useState)(false); + /** * Toggles the style fix for context menu hover/active styles. * This allows us to have unobtrusive, transparent button background by default, @@ -4585,10 +4590,27 @@ function AdBannerContextMenu({ setContextMenuClassNames("ads-context-menu"); } }; - const onClick = e => { - e.preventDefault(); + + /** + * Toggles the context menu to open or close. Sets state depending on whether + * the context menu is accessed by mouse or keyboard. + * + * @param isKeyBoard + */ + const toggleContextMenu = isKeyBoard => { toggleContextMenuStyleSwitch(!showContextMenu); setShowContextMenu(!showContextMenu); + setIsKeyboardAccess(isKeyBoard); + }; + const onClick = e => { + e.preventDefault(); + toggleContextMenu(false); + }; + const onKeyDown = e => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + toggleContextMenu(true); + } }; const onUpdate = () => { toggleContextMenuStyleSwitch(!showContextMenu); @@ -4602,10 +4624,12 @@ function AdBannerContextMenu({ type: "icon", size: "default", iconsrc: "chrome://global/skin/icons/more.svg", - onClick: onClick + onClick: onClick, + onKeyDown: onKeyDown }), showContextMenu && /*#__PURE__*/external_React_default().createElement(LinkMenu, { onUpdate: onUpdate, dispatch: dispatch, + keyboardAccess: isKeyboardAccess, options: ADBANNER_CONTEXT_MENU_OPTIONS, shouldSendImpressionStats: true, userEvent: actionCreators.DiscoveryStreamUserEvent, @@ -7344,6 +7368,7 @@ const INITIAL_STATE = { visible: false, data: {}, }, + sectionData: {}, }, // Messages received from ASRouter to render in newtab Messages: { @@ -8176,6 +8201,8 @@ function DiscoveryStream(prevState = INITIAL_STATE.DiscoveryStream, action) { visible: false, }, }; + case actionTypes.SECTION_DATA_UPDATE: + return { ...prevState, sectionData: action.data }; default: return prevState; } @@ -10684,8 +10711,7 @@ function SectionContextMenu({ dispatch, sectionKey, following, - followedSections, - blockedSections, + sectionData, sectionPosition }) { // Initial context menu options: block this section only. @@ -10717,8 +10743,7 @@ function SectionContextMenu({ options: SECTIONS_CONTEXT_MENU_OPTIONS, shouldSendImpressionStats: true, site: { - followedSections, - blockedSections, + sectionData, sectionKey, sectionPosition, title @@ -10734,7 +10759,6 @@ function SectionContextMenu({ -const PREF_FOLLOWED_SECTIONS = "discoverystream.sections.following"; const PREF_VISIBLE_SECTIONS = "discoverystream.sections.interestPicker.visibleSections"; /** @@ -10755,8 +10779,10 @@ function InterestPicker({ const focusRef = (0,external_React_namespaceObject.useRef)(null); const [focusedIndex, setFocusedIndex] = (0,external_React_namespaceObject.useState)(0); const prefs = (0,external_ReactRedux_namespaceObject.useSelector)(state => state.Prefs.values); + const { + sectionData + } = (0,external_ReactRedux_namespaceObject.useSelector)(state => state.DiscoveryStream); const visibleSections = prefs[PREF_VISIBLE_SECTIONS]?.split(",").map(item => item.trim()).filter(item => item); - const following = prefs[PREF_FOLLOWED_SECTIONS]?.split(",").map(item => item.trim()).filter(item => item) || []; const handleIntersection = (0,external_React_namespaceObject.useCallback)(() => { dispatch(actionCreators.AlsoToMain({ type: actionTypes.INLINE_SELECTION_IMPRESSION, @@ -10797,9 +10823,15 @@ function InterestPicker({ name: topic, checked } = e.target; - let updatedTopics = following; + let updatedSections = { + ...sectionData + }; if (checked) { - updatedTopics = updatedTopics.length ? [...updatedTopics, topic] : [topic]; + updatedSections[topic] = { + isFollowed: true, + isBlocked: false, + followedAt: new Date().toISOString() + }; if (!visibleSections.includes(topic)) { // add section to visible sections and place after the inline picker // subtract 1 from the rank so that it is normalized with array index @@ -10807,7 +10839,7 @@ function InterestPicker({ dispatch(actionCreators.SetPref(PREF_VISIBLE_SECTIONS, visibleSections.join(", "))); } } else { - updatedTopics = updatedTopics.filter(t => t !== topic); + delete updatedSections[topic]; } dispatch(actionCreators.OnlyToMain({ type: actionTypes.INLINE_SELECTION_CLICK, @@ -10818,7 +10850,10 @@ function InterestPicker({ section_position: receivedFeedRank } })); - dispatch(actionCreators.SetPref(PREF_FOLLOWED_SECTIONS, updatedTopics.join(", "))); + dispatch(actionCreators.AlsoToMain({ + type: actionTypes.SECTION_DATA_UPDATE, + data: updatedSections + })); } return /*#__PURE__*/external_React_default().createElement("section", { className: "inline-selection-wrapper ds-section", @@ -10839,7 +10874,7 @@ function InterestPicker({ onBlur: onWrapperBlur, ref: focusRef }, interests.map((interest, index) => { - const checked = following.includes(interest.sectionId); + const checked = sectionData[interest.sectionId]?.isFollowed; return /*#__PURE__*/external_React_default().createElement("li", { key: interest.sectionId, ref: index === focusedIndex ? focusedRef : null @@ -10920,8 +10955,6 @@ const PREF_SECTIONS_CARDS_THUMBS_UP_DOWN_ENABLED = "discoverystream.sections.car const PREF_SECTIONS_PERSONALIZATION_ENABLED = "discoverystream.sections.personalization.enabled"; const CardSections_PREF_TOPICS_ENABLED = "discoverystream.topicLabels.enabled"; const CardSections_PREF_TOPICS_SELECTED = "discoverystream.topicSelection.selectedTopics"; -const CardSections_PREF_FOLLOWED_SECTIONS = "discoverystream.sections.following"; -const PREF_BLOCKED_SECTIONS = "discoverystream.sections.blocked"; const CardSections_PREF_TOPICS_AVAILABLE = "discoverystream.topicSelection.topics"; const CardSections_PREF_THUMBS_UP_DOWN_ENABLED = "discoverystream.thumbsUpDown.enabled"; const PREF_INTEREST_PICKER_ENABLED = "discoverystream.sections.interestPicker.enabled"; @@ -10996,14 +11029,15 @@ function CardSection({ ctaButtonSponsors }) { const prefs = (0,external_ReactRedux_namespaceObject.useSelector)(state => state.Prefs.values); + const { + sectionData + } = (0,external_ReactRedux_namespaceObject.useSelector)(state => state.DiscoveryStream); const showTopics = prefs[CardSections_PREF_TOPICS_ENABLED]; const mayHaveSectionsCards = prefs[CardSections_PREF_SECTIONS_CARDS_ENABLED]; const mayHaveSectionsCardsThumbsUpDown = prefs[PREF_SECTIONS_CARDS_THUMBS_UP_DOWN_ENABLED]; const mayHaveThumbsUpDown = prefs[CardSections_PREF_THUMBS_UP_DOWN_ENABLED]; const selectedTopics = prefs[CardSections_PREF_TOPICS_SELECTED]; const availableTopics = prefs[CardSections_PREF_TOPICS_AVAILABLE]; - const followedSectionsPref = prefs[CardSections_PREF_FOLLOWED_SECTIONS] || ""; - const blockedSectionsPref = prefs[PREF_BLOCKED_SECTIONS] || ""; const { saveToPocketCard } = (0,external_ReactRedux_namespaceObject.useSelector)(state => state.DiscoveryStream); @@ -11016,9 +11050,7 @@ function CardSection({ const { responsiveLayouts } = section.layout; - const followedSections = prefToArray(followedSectionsPref); - const following = followedSections.includes(sectionKey); - const blockedSections = prefToArray(blockedSectionsPref); + const following = sectionData[sectionKey]?.isFollowed; const handleIntersection = (0,external_React_namespaceObject.useCallback)(() => { dispatch(actionCreators.AlsoToMain({ type: actionTypes.CARD_SECTION_IMPRESSION, @@ -11036,7 +11068,18 @@ function CardSection({ // Only show thumbs up/down buttons if both default thumbs and sections thumbs prefs are enabled const mayHaveCombinedThumbsUpDown = mayHaveSectionsCardsThumbsUpDown && mayHaveThumbsUpDown; const onFollowClick = (0,external_React_namespaceObject.useCallback)(() => { - dispatch(actionCreators.SetPref(CardSections_PREF_FOLLOWED_SECTIONS, [...followedSections, sectionKey].join(", "))); + const updatedSectionData = { + ...sectionData, + [sectionKey]: { + isFollowed: true, + isBlocked: false, + followedAt: new Date().toISOString() + } + }; + dispatch(actionCreators.AlsoToMain({ + type: actionTypes.SECTION_DATA_UPDATE, + data: updatedSectionData + })); // Telemetry Event Dispatch dispatch(actionCreators.OnlyToMain({ type: "FOLLOW_SECTION", @@ -11046,9 +11089,17 @@ function CardSection({ event_source: "MOZ_BUTTON" } })); - }, [dispatch, followedSections, sectionKey, sectionPosition]); + }, [dispatch, sectionData, sectionKey, sectionPosition]); const onUnfollowClick = (0,external_React_namespaceObject.useCallback)(() => { - dispatch(actionCreators.SetPref(CardSections_PREF_FOLLOWED_SECTIONS, [...followedSections.filter(item => item !== sectionKey)].join(", "))); + const updatedSectionData = { + ...sectionData + }; + delete updatedSectionData[sectionKey]; + dispatch(actionCreators.AlsoToMain({ + type: actionTypes.SECTION_DATA_UPDATE, + data: updatedSectionData + })); + // Telemetry Event Dispatch dispatch(actionCreators.OnlyToMain({ type: "UNFOLLOW_SECTION", @@ -11058,7 +11109,7 @@ function CardSection({ event_source: "MOZ_BUTTON" } })); - }, [dispatch, followedSections, sectionKey, sectionPosition]); + }, [dispatch, sectionData, sectionKey, sectionPosition]); const { maxTile } = getMaxTiles(responsiveLayouts); @@ -11090,8 +11141,7 @@ function CardSection({ dispatch: dispatch, index: sectionPosition, following: following, - followedSections: followedSections, - blockedSections: blockedSections, + sectionData: sectionData, sectionKey: sectionKey, title: title, type: type, @@ -11195,7 +11245,8 @@ function CardSections({ }) { const prefs = (0,external_ReactRedux_namespaceObject.useSelector)(state => state.Prefs.values); const { - spocs + spocs, + sectionData } = (0,external_ReactRedux_namespaceObject.useSelector)(state => state.DiscoveryStream); const personalizationEnabled = prefs[PREF_SECTIONS_PERSONALIZATION_ENABLED]; const interestPickerEnabled = prefs[PREF_INTEREST_PICKER_ENABLED]; @@ -11205,11 +11256,10 @@ function CardSections({ return null; } const visibleSections = prefToArray(prefs[CardSections_PREF_VISIBLE_SECTIONS]); - const blockedSections = prefToArray(prefs[PREF_BLOCKED_SECTIONS] || ""); const { interestPicker } = data; - let filteredSections = data.sections.filter(section => !blockedSections.includes(section.sectionKey)); + let filteredSections = data.sections.filter(section => !sectionData[section.sectionKey]?.isBlocked); if (interestPickerEnabled && visibleSections.length) { filteredSections = visibleSections.reduce((acc, visibleSection) => { const found = filteredSections.find(({ @@ -11677,25 +11727,13 @@ const DiscoveryStreamBase = (0,external_ReactRedux_namespaceObject.connect)(stat // eslint-disable-next-line no-shadow -const SectionsMgmtPanel_PREF_FOLLOWED_SECTIONS = "discoverystream.sections.following"; -const SectionsMgmtPanel_PREF_BLOCKED_SECTIONS = "discoverystream.sections.blocked"; - -/** - * Transforms a comma-separated string of topics in user preferences - * into a cleaned-up array. - * - * @param pref - * @returns string[] - */ -// TODO: DRY Issue: Import function from CardSections.jsx? -const getTopics = pref => { - return pref.split(",").map(item => item.trim()).filter(item => item); -}; function SectionsMgmtPanel({ exitEventFired }) { const [showPanel, setShowPanel] = (0,external_React_namespaceObject.useState)(false); // State management with useState - const prefs = (0,external_ReactRedux_namespaceObject.useSelector)(state => state.Prefs.values); + const { + sectionData + } = (0,external_ReactRedux_namespaceObject.useSelector)(state => state.DiscoveryStream); const layoutComponents = (0,external_ReactRedux_namespaceObject.useSelector)(state => state.DiscoveryStream.layout[0].components); const sections = (0,external_ReactRedux_namespaceObject.useSelector)(state => state.DiscoveryStream.feeds.data); const dispatch = (0,external_ReactRedux_namespaceObject.useDispatch)(); @@ -11710,24 +11748,28 @@ function SectionsMgmtPanel({ if (sectionsFeedName) { sectionsList = sections[sectionsFeedName].data.sections; } - const followedSectionsPref = prefs[SectionsMgmtPanel_PREF_FOLLOWED_SECTIONS] || ""; - const blockedSectionsPref = prefs[SectionsMgmtPanel_PREF_BLOCKED_SECTIONS] || ""; - const followedSections = getTopics(followedSectionsPref); - const blockedSections = getTopics(blockedSectionsPref); - const [followedSectionsState, setFollowedSectionsState] = (0,external_React_namespaceObject.useState)(followedSectionsPref); // State management with useState - const [blockedSectionsState, setBlockedSectionsState] = (0,external_React_namespaceObject.useState)(blockedSectionsPref); // State management with useState + const [sectionsState, setSectionState] = (0,external_React_namespaceObject.useState)(sectionData); // State management with useState - let followedSectionsData = sectionsList.filter(item => followedSectionsState.includes(item.sectionKey)); - let blockedSectionsData = sectionsList.filter(item => blockedSectionsState.includes(item.sectionKey)); + let followedSectionsData = sectionsList.filter(item => sectionsState[item.sectionKey]?.isFollowed); + let blockedSectionsData = sectionsList.filter(item => sectionsState[item.sectionKey]?.isBlocked); function updateCachedData() { // Reset cached followed/blocked list data while panel is open - setFollowedSectionsState(followedSectionsPref); - setBlockedSectionsState(blockedSectionsPref); - followedSectionsData = sectionsList.filter(item => followedSectionsState.includes(item.sectionKey)); - blockedSectionsData = sectionsList.filter(item => blockedSectionsState.includes(item.sectionKey)); + setSectionState(sectionData); + followedSectionsData = sectionsList.filter(item => sectionsState[item.sectionKey]?.isFollowed); + blockedSectionsData = sectionsList.filter(item => sectionsState[item.sectionKey]?.isBlocked); } const onFollowClick = (0,external_React_namespaceObject.useCallback)((sectionKey, receivedRank) => { - dispatch(actionCreators.SetPref(SectionsMgmtPanel_PREF_FOLLOWED_SECTIONS, [...followedSections, sectionKey].join(", "))); + dispatch(actionCreators.AlsoToMain({ + type: actionTypes.SECTION_DATA_UPDATE, + data: { + ...sectionData, + [sectionKey]: { + isFollowed: true, + isBlocked: false, + followedAt: new Date().toISOString() + } + } + })); // Telemetry Event Dispatch dispatch(actionCreators.OnlyToMain({ type: "FOLLOW_SECTION", @@ -11737,9 +11779,18 @@ function SectionsMgmtPanel({ event_source: "CUSTOMIZE_PANEL" } })); - }, [dispatch, followedSections]); + }, [dispatch, sectionData]); const onBlockClick = (0,external_React_namespaceObject.useCallback)((sectionKey, receivedRank) => { - dispatch(actionCreators.SetPref(SectionsMgmtPanel_PREF_BLOCKED_SECTIONS, [...blockedSections, sectionKey].join(", "))); + dispatch(actionCreators.AlsoToMain({ + type: actionTypes.SECTION_DATA_UPDATE, + data: { + ...sectionData, + [sectionKey]: { + isFollowed: false, + isBlocked: true + } + } + })); // Telemetry Event Dispatch dispatch(actionCreators.OnlyToMain({ @@ -11750,9 +11801,16 @@ function SectionsMgmtPanel({ event_source: "CUSTOMIZE_PANEL" } })); - }, [dispatch, blockedSections]); + }, [dispatch, sectionData]); const onUnblockClick = (0,external_React_namespaceObject.useCallback)((sectionKey, receivedRank) => { - dispatch(actionCreators.SetPref(SectionsMgmtPanel_PREF_BLOCKED_SECTIONS, [...blockedSections.filter(item => item !== sectionKey)].join(", "))); + const updatedSectionData = { + ...sectionData + }; + delete updatedSectionData[sectionKey]; + dispatch(actionCreators.AlsoToMain({ + type: actionTypes.SECTION_DATA_UPDATE, + data: updatedSectionData + })); // Telemetry Event Dispatch dispatch(actionCreators.OnlyToMain({ type: "UNBLOCK_SECTION", @@ -11762,9 +11820,16 @@ function SectionsMgmtPanel({ event_source: "CUSTOMIZE_PANEL" } })); - }, [dispatch, blockedSections]); + }, [dispatch, sectionData]); const onUnfollowClick = (0,external_React_namespaceObject.useCallback)((sectionKey, receivedRank) => { - dispatch(actionCreators.SetPref(SectionsMgmtPanel_PREF_FOLLOWED_SECTIONS, [...followedSections.filter(item => item !== sectionKey)].join(", "))); + const updatedSectionData = { + ...sectionData + }; + delete updatedSectionData[sectionKey]; + dispatch(actionCreators.AlsoToMain({ + type: actionTypes.SECTION_DATA_UPDATE, + data: updatedSectionData + })); // Telemetry Event Dispatch dispatch(actionCreators.OnlyToMain({ type: "UNFOLLOW_SECTION", @@ -11774,7 +11839,7 @@ function SectionsMgmtPanel({ event_source: "CUSTOMIZE_PANEL" } })); - }, [dispatch, followedSections]); + }, [dispatch, sectionData]); // Close followed/blocked topic subpanel when parent menu is closed (0,external_React_namespaceObject.useEffect)(() => { @@ -11795,7 +11860,7 @@ function SectionsMgmtPanel({ title, receivedRank }) => { - const following = followedSections.includes(sectionKey); + const following = sectionData[sectionKey]?.isFollowed; return /*#__PURE__*/external_React_default().createElement("li", { key: sectionKey }, /*#__PURE__*/external_React_default().createElement("label", { @@ -11824,7 +11889,7 @@ function SectionsMgmtPanel({ title, receivedRank }) => { - const blocked = blockedSections.includes(sectionKey); + const blocked = sectionData[sectionKey]?.isBlocked; return /*#__PURE__*/external_React_default().createElement("li", { key: sectionKey }, /*#__PURE__*/external_React_default().createElement("label", { @@ -13473,10 +13538,11 @@ const Weather_Weather = (0,external_ReactRedux_namespaceObject.connect)(state => function DownloadModalToggle({ - onClick + onClick, + isActive }) { return /*#__PURE__*/external_React_default().createElement("button", { - className: "mobile-download-promo", + className: `mobile-download-promo ${isActive ? " is-active" : ""}`, onClick: onClick }, /*#__PURE__*/external_React_default().createElement("div", { className: "icon icon-device-phone" @@ -14615,7 +14681,7 @@ class BaseContent extends (external_React_default()).PureComponent { } } - // eslint-disable-next-line max-statements + // eslint-disable-next-line max-statements, complexity render() { const { props @@ -14736,13 +14802,16 @@ class BaseContent extends (external_React_default()).PureComponent { }, weatherEnabled && /*#__PURE__*/external_React_default().createElement(ErrorBoundary, null, /*#__PURE__*/external_React_default().createElement(Weather_Weather, null))), /*#__PURE__*/external_React_default().createElement("div", { className: `mobileDownloadPromoWrapper ${mobileDownloadPromoWrapperHeightModifier}` }, mobileDownloadPromoEnabled && mobileDownloadPromoVariantABorC && /*#__PURE__*/external_React_default().createElement(ErrorBoundary, null, /*#__PURE__*/external_React_default().createElement(DownloadModalToggle, { + isActive: this.state.showDownloadHighlight, onClick: this.toggleDownloadHighlight }), this.state.showDownloadHighlight && /*#__PURE__*/external_React_default().createElement(MessageWrapper, { hiddenOverride: this.state.showDownloadHighlight, onDismiss: this.handleDismissDownloadHighlight, dispatch: this.props.dispatch - }, /*#__PURE__*/external_React_default().createElement(DownloadMobilePromoHighlight, { - position: "inset-block-end inset-inline-start", + }, /*#__PURE__*/external_React_default().createElement(DownloadMobilePromoHighlight + // Var B layout has the weather right-aligned + , { + position: `${layoutsVariantBEnabled ? "inset-inline-start" : "inset-inline-end"} inset-block-end`, dispatch: this.props.dispatch })))), /*#__PURE__*/external_React_default().createElement("div", { className: outerClassName, diff --git a/browser/extensions/newtab/karma.mc.config.js b/browser/extensions/newtab/karma.mc.config.js index 06ce367b597..07f7650c1b6 100644 --- a/browser/extensions/newtab/karma.mc.config.js +++ b/browser/extensions/newtab/karma.mc.config.js @@ -230,10 +230,10 @@ module.exports = function (config) { }, "content-src/components/DiscoveryStreamComponents/CardSections/CardSections.jsx": { - statements: 86.36, - lines: 85.94, + statements: 86.05, + lines: 85.48, functions: 79.31, - branches: 57.97, + branches: 54.41, }, "content-src/components/DiscoveryStreamComponents/SectionContextMenu/SectionContextMenu.jsx": { @@ -248,10 +248,9 @@ module.exports = function (config) { }, "content-src/components/DiscoveryStreamComponents/AdBannerContextMenu/AdBannerContextMenu.jsx": { - statements: 80, - lines: 80, - functions: 75, - branches: 75, + statements: 86, + lines: 86, + functions: 83, }, "content-src/components/DiscoveryStreamComponents/**/*.jsx": { statements: 90.48, diff --git a/browser/extensions/newtab/lib/DiscoveryStreamFeed.sys.mjs b/browser/extensions/newtab/lib/DiscoveryStreamFeed.sys.mjs index 9ab16ca6d79..35184486c71 100644 --- a/browser/extensions/newtab/lib/DiscoveryStreamFeed.sys.mjs +++ b/browser/extensions/newtab/lib/DiscoveryStreamFeed.sys.mjs @@ -397,6 +397,42 @@ export class DiscoveryStreamFeed { ); } + async configureFollowedSections() { + const prefs = this.store.getState().Prefs.values; + const cachedData = (await this.cache.get()) || {}; + let { sectionData } = cachedData; + + // if sectionData is empty, populate it with data from the followed and blocked prefs + // eventually we could remove this (maybe once more of sections is added to release) + if (sectionData && Object.keys(sectionData).length === 0) { + // Raw string of followed/blocked topics, ex: "entertainment, news" + const followedSectionsString = prefs[PREF_SECTIONS_FOLLOWING]; + const blockedSectionsString = prefs[PREF_SECTIONS_BLOCKED]; + // Format followed sections + const followedSections = followedSectionsString + ? followedSectionsString.split(",").map(s => s.trim()) + : []; + + // Format blocked sections + const blockedSections = blockedSectionsString + ? blockedSectionsString.split(",").map(s => s.trim()) + : []; + + const sectionTopics = new Set([...followedSections, ...blockedSections]); + sectionData = Array.from(sectionTopics).reduce((acc, section) => { + acc[section] = { + isFollowed: followedSections.includes(section), + isBlocked: blockedSections.includes(section), + }; + return acc; + }, {}); + await this.cache.set("sectionData", sectionData); + } + this.store.dispatch( + ac.AlsoToMain({ type: at.SECTION_DATA_UPDATE, data: sectionData }) + ); + } + async setupPocketState(target) { let dispatch = action => this.store.dispatch(ac.OnlyToOneContent(action, target)); @@ -1630,7 +1666,7 @@ export class DiscoveryStreamFeed { let feed = feeds ? feeds[feedUrl] : null; if (this.isExpired({ cachedData, key: "feed", url: feedUrl, isStartup })) { - const options = this.formatComponentFeedRequest(); + const options = this.formatComponentFeedRequest(cachedData.sectionData); const feedResponse = await this.fetchFromEndpoint(feedUrl, options); @@ -1859,7 +1895,7 @@ export class DiscoveryStreamFeed { } } - formatComponentFeedRequest() { + formatComponentFeedRequest(sectionData = {}) { const prefs = this.store.getState().Prefs.values; const headers = new Headers(); if (this.isMerino) { @@ -1877,32 +1913,12 @@ export class DiscoveryStreamFeed { PREF_MERINO_FEED_EXPERIMENT ); - // Raw string of followed/blocked topics, ex: "entertainment, news" - const followedSectionsString = prefs[PREF_SECTIONS_FOLLOWING]; - const blockedSectionsString = prefs[PREF_SECTIONS_BLOCKED]; - - // Format followed sections - const followedSections = followedSectionsString - ? followedSectionsString.split(",").map(s => s.trim()) - : []; - - // Format blocked sections - const blockedSections = blockedSectionsString - ? blockedSectionsString.split(",").map(s => s.trim()) - : []; - - // Combine followed and blocked sections and format into desired JSON shape for merino. - // Example: - // { - // "sectionId": "business", - // "isFollowed": true, - // "isBlocked": false - // } - const sectionTopics = new Set([...followedSections, ...blockedSections]); - const sections = Array.from(sectionTopics).map(section => ({ - sectionId: section, - isFollowed: followedSections.includes(section), - isBlocked: blockedSections.includes(section), + // convert section to array to match what merino is expecting + const sections = Object.entries(sectionData).map(([sectionId, data]) => ({ + sectionId, + isFollowed: data.isFollowed, + isBlocked: data.isBlocked, + ...(data.followedAt && { followedAt: data.followedAt }), })); // To display the inline interest picker pass `enableInterestPicker` into the request @@ -2540,6 +2556,7 @@ export class DiscoveryStreamFeed { if (this.config.enabled) { await this.enable({ updateOpenTabs: true, isStartup: true }); } + await this.configureFollowedSections(); Services.prefs.addObserver(PREF_POCKET_BUTTON, this); // This function is async but just for devtools, // so we don't need to wait for it. @@ -2805,6 +2822,8 @@ export class DiscoveryStreamFeed { case at.TOPIC_SELECTION_IMPRESSION: this.topicSelectionImpressionEvent(); break; + case at.SECTION_DATA_UPDATE: + await this.cache.set("sectionData", action.data); } } } diff --git a/browser/extensions/newtab/test/unit/content-src/components/DiscoveryStreamComponents/AdBannerContextMenu.test.jsx b/browser/extensions/newtab/test/unit/content-src/components/DiscoveryStreamComponents/AdBannerContextMenu.test.jsx index d757d6e6c66..605d3551a26 100644 --- a/browser/extensions/newtab/test/unit/content-src/components/DiscoveryStreamComponents/AdBannerContextMenu.test.jsx +++ b/browser/extensions/newtab/test/unit/content-src/components/DiscoveryStreamComponents/AdBannerContextMenu.test.jsx @@ -51,6 +51,22 @@ describe("", () => { assert.equal(wrapper.find(LinkMenu).length, 1); }); + it("should render LinkMenu when context menu is accessed with the 'Enter' key", () => { + let button = wrapper.find("moz-button"); + + button.simulate("keydown", { key: "Enter", preventDefault: () => {} }); + + assert.equal(wrapper.find(LinkMenu).length, 1); + }); + + it("should render LinkMenu when context menu is accessed with the 'Space' key", () => { + let button = wrapper.find("moz-button"); + + button.simulate("keydown", { key: " ", preventDefault: () => {} }); + + assert.equal(wrapper.find(LinkMenu).length, 1); + }); + it("should pass props to LinkMenu", () => { wrapper.find("moz-button").simulate("click", { preventDefault: () => {}, @@ -59,6 +75,7 @@ describe("", () => { [ "onUpdate", "dispatch", + "keyboardAccess", "options", "shouldSendImpressionStats", "userEvent", diff --git a/browser/extensions/newtab/test/unit/content-src/components/DiscoveryStreamComponents/CardSections.test.jsx b/browser/extensions/newtab/test/unit/content-src/components/DiscoveryStreamComponents/CardSections.test.jsx index 8134dbe88f6..4dc49935460 100644 --- a/browser/extensions/newtab/test/unit/content-src/components/DiscoveryStreamComponents/CardSections.test.jsx +++ b/browser/extensions/newtab/test/unit/content-src/components/DiscoveryStreamComponents/CardSections.test.jsx @@ -5,7 +5,6 @@ import { INITIAL_STATE, reducers } from "common/Reducers.sys.mjs"; import { CardSections } from "content-src/components/DiscoveryStreamComponents/CardSections/CardSections"; import { combineReducers, createStore } from "redux"; import { DSCard } from "../../../../../content-src/components/DiscoveryStreamComponents/DSCard/DSCard"; -const PREF_FOLLOWED_SECTIONS = "discoverystream.sections.following"; const PREF_SECTIONS_PERSONALIZATION_ENABLED = "discoverystream.sections.personalization.enabled"; @@ -206,7 +205,9 @@ describe("", () => { }); }); - it("should dispatch PREF_FOLLOWED_SECTIONS updates with follow and unfollow", () => { + it("should dispatch SECTION_DATA_UPDATE updates with follow and unfollow", () => { + const fakeDate = "2020-01-01T00:00:00.000Z"; + sandbox.useFakeTimers(new Date(fakeDate)); const layout = { title: "layout_name", responsiveLayouts: [ @@ -244,11 +245,19 @@ describe("", () => { // mock the pref for followed section const state = { ...INITIAL_STATE, + DiscoveryStream: { + ...INITIAL_STATE.DiscoveryStream, + sectionData: { + section_key_2: { + isFollowed: true, + isBlocked: false, + }, + }, + }, Prefs: { ...INITIAL_STATE.Prefs, values: { ...INITIAL_STATE.Prefs.values, - [PREF_FOLLOWED_SECTIONS]: "section_key_2", [PREF_SECTIONS_PERSONALIZATION_ENABLED]: true, }, }, @@ -297,11 +306,18 @@ describe("", () => { let button = wrapper.find(".section-follow moz-button").first(); button.simulate("click", {}); - assert.calledWith(dispatch.getCall(0), { - type: "SET_PREF", + assert.deepEqual(dispatch.getCall(0).firstArg, { + type: "SECTION_DATA_UPDATE", data: { - name: "discoverystream.sections.following", - value: "section_key_2, section_key_1", + section_key_2: { + isFollowed: true, + isBlocked: false, + }, + section_key_1: { + isFollowed: true, + isBlocked: false, + followedAt: fakeDate, + }, }, meta: { from: "ActivityStream:Content", @@ -327,11 +343,8 @@ describe("", () => { button.simulate("click", {}); assert.calledWith(dispatch.getCall(2), { - type: "SET_PREF", - data: { - name: "discoverystream.sections.following", - value: "", - }, + type: "SECTION_DATA_UPDATE", + data: {}, meta: { from: "ActivityStream:Content", to: "ActivityStream:Main", diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in index 8ce07e228f5..d0695293417 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -392,6 +392,12 @@ bin/libfreebl_64int_3.so #endif #endif +; [ crashhelper ] +; +#ifdef MOZ_CRASHREPORTER +@BINPATH@/crashhelper@BIN_SUFFIX@ +#endif + ; [ Ping Sender ] ; @BINPATH@/pingsender@BIN_SUFFIX@ diff --git a/browser/installer/windows/nsis/shared.nsh b/browser/installer/windows/nsis/shared.nsh index bc8f5775785..17dfb2aece8 100755 --- a/browser/installer/windows/nsis/shared.nsh +++ b/browser/installer/windows/nsis/shared.nsh @@ -1585,6 +1585,7 @@ ${RemoveDefaultBrowserAgentShortcut} Push "nssdbm3.dll" Push "mozsqlite3.dll" Push "xpcom.dll" + Push "crashhelper.exe" Push "crashreporter.exe" Push "default-browser-agent.exe" Push "nmhproxy.exe" diff --git a/browser/locales/en-US/browser/clearDataForSite.ftl b/browser/locales/en-US/browser/clearDataForSite.ftl new file mode 100644 index 00000000000..bfcdadbb893 --- /dev/null +++ b/browser/locales/en-US/browser/clearDataForSite.ftl @@ -0,0 +1,15 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +clear-data-for-site-title = Clear all data for this website? +# Variables: +# $site (string) - Website name +clear-data-for-site-list = This will clear all data for { $site } including: +clear-data-for-site-browsing-history = Browsing and download history +clear-data-for-site-cookies = Cookies and site data, which may sign you out of the site +clear-data-for-site-cache = Cached files and pages +cclear-data-for-site-permissions = Permissions and preferences +clear-data-for-site-exceptions = Bookmarks and saved passwords won’t be deleted +clear-data-for-site-dialog-accept-button = Clear data +clear-data-for-site-dialog-cancel-button = Cancel diff --git a/browser/locales/en-US/browser/places.ftl b/browser/locales/en-US/browser/places.ftl index c342c2610b6..e39efac45f2 100644 --- a/browser/locales/en-US/browser/places.ftl +++ b/browser/locales/en-US/browser/places.ftl @@ -144,16 +144,6 @@ places-manage-bookmarks = .label = Manage Bookmarks .accesskey = M -places-forget-about-this-site-confirmation-title = - Forgetting about this site - -# Variables: -# $hostOrBaseDomain (string) - The base domain (or host in case there is no base domain) for which data is being removed -places-forget-about-this-site-confirmation-msg = - This action will remove data related to { $hostOrBaseDomain } including history, cookies, cache and content preferences. Related bookmarks and passwords will not be removed. Are you sure you want to proceed? - -places-forget-about-this-site-forget = Forget - places-library3 = .title = Library diff --git a/browser/locales/l10n-changesets.json b/browser/locales/l10n-changesets.json index 18c87a4f1a7..fde87078b88 100644 --- a/browser/locales/l10n-changesets.json +++ b/browser/locales/l10n-changesets.json @@ -17,7 +17,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "af": { "pin": false, @@ -37,7 +37,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "an": { "pin": false, @@ -57,7 +57,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "ar": { "pin": false, @@ -77,7 +77,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "ast": { "pin": false, @@ -97,7 +97,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "az": { "pin": false, @@ -117,7 +117,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "be": { "pin": false, @@ -137,7 +137,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "bg": { "pin": false, @@ -157,7 +157,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "bn": { "pin": false, @@ -177,7 +177,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "bo": { "pin": false, @@ -197,7 +197,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "br": { "pin": false, @@ -217,7 +217,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "brx": { "pin": false, @@ -237,7 +237,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "bs": { "pin": false, @@ -257,7 +257,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "ca": { "pin": false, @@ -277,7 +277,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "ca-valencia": { "pin": false, @@ -297,7 +297,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "cak": { "pin": false, @@ -317,7 +317,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "ckb": { "pin": false, @@ -337,7 +337,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "cs": { "pin": false, @@ -357,7 +357,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "cy": { "pin": false, @@ -377,7 +377,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "da": { "pin": false, @@ -397,7 +397,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "de": { "pin": false, @@ -417,7 +417,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "dsb": { "pin": false, @@ -437,7 +437,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "el": { "pin": false, @@ -457,7 +457,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "en-CA": { "pin": false, @@ -477,7 +477,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "en-GB": { "pin": false, @@ -497,7 +497,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "eo": { "pin": false, @@ -517,7 +517,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "es-AR": { "pin": false, @@ -537,7 +537,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "es-CL": { "pin": false, @@ -557,7 +557,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "es-ES": { "pin": false, @@ -577,7 +577,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "es-MX": { "pin": false, @@ -597,7 +597,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "et": { "pin": false, @@ -617,7 +617,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "eu": { "pin": false, @@ -637,7 +637,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "fa": { "pin": false, @@ -657,7 +657,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "ff": { "pin": false, @@ -677,7 +677,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "fi": { "pin": false, @@ -697,7 +697,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "fr": { "pin": false, @@ -717,7 +717,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "fur": { "pin": false, @@ -737,7 +737,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "fy-NL": { "pin": false, @@ -757,7 +757,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "ga-IE": { "pin": false, @@ -777,7 +777,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "gd": { "pin": false, @@ -797,7 +797,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "gl": { "pin": false, @@ -817,7 +817,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "gn": { "pin": false, @@ -837,7 +837,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "gu-IN": { "pin": false, @@ -857,7 +857,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "he": { "pin": false, @@ -877,7 +877,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "hi-IN": { "pin": false, @@ -897,7 +897,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "hr": { "pin": false, @@ -917,7 +917,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "hsb": { "pin": false, @@ -937,7 +937,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "hu": { "pin": false, @@ -957,7 +957,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "hy-AM": { "pin": false, @@ -977,7 +977,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "hye": { "pin": false, @@ -997,7 +997,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "ia": { "pin": false, @@ -1017,7 +1017,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "id": { "pin": false, @@ -1037,7 +1037,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "is": { "pin": false, @@ -1057,7 +1057,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "it": { "pin": false, @@ -1077,7 +1077,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "ja": { "pin": false, @@ -1095,7 +1095,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "ja-JP-mac": { "pin": false, @@ -1103,7 +1103,7 @@ "macosx64", "macosx64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "ka": { "pin": false, @@ -1123,7 +1123,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "kab": { "pin": false, @@ -1143,7 +1143,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "kk": { "pin": false, @@ -1163,7 +1163,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "km": { "pin": false, @@ -1183,7 +1183,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "kn": { "pin": false, @@ -1203,7 +1203,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "ko": { "pin": false, @@ -1223,7 +1223,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "lij": { "pin": false, @@ -1243,7 +1243,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "lo": { "pin": false, @@ -1263,7 +1263,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "lt": { "pin": false, @@ -1283,7 +1283,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "ltg": { "pin": false, @@ -1303,7 +1303,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "lv": { "pin": false, @@ -1323,7 +1323,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "meh": { "pin": false, @@ -1343,7 +1343,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "mk": { "pin": false, @@ -1363,7 +1363,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "ml": { "pin": false, @@ -1383,7 +1383,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "mr": { "pin": false, @@ -1403,7 +1403,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "ms": { "pin": false, @@ -1423,7 +1423,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "my": { "pin": false, @@ -1443,7 +1443,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "nb-NO": { "pin": false, @@ -1463,7 +1463,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "ne-NP": { "pin": false, @@ -1483,7 +1483,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "nl": { "pin": false, @@ -1503,7 +1503,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "nn-NO": { "pin": false, @@ -1523,7 +1523,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "oc": { "pin": false, @@ -1543,7 +1543,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "pa-IN": { "pin": false, @@ -1563,7 +1563,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "pl": { "pin": false, @@ -1583,7 +1583,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "pt-BR": { "pin": false, @@ -1603,7 +1603,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "pt-PT": { "pin": false, @@ -1623,7 +1623,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "rm": { "pin": false, @@ -1643,7 +1643,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "ro": { "pin": false, @@ -1663,7 +1663,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "ru": { "pin": false, @@ -1683,7 +1683,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "sat": { "pin": false, @@ -1703,7 +1703,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "sc": { "pin": false, @@ -1723,7 +1723,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "scn": { "pin": false, @@ -1743,7 +1743,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "sco": { "pin": false, @@ -1763,7 +1763,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "si": { "pin": false, @@ -1783,7 +1783,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "sk": { "pin": false, @@ -1803,7 +1803,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "skr": { "pin": false, @@ -1823,7 +1823,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "sl": { "pin": false, @@ -1843,7 +1843,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "son": { "pin": false, @@ -1863,7 +1863,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "sq": { "pin": false, @@ -1883,7 +1883,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "sr": { "pin": false, @@ -1903,7 +1903,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "sv-SE": { "pin": false, @@ -1923,7 +1923,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "szl": { "pin": false, @@ -1943,7 +1943,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "ta": { "pin": false, @@ -1963,7 +1963,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "te": { "pin": false, @@ -1983,7 +1983,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "tg": { "pin": false, @@ -2003,7 +2003,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "th": { "pin": false, @@ -2023,7 +2023,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "tl": { "pin": false, @@ -2043,7 +2043,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "tr": { "pin": false, @@ -2063,7 +2063,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "trs": { "pin": false, @@ -2083,7 +2083,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "uk": { "pin": false, @@ -2103,7 +2103,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "ur": { "pin": false, @@ -2123,7 +2123,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "uz": { "pin": false, @@ -2143,7 +2143,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "vi": { "pin": false, @@ -2163,7 +2163,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "wo": { "pin": false, @@ -2183,7 +2183,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "xh": { "pin": false, @@ -2203,7 +2203,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "zh-CN": { "pin": false, @@ -2223,7 +2223,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" }, "zh-TW": { "pin": false, @@ -2243,6 +2243,6 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "6d906285db6cbe1db3dac5621e7b3c91678bb22f" + "revision": "08bc97af026e544b670afa158c6e6c9f41d4e861" } } \ No newline at end of file diff --git a/build/autoconf/acgeneral.m4 b/build/autoconf/acgeneral.m4 deleted file mode 100644 index 772622849ba..00000000000 --- a/build/autoconf/acgeneral.m4 +++ /dev/null @@ -1,2607 +0,0 @@ -dnl Parameterized macros. -dnl Requires GNU m4. -dnl This file is part of Autoconf. -dnl Copyright (C) 1992, 93, 94, 95, 96, 1998 Free Software Foundation, Inc. -dnl -dnl This program is free software; you can redistribute it and/or modify -dnl it under the terms of the GNU General Public License as published by -dnl the Free Software Foundation; either version 2, or (at your option) -dnl any later version. -dnl -dnl This program is distributed in the hope that it will be useful, -dnl but WITHOUT ANY WARRANTY; without even the implied warranty of -dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -dnl GNU General Public License for more details. -dnl -dnl You should have received a copy of the GNU General Public License -dnl along with this program; if not, write to the Free Software -dnl Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA -dnl 02111-1307, USA. -dnl -dnl As a special exception, the Free Software Foundation gives unlimited -dnl permission to copy, distribute and modify the configure scripts that -dnl are the output of Autoconf. You need not follow the terms of the GNU -dnl General Public License when using or distributing such scripts, even -dnl though portions of the text of Autoconf appear in them. The GNU -dnl General Public License (GPL) does govern all other use of the material -dnl that constitutes the Autoconf program. -dnl -dnl Certain portions of the Autoconf source text are designed to be copied -dnl (in certain cases, depending on the input) into the output of -dnl Autoconf. We call these the "data" portions. The rest of the Autoconf -dnl source text consists of comments plus executable code that decides which -dnl of the data portions to output in any given case. We call these -dnl comments and executable code the "non-data" portions. Autoconf never -dnl copies any of the non-data portions into its output. -dnl -dnl This special exception to the GPL applies to versions of Autoconf -dnl released by the Free Software Foundation. When you make and -dnl distribute a modified version of Autoconf, you may extend this special -dnl exception to the GPL to apply to your modified version as well, *unless* -dnl your modified version has the potential to copy into its output some -dnl of the text that was the non-data portion of the version that you started -dnl with. (In other words, unless your change moves or copies text from -dnl the non-data portions to the data portions.) If your modification has -dnl such potential, you must delete any notice of this special exception -dnl to the GPL from your modified version. -dnl -dnl Written by David MacKenzie, with help from -dnl Franc,ois Pinard, Karl Berry, Richard Pixley, Ian Lance Taylor, -dnl Roland McGrath, Noah Friedman, david d zuhn, and many others. -dnl -divert(-1)dnl Throw away output until AC_INIT is called. -changequote([, ]) - -define(AC_ACVERSION, 2.13) - -dnl Some old m4's don't support m4exit. But they provide -dnl equivalent functionality by core dumping because of the -dnl long macros we define. -ifdef([__gnu__], , [errprint(Autoconf requires GNU m4. -Install it before installing Autoconf or set the -M4 environment variable to its path name. -)m4exit(2)]) - -undefine([eval]) -undefine([include]) -undefine([shift]) -undefine([format]) - - -dnl ### Defining macros - - -dnl m4 output diversions. We let m4 output them all in order at the end, -dnl except that we explicitly undivert AC_DIVERSION_SED, AC_DIVERSION_CMDS, -dnl and AC_DIVERSION_ICMDS. - -dnl AC_DIVERSION_NOTICE - 1 (= 0) AC_REQUIRE'd #! /bin/sh line -define(AC_DIVERSION_NOTICE, 1)dnl copyright notice & option help strings -define(AC_DIVERSION_INIT, 2)dnl initialization code -define(AC_DIVERSION_NORMAL_4, 3)dnl AC_REQUIRE'd code, 4 level deep -define(AC_DIVERSION_NORMAL_3, 4)dnl AC_REQUIRE'd code, 3 level deep -define(AC_DIVERSION_NORMAL_2, 5)dnl AC_REQUIRE'd code, 2 level deep -define(AC_DIVERSION_NORMAL_1, 6)dnl AC_REQUIRE'd code, 1 level deep -define(AC_DIVERSION_NORMAL, 7)dnl the tests and output code -define(AC_DIVERSION_SED, 8)dnl variable substitutions in config.status -define(AC_DIVERSION_CMDS, 9)dnl extra shell commands in config.status -define(AC_DIVERSION_ICMDS, 10)dnl extra initialization in config.status - -dnl Change the diversion stream to STREAM, while stacking old values. -dnl AC_DIVERT_PUSH(STREAM) -define(AC_DIVERT_PUSH, -[pushdef([AC_DIVERSION_CURRENT], $1)dnl -divert(AC_DIVERSION_CURRENT)dnl -]) - -dnl Change the diversion stream to its previous value, unstacking it. -dnl AC_DIVERT_POP() -define(AC_DIVERT_POP, -[popdef([AC_DIVERSION_CURRENT])dnl -divert(AC_DIVERSION_CURRENT)dnl -]) - -dnl Initialize the diversion setup. -define([AC_DIVERSION_CURRENT], AC_DIVERSION_NORMAL) -dnl This will be popped by AC_REQUIRE in AC_INIT. -pushdef([AC_DIVERSION_CURRENT], AC_DIVERSION_NOTICE) - -dnl The prologue for Autoconf macros. -dnl AC_PRO(MACRO-NAME) -define(AC_PRO, -[define([AC_PROVIDE_$1], )dnl -ifelse(AC_DIVERSION_CURRENT, AC_DIVERSION_NORMAL, -[AC_DIVERT_PUSH(builtin(eval, AC_DIVERSION_CURRENT - 1))], -[pushdef([AC_DIVERSION_CURRENT], AC_DIVERSION_CURRENT)])dnl -]) - -dnl The Epilogue for Autoconf macros. -dnl AC_EPI() -define(AC_EPI, -[AC_DIVERT_POP()dnl -ifelse(AC_DIVERSION_CURRENT, AC_DIVERSION_NORMAL, -[undivert(AC_DIVERSION_NORMAL_4)dnl -undivert(AC_DIVERSION_NORMAL_3)dnl -undivert(AC_DIVERSION_NORMAL_2)dnl -undivert(AC_DIVERSION_NORMAL_1)dnl -])dnl -]) - -dnl Define a macro which automatically provides itself. Add machinery -dnl so the macro automatically switches expansion to the diversion -dnl stack if it is not already using it. In this case, once finished, -dnl it will bring back all the code accumulated in the diversion stack. -dnl This, combined with AC_REQUIRE, achieves the topological ordering of -dnl macros. We don't use this macro to define some frequently called -dnl macros that are not involved in ordering constraints, to save m4 -dnl processing. -dnl AC_DEFUN(NAME, EXPANSION) -define([AC_DEFUN], -[define($1, [AC_PRO([$1])$2[]AC_EPI()])]) - - -dnl ### Initialization - - -dnl AC_INIT_NOTICE() -AC_DEFUN(AC_INIT_NOTICE, -[# Guess values for system-dependent variables and create Makefiles. -# Generated automatically using autoconf version] AC_ACVERSION [ -# Copyright (C) 1992, 93, 94, 95, 96 Free Software Foundation, Inc. -# -# This configure script is free software; the Free Software Foundation -# gives unlimited permission to copy, distribute and modify it. - -# Defaults: -ac_help= -ac_default_prefix=/usr/local -[#] Any additions from configure.in:]) - -dnl AC_PREFIX_DEFAULT(PREFIX) -AC_DEFUN(AC_PREFIX_DEFAULT, -[AC_DIVERT_PUSH(AC_DIVERSION_NOTICE)dnl -ac_default_prefix=$1 -AC_DIVERT_POP()]) - -dnl AC_INIT_PARSE_ARGS() -AC_DEFUN(AC_INIT_PARSE_ARGS, -[ -# Initialize some variables set by options. -# The variables have the same names as the options, with -# dashes changed to underlines. -build=NONE -cache_file=./config.cache -exec_prefix=NONE -host=NONE -no_create= -nonopt=NONE -no_recursion= -prefix=NONE -program_prefix=NONE -program_suffix=NONE -program_transform_name=s,x,x, -silent= -site= -srcdir= -target=NONE -verbose= -x_includes=NONE -x_libraries=NONE -dnl Installation directory options. -dnl These are left unexpanded so users can "make install exec_prefix=/foo" -dnl and all the variables that are supposed to be based on exec_prefix -dnl by default will actually change. -dnl Use braces instead of parens because sh, perl, etc. also accept them. -bindir='${exec_prefix}/bin' -sbindir='${exec_prefix}/sbin' -libexecdir='${exec_prefix}/libexec' -datadir='${prefix}/share' -sysconfdir='${prefix}/etc' -sharedstatedir='${prefix}/com' -localstatedir='${prefix}/var' -libdir='${exec_prefix}/lib' -includedir='${prefix}/include' -oldincludedir='/usr/include' -infodir='${prefix}/info' -mandir='${prefix}/man' - -# Initialize some other variables. -subdirs= -MFLAGS= MAKEFLAGS= -SHELL=${CONFIG_SHELL-/bin/sh} -# Maximum number of lines to put in a shell here document. -ac_max_here_lines=12 - -ac_prev= -for ac_option -do - - # If the previous option needs an argument, assign it. - if test -n "$ac_prev"; then - eval "$ac_prev=\$ac_option" - ac_prev= - continue - fi - - case "$ac_option" in -changequote(, )dnl - -*=*) ac_optarg=`echo "$ac_option" | sed 's/[-_a-zA-Z0-9]*=//'` ;; -changequote([, ])dnl - *) ac_optarg= ;; - esac - - # Accept the important Cygnus configure options, so we can diagnose typos. - - case "$ac_option" in - - -bindir | --bindir | --bindi | --bind | --bin | --bi) - ac_prev=bindir ;; - -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) - bindir="$ac_optarg" ;; - - -build | --build | --buil | --bui | --bu) - ac_prev=build ;; - -build=* | --build=* | --buil=* | --bui=* | --bu=*) - build="$ac_optarg" ;; - - -cache-file | --cache-file | --cache-fil | --cache-fi \ - | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) - ac_prev=cache_file ;; - -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ - | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*) - cache_file="$ac_optarg" ;; - - -datadir | --datadir | --datadi | --datad | --data | --dat | --da) - ac_prev=datadir ;; - -datadir=* | --datadir=* | --datadi=* | --datad=* | --data=* | --dat=* \ - | --da=*) - datadir="$ac_optarg" ;; - - -disable-* | --disable-*) - ac_feature=`echo $ac_option|sed -e 's/-*disable-//'` - # Reject names that are not valid shell variable names. -changequote(, )dnl - if test -n "`echo $ac_feature| sed 's/[-a-zA-Z0-9_]//g'`"; then -changequote([, ])dnl - AC_MSG_ERROR($ac_feature: invalid feature name) - fi - ac_feature=`echo $ac_feature| sed 's/-/_/g'` - eval "enable_${ac_feature}=no" ;; - - -enable-* | --enable-*) - ac_feature=`echo $ac_option|sed -e 's/-*enable-//' -e 's/=.*//'` - # Reject names that are not valid shell variable names. -changequote(, )dnl - if test -n "`echo $ac_feature| sed 's/[-_a-zA-Z0-9]//g'`"; then -changequote([, ])dnl - AC_MSG_ERROR($ac_feature: invalid feature name) - fi - ac_feature=`echo $ac_feature| sed 's/-/_/g'` - case "$ac_option" in - *=*) ;; - *) ac_optarg=yes ;; - esac - eval "enable_${ac_feature}='$ac_optarg'" ;; - - -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ - | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ - | --exec | --exe | --ex) - ac_prev=exec_prefix ;; - -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \ - | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \ - | --exec=* | --exe=* | --ex=*) - exec_prefix="$ac_optarg" ;; - - -gas | --gas | --ga | --g) - # Obsolete; use --with-gas. - with_gas=yes ;; - - -help | --help | --hel | --he) - # Omit some internal or obsolete options to make the list less imposing. - # This message is too long to be a string in the A/UX 3.1 sh. - cat << EOF -changequote(, )dnl -Usage: configure [options] [host] -Options: [defaults in brackets after descriptions] -Configuration: - --cache-file=FILE cache test results in FILE - --help print this message - --no-create do not create output files - --quiet, --silent do not print \`checking...' messages - --version print the version of autoconf that created configure -Directory and file names: - --prefix=PREFIX install architecture-independent files in PREFIX - [$ac_default_prefix] - --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX - [same as prefix] - --bindir=DIR user executables in DIR [EPREFIX/bin] - --sbindir=DIR system admin executables in DIR [EPREFIX/sbin] - --libexecdir=DIR program executables in DIR [EPREFIX/libexec] - --datadir=DIR read-only architecture-independent data in DIR - [PREFIX/share] - --sysconfdir=DIR read-only single-machine data in DIR [PREFIX/etc] - --sharedstatedir=DIR modifiable architecture-independent data in DIR - [PREFIX/com] - --localstatedir=DIR modifiable single-machine data in DIR [PREFIX/var] - --libdir=DIR object code libraries in DIR [EPREFIX/lib] - --includedir=DIR C header files in DIR [PREFIX/include] - --oldincludedir=DIR C header files for non-gcc in DIR [/usr/include] - --infodir=DIR info documentation in DIR [PREFIX/info] - --mandir=DIR man documentation in DIR [PREFIX/man] - --srcdir=DIR find the sources in DIR [configure dir or ..] - --program-prefix=PREFIX prepend PREFIX to installed program names - --program-suffix=SUFFIX append SUFFIX to installed program names - --program-transform-name=PROGRAM - run sed PROGRAM on installed program names -EOF - cat << EOF -Host type: - --build=BUILD configure for building on BUILD [BUILD=HOST] - --host=HOST configure for HOST [guessed] - --target=TARGET configure for TARGET [TARGET=HOST] -Features and packages: - --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) - --enable-FEATURE[=ARG] include FEATURE [ARG=yes] - --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] - --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) - --x-includes=DIR X include files are in DIR - --x-libraries=DIR X library files are in DIR -changequote([, ])dnl -EOF - if test -n "$ac_help"; then - echo "--enable and --with options recognized:$ac_help" - fi - exit 0 ;; - - -host | --host | --hos | --ho) - ac_prev=host ;; - -host=* | --host=* | --hos=* | --ho=*) - host="$ac_optarg" ;; - - -includedir | --includedir | --includedi | --included | --include \ - | --includ | --inclu | --incl | --inc) - ac_prev=includedir ;; - -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ - | --includ=* | --inclu=* | --incl=* | --inc=*) - includedir="$ac_optarg" ;; - - -infodir | --infodir | --infodi | --infod | --info | --inf) - ac_prev=infodir ;; - -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*) - infodir="$ac_optarg" ;; - - -libdir | --libdir | --libdi | --libd) - ac_prev=libdir ;; - -libdir=* | --libdir=* | --libdi=* | --libd=*) - libdir="$ac_optarg" ;; - - -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \ - | --libexe | --libex | --libe) - ac_prev=libexecdir ;; - -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \ - | --libexe=* | --libex=* | --libe=*) - libexecdir="$ac_optarg" ;; - - -localstatedir | --localstatedir | --localstatedi | --localstated \ - | --localstate | --localstat | --localsta | --localst \ - | --locals | --local | --loca | --loc | --lo) - ac_prev=localstatedir ;; - -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \ - | --localstate=* | --localstat=* | --localsta=* | --localst=* \ - | --locals=* | --local=* | --loca=* | --loc=* | --lo=*) - localstatedir="$ac_optarg" ;; - - -mandir | --mandir | --mandi | --mand | --man | --ma | --m) - ac_prev=mandir ;; - -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*) - mandir="$ac_optarg" ;; - - -nfp | --nfp | --nf) - # Obsolete; use --without-fp. - with_fp=no ;; - - -no-create | --no-create | --no-creat | --no-crea | --no-cre \ - | --no-cr | --no-c) - no_create=yes ;; - - -no-recursion | --no-recursion | --no-recursio | --no-recursi \ - | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) - no_recursion=yes ;; - - -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \ - | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \ - | --oldin | --oldi | --old | --ol | --o) - ac_prev=oldincludedir ;; - -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \ - | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \ - | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*) - oldincludedir="$ac_optarg" ;; - - -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) - ac_prev=prefix ;; - -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) - prefix="$ac_optarg" ;; - - -program-prefix | --program-prefix | --program-prefi | --program-pref \ - | --program-pre | --program-pr | --program-p) - ac_prev=program_prefix ;; - -program-prefix=* | --program-prefix=* | --program-prefi=* \ - | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*) - program_prefix="$ac_optarg" ;; - - -program-suffix | --program-suffix | --program-suffi | --program-suff \ - | --program-suf | --program-su | --program-s) - ac_prev=program_suffix ;; - -program-suffix=* | --program-suffix=* | --program-suffi=* \ - | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*) - program_suffix="$ac_optarg" ;; - - -program-transform-name | --program-transform-name \ - | --program-transform-nam | --program-transform-na \ - | --program-transform-n | --program-transform- \ - | --program-transform | --program-transfor \ - | --program-transfo | --program-transf \ - | --program-trans | --program-tran \ - | --progr-tra | --program-tr | --program-t) - ac_prev=program_transform_name ;; - -program-transform-name=* | --program-transform-name=* \ - | --program-transform-nam=* | --program-transform-na=* \ - | --program-transform-n=* | --program-transform-=* \ - | --program-transform=* | --program-transfor=* \ - | --program-transfo=* | --program-transf=* \ - | --program-trans=* | --program-tran=* \ - | --progr-tra=* | --program-tr=* | --program-t=*) - program_transform_name="$ac_optarg" ;; - - -q | -quiet | --quiet | --quie | --qui | --qu | --q \ - | -silent | --silent | --silen | --sile | --sil) - silent=yes ;; - - -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) - ac_prev=sbindir ;; - -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ - | --sbi=* | --sb=*) - sbindir="$ac_optarg" ;; - - -sharedstatedir | --sharedstatedir | --sharedstatedi \ - | --sharedstated | --sharedstate | --sharedstat | --sharedsta \ - | --sharedst | --shareds | --shared | --share | --shar \ - | --sha | --sh) - ac_prev=sharedstatedir ;; - -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \ - | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \ - | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \ - | --sha=* | --sh=*) - sharedstatedir="$ac_optarg" ;; - - -site | --site | --sit) - ac_prev=site ;; - -site=* | --site=* | --sit=*) - site="$ac_optarg" ;; - - -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) - ac_prev=srcdir ;; - -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) - srcdir="$ac_optarg" ;; - - -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ - | --syscon | --sysco | --sysc | --sys | --sy) - ac_prev=sysconfdir ;; - -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ - | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*) - sysconfdir="$ac_optarg" ;; - - -target | --target | --targe | --targ | --tar | --ta | --t) - ac_prev=target ;; - -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*) - target="$ac_optarg" ;; - - -v | -verbose | --verbose | --verbos | --verbo | --verb) - verbose=yes ;; - - -version | --version | --versio | --versi | --vers) - echo "configure generated by autoconf version AC_ACVERSION" - exit 0 ;; - - -with-* | --with-*) - ac_package=`echo $ac_option|sed -e 's/-*with-//' -e 's/=.*//'` - # Reject names that are not valid shell variable names. -changequote(, )dnl - if test -n "`echo $ac_package| sed 's/[-_a-zA-Z0-9]//g'`"; then -changequote([, ])dnl - AC_MSG_ERROR($ac_package: invalid package name) - fi - ac_package=`echo $ac_package| sed 's/-/_/g'` - case "$ac_option" in - *=*) ;; - *) ac_optarg=yes ;; - esac - eval "with_${ac_package}='$ac_optarg'" ;; - - -without-* | --without-*) - ac_package=`echo $ac_option|sed -e 's/-*without-//'` - # Reject names that are not valid shell variable names. -changequote(, )dnl - if test -n "`echo $ac_package| sed 's/[-a-zA-Z0-9_]//g'`"; then -changequote([, ])dnl - AC_MSG_ERROR($ac_package: invalid package name) - fi - ac_package=`echo $ac_package| sed 's/-/_/g'` - eval "with_${ac_package}=no" ;; - - --x) - # Obsolete; use --with-x. - with_x=yes ;; - - -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \ - | --x-incl | --x-inc | --x-in | --x-i) - ac_prev=x_includes ;; - -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \ - | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*) - x_includes="$ac_optarg" ;; - - -x-libraries | --x-libraries | --x-librarie | --x-librari \ - | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l) - ac_prev=x_libraries ;; - -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \ - | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) - x_libraries="$ac_optarg" ;; - - -*) AC_MSG_ERROR([$ac_option: invalid option; use --help to show usage]) - ;; - - *) -changequote(, )dnl - if test -n "`echo $ac_option| sed 's/[-a-z0-9.]//g'`"; then -changequote([, ])dnl - AC_MSG_WARN($ac_option: invalid host type) - fi - if test "x$nonopt" != xNONE; then - AC_MSG_ERROR(can only configure for one host and one target at a time) - fi - nonopt="$ac_option" - ;; - - esac -done - -if test -n "$ac_prev"; then - AC_MSG_ERROR(missing argument to --`echo $ac_prev | sed 's/_/-/g'`) -fi -]) - -dnl Try to have only one #! line, so the script doesn't look funny -dnl for users of AC_REVISION. -dnl AC_INIT_BINSH() -AC_DEFUN(AC_INIT_BINSH, -[#! /bin/sh -]) - -dnl AC_INIT(UNIQUE-FILE-IN-SOURCE-DIR) -AC_DEFUN(AC_INIT, -[sinclude(acsite.m4)dnl -sinclude(./aclocal.m4)dnl -AC_REQUIRE([AC_INIT_BINSH])dnl -AC_INIT_NOTICE -AC_DIVERT_POP()dnl to NORMAL -AC_DIVERT_PUSH(AC_DIVERSION_INIT)dnl -AC_INIT_PARSE_ARGS -AC_INIT_PREPARE($1)dnl -AC_DIVERT_POP()dnl to NORMAL -]) - -dnl AC_INIT_PREPARE(UNIQUE-FILE-IN-SOURCE-DIR) -AC_DEFUN(AC_INIT_PREPARE, -[trap 'rm -fr conftest* confdefs* core core.* *.core $ac_clean_files; exit 1' 1 2 15 - -# File descriptor usage: -# 0 standard input -# 1 file creation -# 2 errors and warnings -# 3 some systems may open it to /dev/tty -# 4 used on the Kubota Titan -define(AC_FD_MSG, 6)dnl -[#] AC_FD_MSG checking for... messages and results -define(AC_FD_CC, 5)dnl -[#] AC_FD_CC compiler messages saved in config.log -if test "$silent" = yes; then - exec AC_FD_MSG>/dev/null -else - exec AC_FD_MSG>&1 -fi -exec AC_FD_CC>./config.log - -echo "\ -This file contains any messages produced by compilers while -running configure, to aid debugging if configure makes a mistake. -" 1>&AC_FD_CC - -# Strip out --no-create and --no-recursion so they do not pile up. -# Also quote any args containing shell metacharacters. -ac_configure_args= -for ac_arg -do - case "$ac_arg" in - -no-create | --no-create | --no-creat | --no-crea | --no-cre \ - | --no-cr | --no-c) ;; - -no-recursion | --no-recursion | --no-recursio | --no-recursi \ - | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) ;; -changequote(<<, >>)dnl -dnl If you change this globbing pattern, test it on an old shell -- -dnl it's sensitive. Putting any kind of quote in it causes syntax errors. - *" "*|*" "*|*[\[\]\~\<<#>>\$\^\&\*\(\)\{\}\\\|\;\<\>\?]*) - ac_configure_args="$ac_configure_args '$ac_arg'" ;; -changequote([, ])dnl - *) ac_configure_args="$ac_configure_args $ac_arg" ;; - esac -done - -# NLS nuisances. -# Only set these to C if already set. These must not be set unconditionally -# because not all systems understand e.g. LANG=C (notably SCO). -# Fixing LC_MESSAGES prevents Solaris sh from translating var values in `set'! -# Non-C LC_CTYPE values break the ctype check. -if test "${LANG+set}" = set; then LANG=C; export LANG; fi -if test "${LC_ALL+set}" = set; then LC_ALL=C; export LC_ALL; fi -if test "${LC_MESSAGES+set}" = set; then LC_MESSAGES=C; export LC_MESSAGES; fi -if test "${LC_CTYPE+set}" = set; then LC_CTYPE=C; export LC_CTYPE; fi - -# confdefs.h avoids OS command line length limits that DEFS can exceed. -rm -rf conftest* confdefs.h -# AIX cpp loses on an empty file, so make sure it contains at least a newline. -echo > confdefs.h - -# A filename unique to this package, relative to the directory that -# configure is in, which we can look for to find out if srcdir is correct. -ac_unique_file=$1 - -# Find the source files, if location was not specified. -if test -z "$srcdir"; then - ac_srcdir_defaulted=yes - # Try the directory containing this script, then its parent. - ac_prog=[$]0 -changequote(, )dnl - ac_confdir=`echo $ac_prog|sed 's%/[^/][^/]*$%%'` -changequote([, ])dnl - test "x$ac_confdir" = "x$ac_prog" && ac_confdir=. - srcdir=$ac_confdir - if test ! -r $srcdir/$ac_unique_file; then - srcdir=.. - fi -else - ac_srcdir_defaulted=no -fi -if test ! -r $srcdir/$ac_unique_file; then - if test "$ac_srcdir_defaulted" = yes; then - AC_MSG_ERROR(can not find sources in $ac_confdir or ..) - else - AC_MSG_ERROR(can not find sources in $srcdir) - fi -fi -dnl Double slashes in pathnames in object file debugging info -dnl mess up M-x gdb in Emacs. -changequote(, )dnl -srcdir=`echo "${srcdir}" | sed 's%\([^/]\)/*$%\1%'` -changequote([, ])dnl - -dnl Let the site file select an alternate cache file if it wants to. -AC_SITE_LOAD -AC_CACHE_LOAD -AC_LANG_C -dnl By default always use an empty string as the executable -dnl extension. Only change it if the script calls AC_EXEEXT. -ac_exeext= -dnl By default assume that objects files use an extension of .o. Only -dnl change it if the script calls AC_OBJEXT. -ac_objext=o -AC_PROG_ECHO_N -dnl Substitute for predefined variables. -AC_SUBST(SHELL)dnl -AC_SUBST(CFLAGS)dnl -AC_SUBST(CPPFLAGS)dnl -AC_SUBST(CXXFLAGS)dnl -AC_SUBST(FFLAGS)dnl -AC_SUBST(DEFS)dnl -AC_SUBST(LDFLAGS)dnl -AC_SUBST(LIBS)dnl -AC_SUBST(exec_prefix)dnl -AC_SUBST(prefix)dnl -AC_SUBST(program_transform_name)dnl -dnl Installation directory options. -AC_SUBST(bindir)dnl -AC_SUBST(sbindir)dnl -AC_SUBST(libexecdir)dnl -AC_SUBST(datadir)dnl -AC_SUBST(sysconfdir)dnl -AC_SUBST(sharedstatedir)dnl -AC_SUBST(localstatedir)dnl -AC_SUBST(libdir)dnl -AC_SUBST(includedir)dnl -AC_SUBST(oldincludedir)dnl -AC_SUBST(infodir)dnl -AC_SUBST(mandir)dnl -]) - - -dnl ### Selecting optional features - - -dnl AC_ARG_ENABLE(FEATURE, HELP-STRING, ACTION-IF-TRUE [, ACTION-IF-FALSE]) -AC_DEFUN(AC_ARG_ENABLE, -[AC_DIVERT_PUSH(AC_DIVERSION_NOTICE)dnl -ac_help="$ac_help -[$2]" -AC_DIVERT_POP()dnl -[#] Check whether --enable-[$1] or --disable-[$1] was given. -if test "[${enable_]patsubst([$1], -, _)+set}" = set; then - enableval="[$enable_]patsubst([$1], -, _)" - ifelse([$3], , :, [$3]) -ifelse([$4], , , [else - $4 -])dnl -fi -]) - -AC_DEFUN(AC_ENABLE, -[AC_OBSOLETE([$0], [; instead use AC_ARG_ENABLE])dnl -AC_ARG_ENABLE([$1], [ --enable-$1], [$2], [$3])dnl -]) - - -dnl ### Working with optional software - - -dnl AC_ARG_WITH(PACKAGE, HELP-STRING, ACTION-IF-TRUE [, ACTION-IF-FALSE]) -AC_DEFUN(AC_ARG_WITH, -[AC_DIVERT_PUSH(AC_DIVERSION_NOTICE)dnl -ac_help="$ac_help -[$2]" -AC_DIVERT_POP()dnl -[#] Check whether --with-[$1] or --without-[$1] was given. -if test "[${with_]patsubst([$1], -, _)+set}" = set; then - withval="[$with_]patsubst([$1], -, _)" - ifelse([$3], , :, [$3]) -ifelse([$4], , , [else - $4 -])dnl -fi -]) - -AC_DEFUN(AC_WITH, -[AC_OBSOLETE([$0], [; instead use AC_ARG_WITH])dnl -AC_ARG_WITH([$1], [ --with-$1], [$2], [$3])dnl -]) - - -dnl ### Transforming program names. - - -dnl AC_ARG_PROGRAM() -AC_DEFUN(AC_ARG_PROGRAM, -[if test "$program_transform_name" = s,x,x,; then - program_transform_name= -else - # Double any \ or $. echo might interpret backslashes. - cat <<\EOF_SED > conftestsed -s,\\,\\\\,g; s,\$,$$,g -EOF_SED - program_transform_name="`echo $program_transform_name|sed -f conftestsed`" - rm -f conftestsed -fi -test "$program_prefix" != NONE && - program_transform_name="s,^,${program_prefix},; $program_transform_name" -# Use a double $ so make ignores it. -test "$program_suffix" != NONE && - program_transform_name="s,\$\$,${program_suffix},; $program_transform_name" - -# sed with no file args requires a program. -test "$program_transform_name" = "" && program_transform_name="s,x,x," -]) - - -dnl ### Version numbers - - -dnl AC_REVISION(REVISION-INFO) -AC_DEFUN(AC_REVISION, -[AC_REQUIRE([AC_INIT_BINSH])dnl -[# From configure.in] translit([$1], $")]) - -dnl Subroutines of AC_PREREQ. - -dnl Change the dots in NUMBER into commas. -dnl AC_PREREQ_SPLIT(NUMBER) -define(AC_PREREQ_SPLIT, -[translit($1, ., [, ])]) - -dnl Default the ternary version number to 0 (e.g., 1, 7 -> 1, 7, 0). -dnl AC_PREREQ_CANON(MAJOR, MINOR [,TERNARY]) -define(AC_PREREQ_CANON, -[$1, $2, ifelse([$3], , 0, [$3])]) - -dnl Complain and exit if version number 1 is less than version number 2. -dnl PRINTABLE2 is the printable version of version number 2. -dnl AC_PREREQ_COMPARE(MAJOR1, MINOR1, TERNARY1, MAJOR2, MINOR2, TERNARY2, -dnl PRINTABLE2) -define(AC_PREREQ_COMPARE, -[ifelse(builtin([eval], -[$3 + $2 * 1000 + $1 * 1000000 < $6 + $5 * 1000 + $4 * 1000000]), 1, -[errprint(dnl -FATAL ERROR: Autoconf version $7 or higher is required for this script -)m4exit(3)])]) - -dnl Complain and exit if the Autoconf version is less than VERSION. -dnl AC_PREREQ(VERSION) -define(AC_PREREQ, -[AC_PREREQ_COMPARE(AC_PREREQ_CANON(AC_PREREQ_SPLIT(AC_ACVERSION)), -AC_PREREQ_CANON(AC_PREREQ_SPLIT([$1])), [$1])]) - - -dnl ### Getting the canonical system type - - -dnl Find install-sh, config.sub, config.guess, and Cygnus configure -dnl in directory DIR. These are auxiliary files used in configuration. -dnl DIR can be either absolute or relative to $srcdir. -dnl AC_CONFIG_AUX_DIR(DIR) -AC_DEFUN(AC_CONFIG_AUX_DIR, -[AC_CONFIG_AUX_DIRS($1 $srcdir/$1)]) - -dnl The default is `$srcdir' or `$srcdir/..' or `$srcdir/../..'. -dnl There's no need to call this macro explicitly; just AC_REQUIRE it. -AC_DEFUN(AC_CONFIG_AUX_DIR_DEFAULT, -[AC_CONFIG_AUX_DIRS($srcdir $srcdir/.. $srcdir/../..)]) - -dnl Internal subroutine. -dnl Search for the configuration auxiliary files in directory list $1. -dnl We look only for install-sh, so users of AC_PROG_INSTALL -dnl do not automatically need to distribute the other auxiliary files. -dnl AC_CONFIG_AUX_DIRS(DIR ...) -AC_DEFUN(AC_CONFIG_AUX_DIRS, -[ac_aux_dir= -for ac_dir in $1; do - if test -f $ac_dir/install-sh; then - ac_aux_dir=$ac_dir - ac_install_sh="$ac_aux_dir/install-sh -c" - break - elif test -f $ac_dir/install.sh; then - ac_aux_dir=$ac_dir - ac_install_sh="$ac_aux_dir/install.sh -c" - break - fi -done -if test -z "$ac_aux_dir"; then - AC_MSG_ERROR([can not find install-sh or install.sh in $1]) -fi -ac_config_guess=$ac_aux_dir/config.guess -ac_config_sub=$ac_aux_dir/config.sub -ac_configure=$ac_aux_dir/configure # This should be Cygnus configure. -AC_PROVIDE([AC_CONFIG_AUX_DIR_DEFAULT])dnl -]) - -dnl Canonicalize the host, target, and build system types. -AC_DEFUN(AC_CANONICAL_SYSTEM, -[AC_REQUIRE([AC_CONFIG_AUX_DIR_DEFAULT])dnl -AC_BEFORE([$0], [AC_ARG_PROGRAM]) -# Do some error checking and defaulting for the host and target type. -# The inputs are: -# configure --host=HOST --target=TARGET --build=BUILD NONOPT -# -# The rules are: -# 1. You are not allowed to specify --host, --target, and nonopt at the -# same time. -# 2. Host defaults to nonopt. -# 3. If nonopt is not specified, then host defaults to the current host, -# as determined by config.guess. -# 4. Target and build default to nonopt. -# 5. If nonopt is not specified, then target and build default to host. - -# The aliases save the names the user supplied, while $host etc. -# will get canonicalized. -case $host---$target---$nonopt in -NONE---*---* | *---NONE---* | *---*---NONE) ;; -*) AC_MSG_ERROR(can only configure for one host and one target at a time) ;; -esac - -AC_CANONICAL_HOST -AC_CANONICAL_TARGET -AC_CANONICAL_BUILD -test "$host_alias" != "$target_alias" && - test "$program_prefix$program_suffix$program_transform_name" = \ - NONENONEs,x,x, && - program_prefix=${target_alias}- -]) - -dnl Subroutines of AC_CANONICAL_SYSTEM. - -AC_DEFUN(AC_CANONICAL_HOST, -[AC_REQUIRE([AC_CONFIG_AUX_DIR_DEFAULT])dnl - -# Make sure we can run config.sub. -if ${CONFIG_SHELL-/bin/sh} $ac_config_sub sun4 >/dev/null 2>&1; then : -else AC_MSG_ERROR(can not run $ac_config_sub) -fi - -AC_MSG_CHECKING(host system type) - -dnl Set host_alias. -host_alias=$host -case "$host_alias" in -NONE) - case $nonopt in - NONE) - if host_alias=`${CONFIG_SHELL-/bin/sh} $ac_config_guess`; then : - else AC_MSG_ERROR(can not guess host type; you must specify one) - fi ;; - *) host_alias=$nonopt ;; - esac ;; -esac - -dnl Set the other host vars. -changequote(<<, >>)dnl -host=`${CONFIG_SHELL-/bin/sh} $ac_config_sub $host_alias` -host_cpu=`echo $host | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\1/'` -host_vendor=`echo $host | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\2/'` -host_os=`echo $host | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\3/'` -changequote([, ])dnl -AC_MSG_RESULT($host) -AC_SUBST(host)dnl -AC_SUBST(host_alias)dnl -AC_SUBST(host_cpu)dnl -AC_SUBST(host_vendor)dnl -AC_SUBST(host_os)dnl -]) - -dnl Internal use only. -AC_DEFUN(AC_CANONICAL_TARGET, -[AC_REQUIRE([AC_CONFIG_AUX_DIR_DEFAULT])dnl -AC_MSG_CHECKING(target system type) - -dnl Set target_alias. -target_alias=$target -case "$target_alias" in -NONE) - case $nonopt in - NONE) target_alias=$host_alias ;; - *) target_alias=$nonopt ;; - esac ;; -esac - -dnl Set the other target vars. -changequote(<<, >>)dnl -target=`${CONFIG_SHELL-/bin/sh} $ac_config_sub $target_alias` -target_cpu=`echo $target | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\1/'` -target_vendor=`echo $target | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\2/'` -target_os=`echo $target | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\3/'` -changequote([, ])dnl -AC_MSG_RESULT($target) -AC_SUBST(target)dnl -AC_SUBST(target_alias)dnl -AC_SUBST(target_cpu)dnl -AC_SUBST(target_vendor)dnl -AC_SUBST(target_os)dnl -]) - -dnl Internal use only. -AC_DEFUN(AC_CANONICAL_BUILD, -[AC_REQUIRE([AC_CONFIG_AUX_DIR_DEFAULT])dnl -AC_MSG_CHECKING(build system type) - -dnl Set build_alias. -build_alias=$build -case "$build_alias" in -NONE) - case $nonopt in - NONE) build_alias=$host_alias ;; - *) build_alias=$nonopt ;; - esac ;; -esac - -dnl Set the other build vars. -changequote(<<, >>)dnl -build=`${CONFIG_SHELL-/bin/sh} $ac_config_sub $build_alias` -build_cpu=`echo $build | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\1/'` -build_vendor=`echo $build | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\2/'` -build_os=`echo $build | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\3/'` -changequote([, ])dnl -AC_MSG_RESULT($build) -AC_SUBST(build)dnl -AC_SUBST(build_alias)dnl -AC_SUBST(build_cpu)dnl -AC_SUBST(build_vendor)dnl -AC_SUBST(build_os)dnl -]) - - -dnl AC_VALIDATE_CACHED_SYSTEM_TUPLE[(cmd)] -dnl if the cache file is inconsistent with the current host, -dnl target and build system types, execute CMD or print a default -dnl error message. -AC_DEFUN(AC_VALIDATE_CACHED_SYSTEM_TUPLE, [ - AC_REQUIRE([AC_CANONICAL_SYSTEM]) - AC_MSG_CHECKING([cached system tuple]) - if { test x"${ac_cv_host_system_type+set}" = x"set" && - test x"$ac_cv_host_system_type" != x"$host"; } || - { test x"${ac_cv_build_system_type+set}" = x"set" && - test x"$ac_cv_build_system_type" != x"$build"; } || - { test x"${ac_cv_target_system_type+set}" = x"set" && - test x"$ac_cv_target_system_type" != x"$target"; }; then - AC_MSG_RESULT([different]) - ifelse($#, 1, [$1], - [AC_MSG_ERROR([remove config.cache and re-run configure])]) - else - AC_MSG_RESULT(ok) - fi - ac_cv_host_system_type="$host" - ac_cv_build_system_type="$build" - ac_cv_target_system_type="$target" -]) - - -dnl ### Caching test results - - -dnl Look for site or system specific initialization scripts. -dnl AC_SITE_LOAD() -define(AC_SITE_LOAD, -[# Prefer explicitly selected file to automatically selected ones. -if test -z "$CONFIG_SITE"; then - if test "x$prefix" != xNONE; then - CONFIG_SITE="$prefix/share/config.site $prefix/etc/config.site" - else - CONFIG_SITE="$ac_default_prefix/share/config.site $ac_default_prefix/etc/config.site" - fi -fi -for ac_site_file in $CONFIG_SITE; do - if test -r "$ac_site_file"; then - echo "loading site script $ac_site_file" - . "$ac_site_file" - fi -done -]) - -dnl AC_CACHE_LOAD() -define(AC_CACHE_LOAD, -[if test -r "$cache_file"; then - echo "loading cache $cache_file" - . $cache_file -else - echo "creating cache $cache_file" - > $cache_file -fi -]) - -dnl AC_CACHE_SAVE() -define(AC_CACHE_SAVE, -[cat > confcache <<\EOF -# This file is a shell script that caches the results of configure -# tests run on this system so they can be shared between configure -# scripts and configure runs. It is not useful on other systems. -# If it contains results you don't want to keep, you may remove or edit it. -# -# By default, configure uses ./config.cache as the cache file, -# creating it if it does not exist already. You can give configure -# the --cache-file=FILE option to use a different cache file; that is -# what configure does when it calls configure scripts in -# subdirectories, so they share the cache. -# Giving --cache-file=/dev/null disables caching, for debugging configure. -# config.status only pays attention to the cache file if you give it the -# --recheck option to rerun configure. -# -EOF -dnl Allow a site initialization script to override cache values. -# The following way of writing the cache mishandles newlines in values, -# but we know of no workaround that is simple, portable, and efficient. -# So, don't put newlines in cache variables' values. -# Ultrix sh set writes to stderr and can't be redirected directly, -# and sets the high bit in the cache file unless we assign to the vars. -changequote(, )dnl -(set) 2>&1 | - case `(ac_space=' '; set | grep ac_space) 2>&1` in - *ac_space=\ *) - # `set' does not quote correctly, so add quotes (double-quote substitution - # turns \\\\ into \\, and sed turns \\ into \). - sed -n \ - -e "s/'/'\\\\''/g" \ - -e "s/^\\([a-zA-Z0-9_]*_cv_[a-zA-Z0-9_]*\\)=\\(.*\\)/\\1=\${\\1='\\2'}/p" - ;; - *) - # `set' quotes correctly as required by POSIX, so do not add quotes. - sed -n -e 's/^\([a-zA-Z0-9_]*_cv_[a-zA-Z0-9_]*\)=\(.*\)/\1=${\1=\2}/p' - ;; - esac >> confcache -changequote([, ])dnl -if cmp -s $cache_file confcache; then - : -else - if test -w $cache_file; then - echo "updating cache $cache_file" - cat confcache > $cache_file - else - echo "not updating unwritable cache $cache_file" - fi -fi -rm -f confcache -]) - -dnl The name of shell var CACHE-ID must contain `_cv_' in order to get saved. -dnl AC_CACHE_VAL(CACHE-ID, COMMANDS-TO-SET-IT) -define(AC_CACHE_VAL, -[dnl We used to use the below line, but it fails if the 1st arg is a -dnl shell variable, so we need the eval. -dnl if test "${$1+set}" = set; then -dnl the '' avoids an AIX 4.1 sh bug ("invalid expansion"). -if eval "test \"`echo '$''{'$1'+set}'`\" = set"; then - echo $ac_n "(cached) $ac_c" 1>&AC_FD_MSG -else - $2 -fi -]) - -dnl AC_CACHE_CHECK(MESSAGE, CACHE-ID, COMMANDS) -define(AC_CACHE_CHECK, -[AC_MSG_CHECKING([$1]) -AC_CACHE_VAL([$2], [$3]) -AC_MSG_RESULT([$]$2)]) - - -dnl ### Defining symbols - - -dnl Set VARIABLE to VALUE, verbatim, or 1. -dnl AC_DEFINE(VARIABLE [, VALUE]) -define(AC_DEFINE, -[cat >> confdefs.h <<\EOF -[#define] $1 ifelse($#, 2, [$2], $#, 3, [$2], 1) -EOF -]) - -dnl Similar, but perform shell substitutions $ ` \ once on VALUE. -define(AC_DEFINE_UNQUOTED, -[cat >> confdefs.h <&AC_FD_MSG -echo "configure:__oline__: checking $1" >&AC_FD_CC]) - -dnl AC_CHECKING(FEATURE-DESCRIPTION) -define(AC_CHECKING, -[echo "checking $1" 1>&AC_FD_MSG -echo "configure:__oline__: checking $1" >&AC_FD_CC]) - -dnl AC_MSG_RESULT(RESULT-DESCRIPTION) -define(AC_MSG_RESULT, -[echo "$ac_t""$1" 1>&AC_FD_MSG]) - -dnl AC_VERBOSE(RESULT-DESCRIPTION) -define(AC_VERBOSE, -[AC_OBSOLETE([$0], [; instead use AC_MSG_RESULT])dnl -echo " $1" 1>&AC_FD_MSG]) - -dnl AC_MSG_WARN(PROBLEM-DESCRIPTION) -define(AC_MSG_WARN, -[echo "configure: warning: $1" 1>&2]) - -dnl AC_MSG_ERROR(ERROR-DESCRIPTION) -define(AC_MSG_ERROR, -[{ echo "configure: error: $1" 1>&2; exit 1; }]) - - -dnl ### Selecting which language to use for testing - - -dnl AC_LANG_C() -AC_DEFUN(AC_LANG_C, -[define([AC_LANG], [C])dnl -ac_ext=c -# CFLAGS is not in ac_cpp because -g, -O, etc. are not valid cpp options. -ac_cpp='$CPP $CPPFLAGS' -ac_compile='${CC-cc} -c $CFLAGS $CPPFLAGS conftest.$ac_ext 1>&AC_FD_CC' -ac_link='${CC-cc} -o conftest${ac_exeext} $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS 1>&AC_FD_CC' -cross_compiling=$ac_cv_prog_cc_cross -]) - -dnl AC_LANG_CPLUSPLUS() -AC_DEFUN(AC_LANG_CPLUSPLUS, -[define([AC_LANG], [CPLUSPLUS])dnl -ac_ext=C -# CXXFLAGS is not in ac_cpp because -g, -O, etc. are not valid cpp options. -ac_cpp='$CXXCPP $CPPFLAGS' -ac_compile='${CXX-g++} -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext 1>&AC_FD_CC' -ac_link='${CXX-g++} -o conftest${ac_exeext} $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS 1>&AC_FD_CC' -cross_compiling=$ac_cv_prog_cxx_cross -]) - -dnl AC_LANG_FORTRAN77() -AC_DEFUN(AC_LANG_FORTRAN77, -[define([AC_LANG], [FORTRAN77])dnl -ac_ext=f -ac_compile='${F77-f77} -c $FFLAGS conftest.$ac_ext 1>&AC_FD_CC' -ac_link='${F77-f77} -o conftest${ac_exeext} $FFLAGS $LDFLAGS conftest.$ac_ext $LIBS 1>&AC_FD_CC' -cross_compiling=$ac_cv_prog_f77_cross -]) - -dnl Push the current language on a stack. -dnl AC_LANG_SAVE() -define(AC_LANG_SAVE, -[pushdef([AC_LANG_STACK], AC_LANG)]) - -dnl Restore the current language from the stack. -dnl AC_LANG_RESTORE() -pushdef([AC_LANG_RESTORE], -[ifelse(AC_LANG_STACK, [C], [AC_LANG_C],dnl -AC_LANG_STACK, [CPLUSPLUS], [AC_LANG_CPLUSPLUS],dnl -AC_LANG_STACK, [FORTRAN77], [AC_LANG_FORTRAN77])[]popdef([AC_LANG_STACK])]) - - -dnl ### Compiler-running mechanics - - -dnl The purpose of this macro is to "configure:123: command line" -dnl written into config.log for every test run. -dnl AC_TRY_EVAL(VARIABLE) -AC_DEFUN(AC_TRY_EVAL, -[{ (eval echo configure:__oline__: \"[$]$1\") 1>&AC_FD_CC; dnl -(eval [$]$1) 2>&AC_FD_CC; }]) - -dnl AC_TRY_COMMAND(COMMAND) -AC_DEFUN(AC_TRY_COMMAND, -[{ ac_try='$1'; AC_TRY_EVAL(ac_try); }]) - - -dnl ### Dependencies between macros - - -dnl AC_BEFORE(THIS-MACRO-NAME, CALLED-MACRO-NAME) -define(AC_BEFORE, -[ifdef([AC_PROVIDE_$2], [errprint(__file__:__line__: [$2 was called before $1 -])])]) - -dnl AC_REQUIRE(MACRO-NAME) -define(AC_REQUIRE, -[ifdef([AC_PROVIDE_$1], , -[AC_DIVERT_PUSH(builtin(eval, AC_DIVERSION_CURRENT - 1))dnl -indir([$1]) -AC_DIVERT_POP()dnl -])]) - -dnl AC_PROVIDE(MACRO-NAME) -define(AC_PROVIDE, -[define([AC_PROVIDE_$1], )]) - -dnl AC_OBSOLETE(THIS-MACRO-NAME [, SUGGESTION]) -define(AC_OBSOLETE, -[errprint(__file__:__line__: warning: [$1] is obsolete[$2] -)]) - - -dnl ### Checking for programs - - -dnl AC_CHECK_PROG(VARIABLE, PROG-TO-CHECK-FOR, VALUE-IF-FOUND -dnl [, [VALUE-IF-NOT-FOUND] [, [PATH] [, [REJECT]]]]) -AC_DEFUN(AC_CHECK_PROG, -[# Extract the first word of "$2", so it can be a program name with args. -set dummy $2; ac_word=[$]2 -AC_MSG_CHECKING([for $ac_word]) -AC_CACHE_VAL(ac_cv_prog_$1, -[if test -n "[$]$1"; then - ac_cv_prog_$1="[$]$1" # Let the user override the test. -else - IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":" -ifelse([$6], , , [ ac_prog_rejected=no -])dnl -dnl $ac_dummy forces splitting on constant user-supplied paths. -dnl POSIX.2 word splitting is done only on the output of word expansions, -dnl not every word. This closes a longstanding sh security hole. - ac_dummy="ifelse([$5], , $PATH, [$5])" - for ac_dir in $ac_dummy; do - test -z "$ac_dir" && ac_dir=. - if test -f $ac_dir/$ac_word; then -ifelse([$6], , , dnl -[ if test "[$ac_dir/$ac_word]" = "$6"; then - ac_prog_rejected=yes - continue - fi -])dnl - ac_cv_prog_$1="$3" - break - fi - done - IFS="$ac_save_ifs" -ifelse([$6], , , [if test $ac_prog_rejected = yes; then - # We found a bogon in the path, so make sure we never use it. - set dummy [$]ac_cv_prog_$1 - shift - if test [$]# -gt 0; then - # We chose a different compiler from the bogus one. - # However, it has the same basename, so the bogon will be chosen - # first if we set $1 to just the basename; use the full file name. - shift - set dummy "$ac_dir/$ac_word" "[$]@" - shift - ac_cv_prog_$1="[$]@" -ifelse([$2], [$4], dnl -[ else - # Default is a loser. - AC_MSG_ERROR([$1=$6 unacceptable, but no other $4 found in dnl -ifelse([$5], , [\$]PATH, [$5])]) -])dnl - fi -fi -])dnl -dnl If no 4th arg is given, leave the cache variable unset, -dnl so AC_CHECK_PROGS will keep looking. -ifelse([$4], , , [ test -z "[$]ac_cv_prog_$1" && ac_cv_prog_$1="$4" -])dnl -fi])dnl -$1="$ac_cv_prog_$1" -if test -n "[$]$1"; then - AC_MSG_RESULT([$]$1) -else - AC_MSG_RESULT(no) -fi -AC_SUBST($1)dnl -]) - -dnl AC_PATH_PROG(VARIABLE, PROG-TO-CHECK-FOR [, VALUE-IF-NOT-FOUND [, PATH]]) -AC_DEFUN(AC_PATH_PROG, -[# Extract the first word of "$2", so it can be a program name with args. -set dummy $2; ac_word=[$]2 -AC_MSG_CHECKING([for $ac_word]) -AC_CACHE_VAL(ac_cv_path_$1, -[case "[$]$1" in - /*) - ac_cv_path_$1="[$]$1" # Let the user override the test with a path. - ;; - ?:/*) - ac_cv_path_$1="[$]$1" # Let the user override the test with a dos path. - ;; - *) - IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":" -dnl $ac_dummy forces splitting on constant user-supplied paths. -dnl POSIX.2 word splitting is done only on the output of word expansions, -dnl not every word. This closes a longstanding sh security hole. - ac_dummy="ifelse([$4], , $PATH, [$4])" - for ac_dir in $ac_dummy; do - test -z "$ac_dir" && ac_dir=. - if test -f $ac_dir/$ac_word; then - ac_cv_path_$1="$ac_dir/$ac_word" - break - fi - done - IFS="$ac_save_ifs" -dnl If no 3rd arg is given, leave the cache variable unset, -dnl so AC_PATH_PROGS will keep looking. -ifelse([$3], , , [ test -z "[$]ac_cv_path_$1" && ac_cv_path_$1="$3" -])dnl - ;; -esac])dnl -$1="$ac_cv_path_$1" -if test -n "[$]$1"; then - AC_MSG_RESULT([$]$1) -else - AC_MSG_RESULT(no) -fi -AC_SUBST($1)dnl -]) - -dnl AC_CHECK_PROGS(VARIABLE, PROGS-TO-CHECK-FOR [, VALUE-IF-NOT-FOUND -dnl [, PATH]]) -AC_DEFUN(AC_CHECK_PROGS, -[for ac_prog in $2 -do -AC_CHECK_PROG($1, [$]ac_prog, [$]ac_prog, , $4) -test -n "[$]$1" && break -done -ifelse([$3], , , [test -n "[$]$1" || $1="$3" -])]) - -dnl AC_PATH_PROGS(VARIABLE, PROGS-TO-CHECK-FOR [, VALUE-IF-NOT-FOUND -dnl [, PATH]]) -AC_DEFUN(AC_PATH_PROGS, -[for ac_prog in $2 -do -AC_PATH_PROG($1, [$]ac_prog, , $4) -test -n "[$]$1" && break -done -ifelse([$3], , , [test -n "[$]$1" || $1="$3" -])]) - -dnl Internal subroutine. -AC_DEFUN(AC_CHECK_TOOL_PREFIX, -[AC_REQUIRE([AC_CANONICAL_HOST])AC_REQUIRE([AC_CANONICAL_BUILD])dnl -if test $host != $build; then - ac_tool_prefix=${host_alias}- -else - ac_tool_prefix= -fi -]) - -dnl AC_CHECK_TOOL(VARIABLE, PROG-TO-CHECK-FOR[, VALUE-IF-NOT-FOUND [, PATH]]) -AC_DEFUN(AC_CHECK_TOOL, -[AC_REQUIRE([AC_CHECK_TOOL_PREFIX])dnl -AC_CHECK_PROG($1, ${ac_tool_prefix}$2, ${ac_tool_prefix}$2, - ifelse([$3], , [$2], ), $4) -ifelse([$3], , , [ -if test -z "$ac_cv_prog_$1"; then -if test -n "$ac_tool_prefix"; then - AC_CHECK_PROG($1, $2, $2, $3) -else - $1="$3" -fi -fi]) -]) - -dnl Guess the value for the `prefix' variable by looking for -dnl the argument program along PATH and taking its parent. -dnl Example: if the argument is `gcc' and we find /usr/local/gnu/bin/gcc, -dnl set `prefix' to /usr/local/gnu. -dnl This comes too late to find a site file based on the prefix, -dnl and it might use a cached value for the path. -dnl No big loss, I think, since most configures don't use this macro anyway. -dnl AC_PREFIX_PROGRAM(PROGRAM) -AC_DEFUN(AC_PREFIX_PROGRAM, -[if test "x$prefix" = xNONE; then -changequote(<<, >>)dnl -define(<>, translit($1, [a-z], [A-Z]))dnl -changequote([, ])dnl -dnl We reimplement AC_MSG_CHECKING (mostly) to avoid the ... in the middle. -echo $ac_n "checking for prefix by $ac_c" 1>&AC_FD_MSG -AC_PATH_PROG(AC_VAR_NAME, $1) -changequote(<<, >>)dnl - if test -n "$ac_cv_path_<<>>AC_VAR_NAME"; then - prefix=`echo $ac_cv_path_<<>>AC_VAR_NAME|sed 's%/[^/][^/]*//*[^/][^/]*$%%'` -changequote([, ])dnl - fi -fi -undefine([AC_VAR_NAME])dnl -]) - -dnl Try to compile, link and execute TEST-PROGRAM. Set WORKING-VAR to -dnl `yes' if the current compiler works, otherwise set it ti `no'. Set -dnl CROSS-VAR to `yes' if the compiler and linker produce non-native -dnl executables, otherwise set it to `no'. Before calling -dnl `AC_TRY_COMPILER()', call `AC_LANG_*' to set-up for the right -dnl language. -dnl -dnl AC_TRY_COMPILER(TEST-PROGRAM, WORKING-VAR, CROSS-VAR) -AC_DEFUN(AC_TRY_COMPILER, -[cat > conftest.$ac_ext << EOF -ifelse(AC_LANG, [FORTRAN77], , -[ -[#]line __oline__ "configure" -#include "confdefs.h" -]) -[$1] -EOF -if AC_TRY_EVAL(ac_link) && test -s conftest${ac_exeext}; then - [$2]=yes - # If we can't run a trivial program, we are probably using a cross compiler. - if (./conftest; exit) 2>/dev/null; then - [$3]=no - else - [$3]=yes - fi -else - echo "configure: failed program was:" >&AC_FD_CC - cat conftest.$ac_ext >&AC_FD_CC - [$2]=no -fi -rm -fr conftest*]) - - -dnl ### Checking for libraries - - -dnl AC_TRY_LINK_FUNC(func, action-if-found, action-if-not-found) -dnl Try to link a program that calls FUNC, handling GCC builtins. If -dnl the link succeeds, execute ACTION-IF-FOUND; otherwise, execute -dnl ACTION-IF-NOT-FOUND. - -AC_DEFUN(AC_TRY_LINK_FUNC, -AC_TRY_LINK(dnl -ifelse([$1], [main], , dnl Avoid conflicting decl of main. -[/* Override any gcc2 internal prototype to avoid an error. */ -]ifelse(AC_LANG, CPLUSPLUS, [#ifdef __cplusplus -extern "C" -#endif -])dnl -[/* We use char because int might match the return type of a gcc2 - builtin and then its argument prototype would still apply. */ -char $1(); -]), -[$1()], -[$2], -[$3])) - - -dnl AC_SEARCH_LIBS(FUNCTION, SEARCH-LIBS [, ACTION-IF-FOUND -dnl [, ACTION-IF-NOT-FOUND [, OTHER-LIBRARIES]]]) -dnl Search for a library defining FUNC, if it's not already available. - -AC_DEFUN(AC_SEARCH_LIBS, -[AC_PREREQ([2.13]) -AC_CACHE_CHECK([for library containing $1], [ac_cv_search_$1], -[ac_func_search_save_LIBS="$LIBS" -ac_cv_search_$1="no" -AC_TRY_LINK_FUNC([$1], [ac_cv_search_$1="none required"]) -test "$ac_cv_search_$1" = "no" && for i in $2; do -LIBS="-l$i $5 $ac_func_search_save_LIBS" -AC_TRY_LINK_FUNC([$1], -[ac_cv_search_$1="-l$i" -break]) -done -LIBS="$ac_func_search_save_LIBS"]) -if test "$ac_cv_search_$1" != "no"; then - test "$ac_cv_search_$1" = "none required" || LIBS="$ac_cv_search_$1 $LIBS" - $3 -else : - $4 -fi]) - - - -dnl AC_CHECK_LIB(LIBRARY, FUNCTION [, ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND -dnl [, OTHER-LIBRARIES]]]) -AC_DEFUN(AC_CHECK_LIB, -[AC_MSG_CHECKING([for $2 in -l$1]) -dnl Use a cache variable name containing both the library and function name, -dnl because the test really is for library $1 defining function $2, not -dnl just for library $1. Separate tests with the same $1 and different $2s -dnl may have different results. -ac_lib_var=`echo $1['_']$2 | sed 'y%./+-%__p_%'` -AC_CACHE_VAL(ac_cv_lib_$ac_lib_var, -[ac_save_LIBS="$LIBS" -LIBS="-l$1 $5 $LIBS" -AC_TRY_LINK(dnl -ifelse(AC_LANG, [FORTRAN77], , -ifelse([$2], [main], , dnl Avoid conflicting decl of main. -[/* Override any gcc2 internal prototype to avoid an error. */ -]ifelse(AC_LANG, CPLUSPLUS, [#ifdef __cplusplus -extern "C" -#endif -])dnl -[/* We use char because int might match the return type of a gcc2 - builtin and then its argument prototype would still apply. */ -char $2(); -])), - [$2()], - eval "ac_cv_lib_$ac_lib_var=yes", - eval "ac_cv_lib_$ac_lib_var=no") -LIBS="$ac_save_LIBS" -])dnl -if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then - AC_MSG_RESULT(yes) - ifelse([$3], , -[changequote(, )dnl - ac_tr_lib=HAVE_LIB`echo $1 | sed -e 's/[^a-zA-Z0-9_]/_/g' \ - -e 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'` -changequote([, ])dnl - AC_DEFINE_UNQUOTED($ac_tr_lib) - LIBS="-l$1 $LIBS" -], [$3]) -else - AC_MSG_RESULT(no) -ifelse([$4], , , [$4 -])dnl -fi -]) - -dnl AC_HAVE_LIBRARY(LIBRARY, [, ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND -dnl [, OTHER-LIBRARIES]]]) -AC_DEFUN(AC_HAVE_LIBRARY, -[AC_OBSOLETE([$0], [; instead use AC_CHECK_LIB])dnl -changequote(<<, >>)dnl -define(<>, dnl -patsubst(patsubst($1, <>, <<\1>>), <<-l>>, <<>>))dnl -define(<>, ac_cv_lib_<<>>AC_LIB_NAME)dnl -changequote([, ])dnl -AC_MSG_CHECKING([for -l[]AC_LIB_NAME]) -AC_CACHE_VAL(AC_CV_NAME, -[ac_save_LIBS="$LIBS" -LIBS="-l[]AC_LIB_NAME[] $4 $LIBS" -AC_TRY_LINK( , [main()], AC_CV_NAME=yes, AC_CV_NAME=no) -LIBS="$ac_save_LIBS" -])dnl -AC_MSG_RESULT($AC_CV_NAME) -if test "$AC_CV_NAME" = yes; then - ifelse([$2], , -[AC_DEFINE([HAVE_LIB]translit(AC_LIB_NAME, [a-z], [A-Z])) - LIBS="-l[]AC_LIB_NAME[] $LIBS" -], [$2]) -ifelse([$3], , , [else - $3 -])dnl -fi -undefine([AC_LIB_NAME])dnl -undefine([AC_CV_NAME])dnl -]) - - -dnl ### Examining declarations - - -dnl AC_TRY_CPP(INCLUDES, [ACTION-IF-TRUE [, ACTION-IF-FALSE]]) -AC_DEFUN(AC_TRY_CPP, -[AC_REQUIRE_CPP()dnl -cat > conftest.$ac_ext <&AC_FD_CC - echo "configure: failed program was:" >&AC_FD_CC - cat conftest.$ac_ext >&AC_FD_CC -ifelse([$3], , , [ rm -rf conftest* - $3 -])dnl -fi -rm -f conftest*]) - -dnl AC_EGREP_HEADER(PATTERN, HEADER-FILE, ACTION-IF-FOUND [, -dnl ACTION-IF-NOT-FOUND]) -AC_DEFUN(AC_EGREP_HEADER, -[AC_EGREP_CPP([$1], [#include <$2>], [$3], [$4])]) - -dnl Because this macro is used by AC_PROG_GCC_TRADITIONAL, which must -dnl come early, it is not included in AC_BEFORE checks. -dnl AC_EGREP_CPP(PATTERN, PROGRAM, [ACTION-IF-FOUND [, -dnl ACTION-IF-NOT-FOUND]]) -AC_DEFUN(AC_EGREP_CPP, -[AC_REQUIRE_CPP()dnl -cat > conftest.$ac_ext <&AC_FD_CC | -dnl Prevent m4 from eating character classes: -changequote(, )dnl - grep -E "$1" >/dev/null 2>&1; then -changequote([, ])dnl - ifelse([$3], , :, [rm -rf conftest* - $3]) -ifelse([$4], , , [else - rm -rf conftest* - $4 -])dnl -fi -rm -f conftest* -]) - - -dnl ### Examining syntax - - -dnl AC_TRY_COMPILE(INCLUDES, FUNCTION-BODY, -dnl [ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]) -AC_DEFUN(AC_TRY_COMPILE, -[cat > conftest.$ac_ext <&AC_FD_CC - cat conftest.$ac_ext >&AC_FD_CC -ifelse([$4], , , [ rm -rf conftest* - $4 -])dnl -fi -rm -f conftest*]) - - -dnl ### Examining libraries - - -dnl AC_COMPILE_CHECK(ECHO-TEXT, INCLUDES, FUNCTION-BODY, -dnl ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]) -AC_DEFUN(AC_COMPILE_CHECK, -[AC_OBSOLETE([$0], [; instead use AC_TRY_COMPILE or AC_TRY_LINK, and AC_MSG_CHECKING and AC_MSG_RESULT])dnl -ifelse([$1], , , [AC_CHECKING([for $1]) -])dnl -AC_TRY_LINK([$2], [$3], [$4], [$5]) -]) - -dnl AC_TRY_LINK(INCLUDES, FUNCTION-BODY, -dnl [ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]) -AC_DEFUN(AC_TRY_LINK, -[cat > conftest.$ac_ext <&AC_FD_CC - cat conftest.$ac_ext >&AC_FD_CC -ifelse([$4], , , [ rm -rf conftest* - $4 -])dnl -fi -rm -f conftest*]) - - -dnl ### Checking for run-time features - - -dnl AC_TRY_RUN(PROGRAM, [ACTION-IF-TRUE [, ACTION-IF-FALSE -dnl [, ACTION-IF-CROSS-COMPILING]]]) -AC_DEFUN(AC_TRY_RUN, -[if test "$cross_compiling" = yes; then - ifelse([$4], , - [errprint(__file__:__line__: warning: [AC_TRY_RUN] called without default to allow cross compiling -)dnl - AC_MSG_ERROR(can not run test program while cross compiling)], - [$4]) -else - AC_TRY_RUN_NATIVE([$1], [$2], [$3]) -fi -]) - -dnl Like AC_TRY_RUN but assumes a native-environment (non-cross) compiler. -dnl AC_TRY_RUN_NATIVE(PROGRAM, [ACTION-IF-TRUE [, ACTION-IF-FALSE]]) -AC_DEFUN(AC_TRY_RUN_NATIVE, -[cat > conftest.$ac_ext </dev/null -then -dnl Don't remove the temporary files here, so they can be examined. - ifelse([$2], , :, [$2]) -else - echo "configure: failed program was:" >&AC_FD_CC - cat conftest.$ac_ext >&AC_FD_CC -ifelse([$3], , , [ rm -fr conftest* - $3 -])dnl -fi -rm -fr conftest*]) - - -dnl ### Checking for header files - - -dnl AC_CHECK_HEADER(HEADER-FILE, [ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]) -AC_DEFUN(AC_CHECK_HEADER, -[dnl Do the transliteration at runtime so arg 1 can be a shell variable. -ac_safe=`echo "$1" | sed 'y%./+-%__p_%'` -AC_MSG_CHECKING([for $1]) -AC_CACHE_VAL(ac_cv_header_$ac_safe, -[AC_TRY_CPP([#include <$1>], eval "ac_cv_header_$ac_safe=yes", - eval "ac_cv_header_$ac_safe=no")])dnl -if eval "test \"`echo '$ac_cv_header_'$ac_safe`\" = yes"; then - AC_MSG_RESULT(yes) - ifelse([$2], , :, [$2]) -else - AC_MSG_RESULT(no) -ifelse([$3], , , [$3 -])dnl -fi -]) - -dnl AC_CHECK_HEADERS(HEADER-FILE... [, ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]) -AC_DEFUN(AC_CHECK_HEADERS, -[for ac_hdr in $1 -do -AC_CHECK_HEADER($ac_hdr, -[changequote(, )dnl - ac_tr_hdr=HAVE_`echo $ac_hdr | sed 'y%abcdefghijklmnopqrstuvwxyz./-%ABCDEFGHIJKLMNOPQRSTUVWXYZ___%'` -changequote([, ])dnl - AC_DEFINE_UNQUOTED($ac_tr_hdr) $2], $3)dnl -done -]) - - -dnl ### Checking for the existence of files - -dnl AC_CHECK_FILE(FILE, [ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]) -AC_DEFUN(AC_CHECK_FILE, -[AC_REQUIRE([AC_PROG_CC]) -dnl Do the transliteration at runtime so arg 1 can be a shell variable. -ac_safe=`echo "$1" | sed 'y%./+-%__p_%'` -AC_MSG_CHECKING([for $1]) -AC_CACHE_VAL(ac_cv_file_$ac_safe, -[if test "$cross_compiling" = yes; then - errprint(__file__:__line__: warning: Cannot check for file existence when cross compiling -)dnl - AC_MSG_ERROR(Cannot check for file existence when cross compiling) -else - if test -r $1; then - eval "ac_cv_file_$ac_safe=yes" - else - eval "ac_cv_file_$ac_safe=no" - fi -fi])dnl -if eval "test \"`echo '$ac_cv_file_'$ac_safe`\" = yes"; then - AC_MSG_RESULT(yes) - ifelse([$2], , :, [$2]) -else - AC_MSG_RESULT(no) -ifelse([$3], , , [$3]) -fi -]) - -dnl AC_CHECK_FILES(FILE... [, ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]) -AC_DEFUN(AC_CHECK_FILES, -[for ac_file in $1 -do -AC_CHECK_FILE($ac_file, -[changequote(, )dnl - ac_tr_file=HAVE_`echo $ac_file | sed 'y%abcdefghijklmnopqrstuvwxyz./-%ABCDEFGHIJKLMNOPQRSTUVWXYZ___%'` -changequote([, ])dnl - AC_DEFINE_UNQUOTED($ac_tr_file) $2], $3)dnl -done -]) - - -dnl ### Checking for library functions - - -dnl AC_CHECK_FUNC(FUNCTION, [ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]) -AC_DEFUN(AC_CHECK_FUNC, -[AC_MSG_CHECKING([for $1]) -AC_CACHE_VAL(ac_cv_func_$1, -[AC_TRY_LINK( -dnl Don't include because on OSF/1 3.0 it includes -dnl which includes which contains a prototype for -dnl select. Similarly for bzero. -[/* System header to define __stub macros and hopefully few prototypes, - which can conflict with char $1(); below. */ -#include -/* Override any gcc2 internal prototype to avoid an error. */ -]ifelse(AC_LANG, CPLUSPLUS, [#ifdef __cplusplus -extern "C" -#endif -])dnl -[/* We use char because int might match the return type of a gcc2 - builtin and then its argument prototype would still apply. */ -char $1(); -], [ -/* The GNU C library defines this for functions which it implements - to always fail with ENOSYS. Some functions are actually named - something starting with __ and the normal name is an alias. */ -#if defined (__stub_$1) || defined (__stub___$1) -choke me -#else -$1(); -#endif -], eval "ac_cv_func_$1=yes", eval "ac_cv_func_$1=no")]) -if eval "test \"`echo '$ac_cv_func_'$1`\" = yes"; then - AC_MSG_RESULT(yes) - ifelse([$2], , :, [$2]) -else - AC_MSG_RESULT(no) -ifelse([$3], , , [$3 -])dnl -fi -]) - -dnl AC_CHECK_FUNCS(FUNCTION... [, ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]) -AC_DEFUN(AC_CHECK_FUNCS, -[for ac_func in $1 -do -AC_CHECK_FUNC($ac_func, -[changequote(, )dnl - ac_tr_func=HAVE_`echo $ac_func | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'` -changequote([, ])dnl - AC_DEFINE_UNQUOTED($ac_tr_func) $2], $3)dnl -done -]) - -dnl AC_REPLACE_FUNCS(FUNCTION...) -AC_DEFUN(AC_REPLACE_FUNCS, -[AC_CHECK_FUNCS([$1], , [LIBOBJS="$LIBOBJS ${ac_func}.${ac_objext}"]) -AC_SUBST(LIBOBJS)dnl -]) - - -dnl ### Checking compiler characteristics - - -dnl AC_CHECK_SIZEOF(TYPE [, CROSS-SIZE]) -AC_DEFUN(AC_CHECK_SIZEOF, -[changequote(<<, >>)dnl -dnl The name to #define. -define(<>, translit(sizeof_$1, [a-z *], [A-Z_P]))dnl -dnl The cache variable name. -define(<>, translit(ac_cv_sizeof_$1, [ *], [_p]))dnl -changequote([, ])dnl -AC_MSG_CHECKING(size of $1) -AC_CACHE_VAL(AC_CV_NAME, -[AC_TRY_RUN([#include -main() -{ - FILE *f=fopen("conftestval", "w"); - if (!f) exit(1); - fprintf(f, "%d\n", sizeof($1)); - exit(0); -}], AC_CV_NAME=`cat conftestval`, AC_CV_NAME=0, ifelse([$2], , , AC_CV_NAME=$2))])dnl -AC_MSG_RESULT($AC_CV_NAME) -AC_DEFINE_UNQUOTED(AC_TYPE_NAME, $AC_CV_NAME) -undefine([AC_TYPE_NAME])dnl -undefine([AC_CV_NAME])dnl -]) - - -dnl ### Checking for typedefs - - -dnl AC_CHECK_TYPE(TYPE, DEFAULT) -AC_DEFUN(AC_CHECK_TYPE, -[AC_REQUIRE([AC_HEADER_STDC])dnl -AC_MSG_CHECKING(for $1) -AC_CACHE_VAL(ac_cv_type_$1, -[AC_EGREP_CPP(dnl -changequote(<<,>>)dnl -<<(^|[^a-zA-Z_0-9])$1[^a-zA-Z_0-9]>>dnl -changequote([,]), [#include -#if STDC_HEADERS -#include -#include -#endif], ac_cv_type_$1=yes, ac_cv_type_$1=no)])dnl -AC_MSG_RESULT($ac_cv_type_$1) -if test $ac_cv_type_$1 = no; then - AC_DEFINE($1, $2) -fi -]) - - -dnl ### Creating output files - - -dnl AC_CONFIG_HEADER(HEADER-TO-CREATE ...) -AC_DEFUN(AC_CONFIG_HEADER, -[define(AC_LIST_HEADER, $1)]) - -dnl Link each of the existing files SOURCE... to the corresponding -dnl link name in DEST... -dnl AC_LINK_FILES(SOURCE..., DEST...) -AC_DEFUN(AC_LINK_FILES, -[dnl -define([AC_LIST_FILES], ifdef([AC_LIST_FILES], [AC_LIST_FILES ],)[$1])dnl -define([AC_LIST_LINKS], ifdef([AC_LIST_LINKS], [AC_LIST_LINKS ],)[$2])]) - -dnl Add additional commands for AC_OUTPUT to put into config.status. -dnl Use diversions instead of macros so we can be robust in the -dnl presence of commas in $1 and/or $2. -dnl AC_OUTPUT_COMMANDS(EXTRA-CMDS, INIT-CMDS) -AC_DEFUN(AC_OUTPUT_COMMANDS, -[AC_DIVERT_PUSH(AC_DIVERSION_CMDS)dnl -[$1] -AC_DIVERT_POP()dnl -AC_DIVERT_PUSH(AC_DIVERSION_ICMDS)dnl -[$2] -AC_DIVERT_POP()]) - -dnl AC_CONFIG_SUBDIRS(DIR ...) -AC_DEFUN(AC_CONFIG_SUBDIRS, -[AC_REQUIRE([AC_CONFIG_AUX_DIR_DEFAULT])dnl -define([AC_LIST_SUBDIRS], ifdef([AC_LIST_SUBDIRS], [AC_LIST_SUBDIRS ],)[$1])dnl -subdirs="AC_LIST_SUBDIRS" -AC_SUBST(subdirs)dnl -]) - -dnl The big finish. -dnl Produce config.status, config.h, and links; and configure subdirs. -dnl AC_OUTPUT([FILE...] [, EXTRA-CMDS] [, INIT-CMDS]) -define(AC_OUTPUT, -[trap '' 1 2 15 -AC_CACHE_SAVE -trap 'rm -fr conftest* confdefs* core core.* *.core $ac_clean_files; exit 1' 1 2 15 - -test "x$prefix" = xNONE && prefix=$ac_default_prefix -# Let make expand exec_prefix. -test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' - -# Any assignment to VPATH causes Sun make to only execute -# the first set of double-colon rules, so remove it if not needed. -# If there is a colon in the path, we need to keep it. -if test "x$srcdir" = x.; then -changequote(, )dnl - ac_vpsub='/^[ ]*VPATH[ ]*=[^:]*$/d' -changequote([, ])dnl -fi - -trap 'rm -f $CONFIG_STATUS conftest*; exit 1' 1 2 15 - -ifdef([AC_LIST_HEADER], [DEFS=-DHAVE_CONFIG_H], [AC_OUTPUT_MAKE_DEFS()]) - -# Without the "./", some shells look in PATH for config.status. -: ${CONFIG_STATUS=./config.status} - -echo creating $CONFIG_STATUS -rm -f $CONFIG_STATUS -cat > $CONFIG_STATUS </dev/null | sed 1q`: -# -[#] [$]0 [$]ac_configure_args -# -# Compiler output produced by configure, useful for debugging -# configure, is in ./config.log if it exists. - -changequote(, )dnl -ac_cs_usage="Usage: $CONFIG_STATUS [--recheck] [--version] [--help]" -changequote([, ])dnl -for ac_option -do - case "[\$]ac_option" in - -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) - echo "running [\$]{CONFIG_SHELL-/bin/sh} [$]0 [$]ac_configure_args --no-create --no-recursion" - exec [\$]{CONFIG_SHELL-/bin/sh} [$]0 [$]ac_configure_args --no-create --no-recursion ;; - -version | --version | --versio | --versi | --vers | --ver | --ve | --v) - echo "$CONFIG_STATUS generated by autoconf version AC_ACVERSION" - exit 0 ;; - -help | --help | --hel | --he | --h) - echo "[\$]ac_cs_usage"; exit 0 ;; - *) echo "[\$]ac_cs_usage"; exit 1 ;; - esac -done - -ac_given_srcdir=$srcdir -ifdef([AC_PROVIDE_AC_PROG_INSTALL], [ac_given_INSTALL="$INSTALL" -])dnl - -changequote(<<, >>)dnl -ifdef(<>, -<>, -<>) -changequote([, ])dnl -EOF -cat >> $CONFIG_STATUS <> $CONFIG_STATUS <> $CONFIG_STATUS <<\EOF -undivert(AC_DIVERSION_CMDS)dnl -$2 -exit 0 -EOF -chmod +x $CONFIG_STATUS -rm -fr confdefs* $ac_clean_files -test "$no_create" = yes || ${CONFIG_SHELL-/bin/sh} $CONFIG_STATUS || exit 1 -dnl config.status should not do recursion. -ifdef([AC_LIST_SUBDIRS], [AC_OUTPUT_SUBDIRS(AC_LIST_SUBDIRS)])dnl -])dnl - -dnl Set the DEFS variable to the -D options determined earlier. -dnl This is a subroutine of AC_OUTPUT. -dnl It is called inside configure, outside of config.status. -dnl AC_OUTPUT_MAKE_DEFS() -define(AC_OUTPUT_MAKE_DEFS, -[# Transform confdefs.h into DEFS. -dnl Using a here document instead of a string reduces the quoting nightmare. -# Protect against shell expansion while executing Makefile rules. -# Protect against Makefile macro expansion. -cat > conftest.defs <<\EOF -changequote(<<, >>)dnl -s%<<#define>> \([A-Za-z_][A-Za-z0-9_]*\) *\(.*\)%-D\1=\2%g -s%[ `~<<#>>$^&*(){}\\|;'"<>?]%\\&%g -s%\[%\\&%g -s%\]%\\&%g -s%\$%$$%g -changequote([, ])dnl -EOF -DEFS=`sed -f conftest.defs confdefs.h | tr '\012' ' '` -rm -f conftest.defs -]) - -dnl Do the variable substitutions to create the Makefiles or whatever. -dnl This is a subroutine of AC_OUTPUT. It is called inside an unquoted -dnl here document whose contents are going into config.status, but -dnl upon returning, the here document is being quoted. -dnl AC_OUTPUT_FILES(FILE...) -define(AC_OUTPUT_FILES, -[# Protect against being on the right side of a sed subst in config.status. -changequote(, )dnl -sed 's/%@/@@/; s/@%/@@/; s/%g\$/@g/; /@g\$/s/[\\\\&%]/\\\\&/g; - s/@@/%@/; s/@@/@%/; s/@g\$/%g/' > conftest.subs <<\\CEOF -changequote([, ])dnl -dnl These here document variables are unquoted when configure runs -dnl but quoted when config.status runs, so variables are expanded once. -$ac_vpsub -dnl Shell code in configure.in might set extrasub. -$extrasub -dnl Insert the sed substitutions of variables. -undivert(AC_DIVERSION_SED) -CEOF -EOF - -cat >> $CONFIG_STATUS <<\EOF - -# Split the substitutions into bite-sized pieces for seds with -# small command number limits, like on Digital OSF/1 and HP-UX. -ac_max_sed_cmds=90 # Maximum number of lines to put in a sed script. -ac_file=1 # Number of current file. -ac_beg=1 # First line for current file. -ac_end=$ac_max_sed_cmds # Line after last line for current file. -ac_more_lines=: -ac_sed_cmds="" -while $ac_more_lines; do - if test $ac_beg -gt 1; then - sed "1,${ac_beg}d; ${ac_end}q" conftest.subs > conftest.s$ac_file - else - sed "${ac_end}q" conftest.subs > conftest.s$ac_file - fi - if test ! -s conftest.s$ac_file; then - ac_more_lines=false - rm -f conftest.s$ac_file - else - if test -z "$ac_sed_cmds"; then - ac_sed_cmds="sed -f conftest.s$ac_file" - else - ac_sed_cmds="$ac_sed_cmds | sed -f conftest.s$ac_file" - fi - ac_file=`expr $ac_file + 1` - ac_beg=$ac_end - ac_end=`expr $ac_end + $ac_max_sed_cmds` - fi -done -if test -z "$ac_sed_cmds"; then - ac_sed_cmds=cat -fi -EOF - -cat >> $CONFIG_STATUS <> $CONFIG_STATUS <<\EOF -for ac_file in .. $CONFIG_FILES; do if test "x$ac_file" != x..; then -changequote(, )dnl - # Support "outfile[:infile[:infile...]]", defaulting infile="outfile.in". - case "$ac_file" in - *:*) ac_file_in=`echo "$ac_file"|sed 's%[^:]*:%%'` - ac_file=`echo "$ac_file"|sed 's%:.*%%'` ;; - *) ac_file_in="${ac_file}.in" ;; - esac - - # Adjust a relative srcdir, top_srcdir, and INSTALL for subdirectories. - - # Remove last slash and all that follows it. Not all systems have dirname. - ac_dir=`echo $ac_file|sed 's%/[^/][^/]*$%%'` -changequote([, ])dnl - if test "$ac_dir" != "$ac_file" && test "$ac_dir" != .; then - # The file is in a subdirectory. - test ! -d "$ac_dir" && mkdir "$ac_dir" - ac_dir_suffix="/`echo $ac_dir|sed 's%^\./%%'`" - # A "../" for each directory in $ac_dir_suffix. -changequote(, )dnl - ac_dots=`echo $ac_dir_suffix|sed 's%/[^/]*%../%g'` -changequote([, ])dnl - else - ac_dir_suffix= ac_dots= - fi - - case "$ac_given_srcdir" in - .) srcdir=. - if test -z "$ac_dots"; then top_srcdir=. - else top_srcdir=`echo $ac_dots|sed 's%/$%%'`; fi ;; - /*) srcdir="$ac_given_srcdir$ac_dir_suffix"; top_srcdir="$ac_given_srcdir" ;; - *) # Relative path. - srcdir="$ac_dots$ac_given_srcdir$ac_dir_suffix" - top_srcdir="$ac_dots$ac_given_srcdir" ;; - esac - -ifdef([AC_PROVIDE_AC_PROG_INSTALL], -[ case "$ac_given_INSTALL" in -changequote(, )dnl - [/$]*) INSTALL="$ac_given_INSTALL" ;; -changequote([, ])dnl - *) INSTALL="$ac_dots$ac_given_INSTALL" ;; - esac -])dnl - - echo creating "$ac_file" - rm -f "$ac_file" - configure_input="Generated automatically from `echo $ac_file_in|sed 's%.*/%%'` by configure." - case "$ac_file" in - *Makefile*) ac_comsub="1i\\ -# $configure_input" ;; - *) ac_comsub= ;; - esac - - ac_file_inputs=`echo $ac_file_in|sed -e "s%^%$ac_given_srcdir/%" -e "s%:% $ac_given_srcdir/%g"` - sed -e "$ac_comsub -s%@configure_input@%$configure_input%g -s%@srcdir@%$srcdir%g -s%@top_srcdir@%$top_srcdir%g -ifdef([AC_PROVIDE_AC_PROG_INSTALL], [s%@INSTALL@%$INSTALL%g -])dnl -dnl The parens around the eval prevent an "illegal io" in Ultrix sh. -" $ac_file_inputs | (eval "$ac_sed_cmds") > $ac_file -dnl This would break Makefile dependencies. -dnl if cmp -s $ac_file conftest.out 2>/dev/null; then -dnl echo "$ac_file is unchanged" -dnl rm -f conftest.out -dnl else -dnl rm -f $ac_file -dnl mv conftest.out $ac_file -dnl fi -fi; done -rm -f conftest.s* -]) - -dnl Create the config.h files from the config.h.in files. -dnl This is a subroutine of AC_OUTPUT. It is called inside a quoted -dnl here document whose contents are going into config.status. -dnl AC_OUTPUT_HEADER(HEADER-FILE...) -define(AC_OUTPUT_HEADER, -[changequote(<<, >>)dnl -# These sed commands are passed to sed as "A NAME B NAME C VALUE D", where -# NAME is the cpp macro being defined and VALUE is the value it is being given. -# -# ac_d sets the value in "#define NAME VALUE" lines. -ac_dA='s%^\([ ]*\)#\([ ]*define[ ][ ]*\)' -ac_dB='\([ ][ ]*\)[^ ]*%\1#\2' -ac_dC='\3' -ac_dD='%g' -# ac_u turns "#undef NAME" with trailing blanks into "#define NAME VALUE". -ac_uA='s%^\([ ]*\)#\([ ]*\)undef\([ ][ ]*\)' -ac_uB='\([ ]\)%\1#\2define\3' -ac_uC=' ' -ac_uD='\4%g' -# ac_e turns "#undef NAME" without trailing blanks into "#define NAME VALUE". -ac_eA='s%^\([ ]*\)#\([ ]*\)undef\([ ][ ]*\)' -ac_eB='<<$>>%\1#\2define\3' -ac_eC=' ' -ac_eD='%g' -changequote([, ])dnl - -if test "${CONFIG_HEADERS+set}" != set; then -EOF -dnl Support passing AC_CONFIG_HEADER a value containing shell variables. -cat >> $CONFIG_STATUS <> $CONFIG_STATUS <<\EOF -fi -for ac_file in .. $CONFIG_HEADERS; do if test "x$ac_file" != x..; then -changequote(, )dnl - # Support "outfile[:infile[:infile...]]", defaulting infile="outfile.in". - case "$ac_file" in - *:*) ac_file_in=`echo "$ac_file"|sed 's%[^:]*:%%'` - ac_file=`echo "$ac_file"|sed 's%:.*%%'` ;; - *) ac_file_in="${ac_file}.in" ;; - esac -changequote([, ])dnl - - echo creating $ac_file - - rm -f conftest.frag conftest.in conftest.out - ac_file_inputs=`echo $ac_file_in|sed -e "s%^%$ac_given_srcdir/%" -e "s%:% $ac_given_srcdir/%g"` - cat $ac_file_inputs > conftest.in - -EOF - -# Transform confdefs.h into a sed script conftest.vals that substitutes -# the proper values into config.h.in to produce config.h. And first: -# Protect against being on the right side of a sed subst in config.status. -# Protect against being in an unquoted here document in config.status. -rm -f conftest.vals -dnl Using a here document instead of a string reduces the quoting nightmare. -dnl Putting comments in sed scripts is not portable. -cat > conftest.hdr <<\EOF -changequote(<<, >>)dnl -s/[\\&%]/\\&/g -s%[\\$`]%\\&%g -s%<<#define>> \([A-Za-z_][A-Za-z0-9_]*\) *\(.*\)%${ac_dA}\1${ac_dB}\1${ac_dC}\2${ac_dD}%gp -s%ac_d%ac_u%gp -s%ac_u%ac_e%gp -changequote([, ])dnl -EOF -sed -n -f conftest.hdr confdefs.h > conftest.vals -rm -f conftest.hdr - -# This sed command replaces #undef with comments. This is necessary, for -# example, in the case of _POSIX_SOURCE, which is predefined and required -# on some systems where configure will not decide to define it. -cat >> conftest.vals <<\EOF -changequote(, )dnl -s%^[ ]*#[ ]*undef[ ][ ]*[a-zA-Z_][a-zA-Z_0-9]*%/* & */% -changequote([, ])dnl -EOF - -# Break up conftest.vals because some shells have a limit on -# the size of here documents, and old seds have small limits too. - -rm -f conftest.tail -while : -do - ac_lines=`grep -c . conftest.vals` - # grep -c gives empty output for an empty file on some AIX systems. - if test -z "$ac_lines" || test "$ac_lines" -eq 0; then break; fi - # Write a limited-size here document to conftest.frag. - echo ' cat > conftest.frag <> $CONFIG_STATUS - sed ${ac_max_here_lines}q conftest.vals >> $CONFIG_STATUS - echo 'CEOF - sed -f conftest.frag conftest.in > conftest.out - rm -f conftest.in - mv conftest.out conftest.in -' >> $CONFIG_STATUS - sed 1,${ac_max_here_lines}d conftest.vals > conftest.tail - rm -f conftest.vals - mv conftest.tail conftest.vals -done -rm -f conftest.vals - -dnl Now back to your regularly scheduled config.status. -cat >> $CONFIG_STATUS <<\EOF - rm -f conftest.frag conftest.h - echo "/* $ac_file. Generated automatically by configure. */" > conftest.h - cat conftest.in >> conftest.h - rm -f conftest.in - if cmp -s $ac_file conftest.h 2>/dev/null; then - echo "$ac_file is unchanged" - rm -f conftest.h - else - # Remove last slash and all that follows it. Not all systems have dirname. - changequote(, )dnl - ac_dir=`echo $ac_file|sed 's%/[^/][^/]*$%%'` - changequote([, ])dnl - if test "$ac_dir" != "$ac_file" && test "$ac_dir" != .; then - # The file is in a subdirectory. - test ! -d "$ac_dir" && mkdir "$ac_dir" - fi - rm -f $ac_file - mv conftest.h $ac_file - fi -fi; done - -]) - -dnl This is a subroutine of AC_OUTPUT. It is called inside a quoted -dnl here document whose contents are going into config.status. -dnl AC_OUTPUT_LINKS(SOURCE..., DEST...) -define(AC_OUTPUT_LINKS, -[EOF - -cat >> $CONFIG_STATUS <> $CONFIG_STATUS <<\EOF -srcdir=$ac_given_srcdir -while test -n "$ac_sources"; do - set $ac_dests; ac_dest=[$]1; shift; ac_dests=[$]* - set $ac_sources; ac_source=[$]1; shift; ac_sources=[$]* - - echo "linking $srcdir/$ac_source to $ac_dest" - - if test ! -r $srcdir/$ac_source; then - AC_MSG_ERROR($srcdir/$ac_source: File not found) - fi - rm -f $ac_dest - - # Make relative symlinks. - # Remove last slash and all that follows it. Not all systems have dirname. -changequote(, )dnl - ac_dest_dir=`echo $ac_dest|sed 's%/[^/][^/]*$%%'` -changequote([, ])dnl - if test "$ac_dest_dir" != "$ac_dest" && test "$ac_dest_dir" != .; then - # The dest file is in a subdirectory. - test ! -d "$ac_dest_dir" && mkdir "$ac_dest_dir" - ac_dest_dir_suffix="/`echo $ac_dest_dir|sed 's%^\./%%'`" - # A "../" for each directory in $ac_dest_dir_suffix. -changequote(, )dnl - ac_dots=`echo $ac_dest_dir_suffix|sed 's%/[^/]*%../%g'` -changequote([, ])dnl - else - ac_dest_dir_suffix= ac_dots= - fi - - case "$srcdir" in -changequote(, )dnl - [/$]*) ac_rel_source="$srcdir/$ac_source" ;; -changequote([, ])dnl - *) ac_rel_source="$ac_dots$srcdir/$ac_source" ;; - esac - - # Make a symlink if possible; otherwise try a hard link. - if ln -s $ac_rel_source $ac_dest 2>/dev/null || - ln $srcdir/$ac_source $ac_dest; then : - else - AC_MSG_ERROR(can not link $ac_dest to $srcdir/$ac_source) - fi -done -]) - -dnl This is a subroutine of AC_OUTPUT. -dnl It is called after running config.status. -dnl AC_OUTPUT_SUBDIRS(DIRECTORY...) -define(AC_OUTPUT_SUBDIRS, -[ -if test "$no_recursion" != yes; then - - # Remove --cache-file and --srcdir arguments so they do not pile up. - ac_sub_configure_args= - ac_prev= - for ac_arg in $ac_configure_args; do - if test -n "$ac_prev"; then - ac_prev= - continue - fi - case "$ac_arg" in - -cache-file | --cache-file | --cache-fil | --cache-fi \ - | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) - ac_prev=cache_file ;; - -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ - | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*) - ;; - -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) - ac_prev=srcdir ;; - -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) - ;; - *) ac_sub_configure_args="$ac_sub_configure_args $ac_arg" ;; - esac - done - - for ac_config_dir in $1; do - - # Do not complain, so a configure script can configure whichever - # parts of a large source tree are present. - if test ! -d $srcdir/$ac_config_dir; then - continue - fi - - echo configuring in $ac_config_dir - - case "$srcdir" in - .) ;; - *) - if test -d ./$ac_config_dir || mkdir ./$ac_config_dir; then :; - else - AC_MSG_ERROR(can not create `pwd`/$ac_config_dir) - fi - ;; - esac - - ac_popdir=`pwd` - cd $ac_config_dir - -changequote(, )dnl - # A "../" for each directory in /$ac_config_dir. - ac_dots=`echo $ac_config_dir|sed -e 's%^\./%%' -e 's%[^/]$%&/%' -e 's%[^/]*/%../%g'` -changequote([, ])dnl - - case "$srcdir" in - .) # No --srcdir option. We are building in place. - ac_sub_srcdir=$srcdir ;; - /*) # Absolute path. - ac_sub_srcdir=$srcdir/$ac_config_dir ;; - *) # Relative path. - ac_sub_srcdir=$ac_dots$srcdir/$ac_config_dir ;; - esac - - # Check for guested configure; otherwise get Cygnus style configure. - if test -f $ac_sub_srcdir/configure; then - ac_sub_configure=$ac_sub_srcdir/configure - elif test -f $ac_sub_srcdir/configure.in; then - ac_sub_configure=$ac_configure - else - AC_MSG_WARN(no configuration information is in $ac_config_dir) - ac_sub_configure= - fi - - # The recursion is here. - if test -n "$ac_sub_configure"; then - - # Make the cache file name correct relative to the subdirectory. - case "$cache_file" in - /*) ac_sub_cache_file=$cache_file ;; - *) # Relative path. - ac_sub_cache_file="$ac_dots$cache_file" ;; - esac -ifdef([AC_PROVIDE_AC_PROG_INSTALL], - [ case "$ac_given_INSTALL" in -changequote(, )dnl - [/$]*) INSTALL="$ac_given_INSTALL" ;; -changequote([, ])dnl - *) INSTALL="$ac_dots$ac_given_INSTALL" ;; - esac -])dnl - - echo "[running ${CONFIG_SHELL-/bin/sh} $ac_sub_configure $ac_sub_configure_args --cache-file=$ac_sub_cache_file] --srcdir=$ac_sub_srcdir" - # The eval makes quoting arguments work. - if eval ${CONFIG_SHELL-/bin/sh} $ac_sub_configure $ac_sub_configure_args --cache-file=$ac_sub_cache_file --srcdir=$ac_sub_srcdir - then : - else - AC_MSG_ERROR($ac_sub_configure failed for $ac_config_dir) - fi - fi - - cd $ac_popdir - done -fi -]) diff --git a/build/autoconf/acoldnames.m4 b/build/autoconf/acoldnames.m4 deleted file mode 100644 index d31cdd754f5..00000000000 --- a/build/autoconf/acoldnames.m4 +++ /dev/null @@ -1,80 +0,0 @@ -dnl Map old names of Autoconf macros to new regularized names. -dnl This file is part of Autoconf. -dnl Copyright (C) 1994 Free Software Foundation, Inc. -dnl -dnl This program is free software; you can redistribute it and/or modify -dnl it under the terms of the GNU General Public License as published by -dnl the Free Software Foundation; either version 2, or (at your option) -dnl any later version. -dnl -dnl This program is distributed in the hope that it will be useful, -dnl but WITHOUT ANY WARRANTY; without even the implied warranty of -dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -dnl GNU General Public License for more details. -dnl -dnl You should have received a copy of the GNU General Public License -dnl along with this program; if not, write to the Free Software -dnl Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA -dnl 02111-1307, USA. -dnl -dnl General macros. -dnl -define(AC_WARN, [indir([AC_MSG_WARN], $@)])dnl -define(AC_ERROR, [indir([AC_MSG_ERROR], $@)])dnl -AC_DEFUN(AC_PROGRAM_CHECK, [indir([AC_CHECK_PROG], $@)])dnl -AC_DEFUN(AC_PROGRAM_PATH, [indir([AC_PATH_PROG], $@)])dnl -AC_DEFUN(AC_PROGRAMS_CHECK, [indir([AC_CHECK_PROGS], $@)])dnl -AC_DEFUN(AC_PROGRAMS_PATH, [indir([AC_PATH_PROGS], $@)])dnl -AC_DEFUN(AC_PREFIX, [indir([AC_PREFIX_PROGRAM], $@)])dnl -AC_DEFUN(AC_HEADER_EGREP, [indir([AC_EGREP_HEADER], $@)])dnl -AC_DEFUN(AC_PROGRAM_EGREP, [indir([AC_EGREP_CPP], $@)])dnl -AC_DEFUN(AC_TEST_PROGRAM, [indir([AC_TRY_RUN], $@)])dnl -AC_DEFUN(AC_TEST_CPP, [indir([AC_TRY_CPP], $@)])dnl -AC_DEFUN(AC_HEADER_CHECK, [indir([AC_CHECK_HEADER], $@)])dnl -AC_DEFUN(AC_FUNC_CHECK, [indir([AC_CHECK_FUNC], $@)])dnl -AC_DEFUN(AC_HAVE_FUNCS, [indir([AC_CHECK_FUNCS], $@)])dnl -AC_DEFUN(AC_HAVE_HEADERS, [indir([AC_CHECK_HEADERS], $@)])dnl -AC_DEFUN(AC_SIZEOF_TYPE, [indir([AC_CHECK_SIZEOF], $@)])dnl -dnl -dnl Specific macros. -dnl -AC_DEFUN(AC_GCC_TRADITIONAL, [indir([AC_PROG_GCC_TRADITIONAL])])dnl -AC_DEFUN(AC_MINUS_C_MINUS_O, [indir([AC_PROG_CC_C_O])])dnl -AC_DEFUN(AC_SET_MAKE, [indir([AC_PROG_MAKE_SET])])dnl -AC_DEFUN(AC_YYTEXT_POINTER, [indir([AC_DECL_YYTEXT])])dnl -AC_DEFUN(AC_LN_S, [indir([AC_PROG_LN_S])])dnl -AC_DEFUN(AC_STDC_HEADERS, [indir([AC_HEADER_STDC])])dnl -AC_DEFUN(AC_MAJOR_HEADER, [indir([AC_HEADER_MAJOR])])dnl -AC_DEFUN(AC_STAT_MACROS_BROKEN, [indir([AC_HEADER_STAT])])dnl -AC_DEFUN(AC_SYS_SIGLIST_DECLARED, [indir([AC_DECL_SYS_SIGLIST])])dnl -AC_DEFUN(AC_GETGROUPS_T, [indir([AC_TYPE_GETGROUPS])])dnl -AC_DEFUN(AC_UID_T, [indir([AC_TYPE_UID_T])])dnl -AC_DEFUN(AC_SIZE_T, [indir([AC_TYPE_SIZE_T])])dnl -AC_DEFUN(AC_PID_T, [indir([AC_TYPE_PID_T])])dnl -AC_DEFUN(AC_OFF_T, [indir([AC_TYPE_OFF_T])])dnl -AC_DEFUN(AC_MODE_T, [indir([AC_TYPE_MODE_T])])dnl -AC_DEFUN(AC_RETSIGTYPE, [indir([AC_TYPE_SIGNAL])])dnl -AC_DEFUN(AC_MMAP, [indir([AC_FUNC_MMAP])])dnl -AC_DEFUN(AC_VPRINTF, [indir([AC_FUNC_VPRINTF])])dnl -AC_DEFUN(AC_VFORK, [indir([AC_FUNC_VFORK])])dnl -AC_DEFUN(AC_WAIT3, [indir([AC_FUNC_WAIT3])])dnl -AC_DEFUN(AC_ALLOCA, [indir([AC_FUNC_ALLOCA])])dnl -AC_DEFUN(AC_GETLOADAVG, [indir([AC_FUNC_GETLOADAVG])])dnl -AC_DEFUN(AC_UTIME_NULL, [indir([AC_FUNC_UTIME_NULL])])dnl -AC_DEFUN(AC_STRCOLL, [indir([AC_FUNC_STRCOLL])])dnl -AC_DEFUN(AC_SETVBUF_REVERSED, [indir([AC_FUNC_SETVBUF_REVERSED])])dnl -AC_DEFUN(AC_TIME_WITH_SYS_TIME, [indir([AC_HEADER_TIME])])dnl -AC_DEFUN(AC_TIMEZONE, [indir([AC_STRUCT_TIMEZONE])])dnl -AC_DEFUN(AC_ST_BLOCKS, [indir([AC_STRUCT_ST_BLOCKS])])dnl -AC_DEFUN(AC_ST_BLKSIZE, [indir([AC_STRUCT_ST_BLKSIZE])])dnl -AC_DEFUN(AC_ST_RDEV, [indir([AC_STRUCT_ST_RDEV])])dnl -AC_DEFUN(AC_CROSS_CHECK, [indir([AC_C_CROSS])])dnl -AC_DEFUN(AC_CHAR_UNSIGNED, [indir([AC_C_CHAR_UNSIGNED])])dnl -AC_DEFUN(AC_LONG_DOUBLE, [indir([AC_C_LONG_DOUBLE])])dnl -AC_DEFUN(AC_WORDS_BIGENDIAN, [indir([AC_C_BIGENDIAN])])dnl -AC_DEFUN(AC_INLINE, [indir([AC_C_INLINE])])dnl -AC_DEFUN(AC_CONST, [indir([AC_C_CONST])])dnl -AC_DEFUN(AC_LONG_FILE_NAMES, [indir([AC_SYS_LONG_FILE_NAMES])])dnl -AC_DEFUN(AC_RESTARTABLE_SYSCALLS, [indir([AC_SYS_RESTARTABLE_SYSCALLS])])dnl -AC_DEFUN(AC_FIND_X, [indir([AC_PATH_X])])dnl -AC_DEFUN(AC_FIND_XTRA, [indir([AC_PATH_XTRA])])dnl diff --git a/build/autoconf/acspecific.m4 b/build/autoconf/acspecific.m4 deleted file mode 100644 index 28af5228767..00000000000 --- a/build/autoconf/acspecific.m4 +++ /dev/null @@ -1,2758 +0,0 @@ -dnl Macros that test for specific features. -dnl This file is part of Autoconf. -dnl Copyright (C) 1992, 93, 94, 95, 96, 1998 Free Software Foundation, Inc. -dnl -dnl This program is free software; you can redistribute it and/or modify -dnl it under the terms of the GNU General Public License as published by -dnl the Free Software Foundation; either version 2, or (at your option) -dnl any later version. -dnl -dnl This program is distributed in the hope that it will be useful, -dnl but WITHOUT ANY WARRANTY; without even the implied warranty of -dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -dnl GNU General Public License for more details. -dnl -dnl You should have received a copy of the GNU General Public License -dnl along with this program; if not, write to the Free Software -dnl Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA -dnl 02111-1307, USA. -dnl -dnl As a special exception, the Free Software Foundation gives unlimited -dnl permission to copy, distribute and modify the configure scripts that -dnl are the output of Autoconf. You need not follow the terms of the GNU -dnl General Public License when using or distributing such scripts, even -dnl though portions of the text of Autoconf appear in them. The GNU -dnl General Public License (GPL) does govern all other use of the material -dnl that constitutes the Autoconf program. -dnl -dnl Certain portions of the Autoconf source text are designed to be copied -dnl (in certain cases, depending on the input) into the output of -dnl Autoconf. We call these the "data" portions. The rest of the Autoconf -dnl source text consists of comments plus executable code that decides which -dnl of the data portions to output in any given case. We call these -dnl comments and executable code the "non-data" portions. Autoconf never -dnl copies any of the non-data portions into its output. -dnl -dnl This special exception to the GPL applies to versions of Autoconf -dnl released by the Free Software Foundation. When you make and -dnl distribute a modified version of Autoconf, you may extend this special -dnl exception to the GPL to apply to your modified version as well, *unless* -dnl your modified version has the potential to copy into its output some -dnl of the text that was the non-data portion of the version that you started -dnl with. (In other words, unless your change moves or copies text from -dnl the non-data portions to the data portions.) If your modification has -dnl such potential, you must delete any notice of this special exception -dnl to the GPL from your modified version. -dnl -dnl Written by David MacKenzie, with help from -dnl Franc,ois Pinard, Karl Berry, Richard Pixley, Ian Lance Taylor, -dnl Roland McGrath, Noah Friedman, david d zuhn, and many others. - - -dnl ### Checks for programs - - -dnl Check whether to use -n, \c, or newline-tab to separate -dnl checking messages from result messages. -dnl Idea borrowed from dist 3.0. -dnl Internal use only. -AC_DEFUN(AC_PROG_ECHO_N, -[if (echo "testing\c"; echo 1,2,3) | grep c >/dev/null; then - # Stardent Vistra SVR4 grep lacks -e, says ghazi@caip.rutgers.edu. - if (echo -n testing; echo 1,2,3) | sed s/-n/xn/ | grep xn >/dev/null; then - ac_n= ac_c=' -' ac_t=' ' - else - ac_n=-n ac_c= ac_t= - fi -else - ac_n= ac_c='\c' ac_t= -fi -]) - -AC_DEFUN(AC_PROG_CC, -[AC_BEFORE([$0], [AC_PROG_CPP])dnl -AC_CHECK_PROG(CC, gcc, gcc) -if test -z "$CC"; then - AC_CHECK_PROG(CC, cc, cc, , , /usr/ucb/cc) - if test -z "$CC"; then - case "`uname -s`" in - *win32* | *WIN32*) - AC_CHECK_PROG(CC, cl, cl) ;; - esac - fi - test -z "$CC" && AC_MSG_ERROR([no acceptable cc found in \$PATH]) -fi - -AC_PROG_CC_WORKS -AC_PROG_CC_GNU - -if test $ac_cv_prog_gcc = yes; then - GCC=yes -else - GCC= -fi - -dnl Check whether -g works, even if CFLAGS is set, in case the package -dnl plays around with CFLAGS (such as to build both debugging and -dnl normal versions of a library), tasteless as that idea is. -ac_test_CFLAGS="${CFLAGS+set}" -ac_save_CFLAGS="$CFLAGS" -CFLAGS= -AC_PROG_CC_G -if test "$ac_test_CFLAGS" = set; then - CFLAGS="$ac_save_CFLAGS" -elif test $ac_cv_prog_cc_g = yes; then - if test "$GCC" = yes; then - CFLAGS="-g -O2" - else - CFLAGS="-g" - fi -else - if test "$GCC" = yes; then - CFLAGS="-O2" - else - CFLAGS= - fi -fi -]) - -AC_DEFUN(AC_PROG_CXX, -[AC_BEFORE([$0], [AC_PROG_CXXCPP])dnl -AC_CHECK_PROGS(CXX, $CCC c++ g++ gcc CC cxx cc++ cl, gcc) - -AC_PROG_CXX_WORKS -AC_PROG_CXX_GNU - -if test $ac_cv_prog_gxx = yes; then - GXX=yes -else - GXX= -fi - -dnl Check whether -g works, even if CXXFLAGS is set, in case the package -dnl plays around with CXXFLAGS (such as to build both debugging and -dnl normal versions of a library), tasteless as that idea is. -ac_test_CXXFLAGS="${CXXFLAGS+set}" -ac_save_CXXFLAGS="$CXXFLAGS" -CXXFLAGS= -AC_PROG_CXX_G -if test "$ac_test_CXXFLAGS" = set; then - CXXFLAGS="$ac_save_CXXFLAGS" -elif test $ac_cv_prog_cxx_g = yes; then - if test "$GXX" = yes; then - CXXFLAGS="-g -O2" - else - CXXFLAGS="-g" - fi -else - if test "$GXX" = yes; then - CXXFLAGS="-O2" - else - CXXFLAGS= - fi -fi -]) - -dnl Determine a Fortran 77 compiler to use. If `F77' is not already set -dnl in the environment, check for `g77', `f77' and `f2c', in that order. -dnl Set the output variable `F77' to the name of the compiler found. -dnl -dnl If using `g77' (the GNU Fortran 77 compiler), then `AC_PROG_F77' -dnl will set the shell variable `G77' to `yes', and empty otherwise. If -dnl the output variable `FFLAGS' was not already set in the environment, -dnl then set it to `-g -02' for `g77' (or `-O2' where `g77' does not -dnl accept `-g'). Otherwise, set `FFLAGS' to `-g' for all other Fortran -dnl 77 compilers. -dnl -dnl AC_PROG_F77() -AC_DEFUN(AC_PROG_F77, -[AC_BEFORE([$0], [AC_PROG_CPP])dnl -if test -z "$F77"; then - AC_CHECK_PROGS(F77, g77 f77 f2c) - test -z "$F77" && AC_MSG_ERROR([no acceptable Fortran 77 compiler found in \$PATH]) -fi - -AC_PROG_F77_WORKS -AC_PROG_F77_GNU - -if test $ac_cv_prog_g77 = yes; then - G77=yes -dnl Check whether -g works, even if FFLAGS is set, in case the package -dnl plays around with FFLAGS (such as to build both debugging and -dnl normal versions of a library), tasteless as that idea is. - ac_test_FFLAGS="${FFLAGS+set}" - ac_save_FFLAGS="$FFLAGS" - FFLAGS= - AC_PROG_F77_G - if test "$ac_test_FFLAGS" = set; then - FFLAGS="$ac_save_FFLAGS" - elif test $ac_cv_prog_f77_g = yes; then - FFLAGS="-g -O2" - else - FFLAGS="-O2" - fi -else - G77= - test "${FFLAGS+set}" = set || FFLAGS="-g" -fi -]) - -AC_DEFUN(AC_PROG_CC_WORKS, -[AC_MSG_CHECKING([whether the C compiler ($CC $CFLAGS $LDFLAGS) works]) -AC_LANG_SAVE -AC_LANG_C -AC_TRY_COMPILER([main(){return(0);}], ac_cv_prog_cc_works, ac_cv_prog_cc_cross) -AC_LANG_RESTORE -AC_MSG_RESULT($ac_cv_prog_cc_works) -if test $ac_cv_prog_cc_works = no; then - AC_MSG_ERROR([installation or configuration problem: C compiler cannot create executables.]) -fi -AC_MSG_CHECKING([whether the C compiler ($CC $CFLAGS $LDFLAGS) is a cross-compiler]) -AC_MSG_RESULT($ac_cv_prog_cc_cross) -cross_compiling=$ac_cv_prog_cc_cross -]) - -AC_DEFUN(AC_PROG_CXX_WORKS, -[AC_MSG_CHECKING([whether the C++ compiler ($CXX $CXXFLAGS $LDFLAGS) works]) -AC_LANG_SAVE -AC_LANG_CPLUSPLUS -AC_TRY_COMPILER([int main(){return(0);}], ac_cv_prog_cxx_works, ac_cv_prog_cxx_cross) -AC_LANG_RESTORE -AC_MSG_RESULT($ac_cv_prog_cxx_works) -if test $ac_cv_prog_cxx_works = no; then - AC_MSG_ERROR([installation or configuration problem: C++ compiler cannot create executables.]) -fi -AC_MSG_CHECKING([whether the C++ compiler ($CXX $CXXFLAGS $LDFLAGS) is a cross-compiler]) -AC_MSG_RESULT($ac_cv_prog_cxx_cross) -cross_compiling=$ac_cv_prog_cxx_cross -]) - -dnl Test whether the Fortran 77 compiler can compile and link a trivial -dnl Fortran program. Also, test whether the Fortran 77 compiler is a -dnl cross-compiler (which may realistically be the case if the Fortran -dnl compiler is `g77'). -dnl -dnl AC_PROG_F77_WORKS() -AC_DEFUN(AC_PROG_F77_WORKS, -[AC_MSG_CHECKING([whether the Fortran 77 compiler ($F77 $FFLAGS $LDFLAGS) works]) -AC_LANG_SAVE -AC_LANG_FORTRAN77 -AC_TRY_COMPILER(dnl -[ program conftest - end -], ac_cv_prog_f77_works, ac_cv_prog_f77_cross) -AC_LANG_RESTORE -AC_MSG_RESULT($ac_cv_prog_f77_works) -if test $ac_cv_prog_f77_works = no; then - AC_MSG_ERROR([installation or configuration problem: Fortran 77 compiler cannot create executables.]) -fi -AC_MSG_CHECKING([whether the Fortran 77 compiler ($F77 $FFLAGS $LDFLAGS) is a cross-compiler]) -AC_MSG_RESULT($ac_cv_prog_f77_cross) -cross_compiling=$ac_cv_prog_f77_cross -]) - -AC_DEFUN(AC_PROG_CC_GNU, -[AC_CACHE_CHECK(whether we are using GNU C, ac_cv_prog_gcc, -[dnl The semicolon is to pacify NeXT's syntax-checking cpp. -cat > conftest.c </dev/null 2>&1; then - ac_cv_prog_gcc=yes -else - ac_cv_prog_gcc=no -fi])]) - -AC_DEFUN(AC_PROG_CXX_GNU, -[AC_CACHE_CHECK(whether we are using GNU C++, ac_cv_prog_gxx, -[dnl The semicolon is to pacify NeXT's syntax-checking cpp. -cat > conftest.C </dev/null 2>&1; then - ac_cv_prog_gxx=yes -else - ac_cv_prog_gxx=no -fi])]) - -dnl Test whether for Fortran 77 compiler is `g77' (the GNU Fortran 77 -dnl Compiler). This test depends on whether the Fortran 77 compiler can -dnl do CPP pre-processing. -dnl -dnl AC_PROG_F77_GNU() -AC_DEFUN(AC_PROG_F77_GNU, -[AC_CACHE_CHECK(whether we are using GNU Fortran 77, ac_cv_prog_g77, -[cat > conftest.fpp </dev/null 2>&1; then - ac_cv_prog_g77=yes -else - ac_cv_prog_g77=no -fi])]) - -AC_DEFUN(AC_PROG_CC_G, -[AC_CACHE_CHECK(whether ${CC-cc} accepts -g, ac_cv_prog_cc_g, -[echo 'void f(){}' > conftest.c -if test -z "`${CC-cc} -g -c conftest.c 2>&1`"; then - ac_cv_prog_cc_g=yes -else - ac_cv_prog_cc_g=no -fi -rm -f conftest* -])]) - -AC_DEFUN(AC_PROG_CXX_G, -[AC_CACHE_CHECK(whether ${CXX-g++} accepts -g, ac_cv_prog_cxx_g, -[echo 'void f(){}' > conftest.cc -if test -z "`${CXX-g++} -g -c conftest.cc 2>&1`"; then - ac_cv_prog_cxx_g=yes -else - ac_cv_prog_cxx_g=no -fi -rm -f conftest* -])]) - -dnl Test whether the Fortran 77 compiler can accept the `-g' option to -dnl enable debugging. -dnl -dnl AC_PROG_F77_G() -AC_DEFUN(AC_PROG_F77_G, -[AC_CACHE_CHECK(whether $F77 accepts -g, ac_cv_prog_f77_g, -[cat > conftest.f << EOF - program conftest - end -EOF -if test -z "`$F77 -g -c conftest.f 2>&1`"; then - ac_cv_prog_f77_g=yes -else - ac_cv_prog_f77_g=no -fi -rm -f conftest* -])]) - -AC_DEFUN(AC_PROG_GCC_TRADITIONAL, -[AC_REQUIRE([AC_PROG_CC])dnl -AC_REQUIRE([AC_PROG_CPP])dnl -if test $ac_cv_prog_gcc = yes; then - AC_CACHE_CHECK(whether ${CC-cc} needs -traditional, - ac_cv_prog_gcc_traditional, -[ ac_pattern="Autoconf.*'x'" - AC_EGREP_CPP($ac_pattern, [#include -Autoconf TIOCGETP], - ac_cv_prog_gcc_traditional=yes, ac_cv_prog_gcc_traditional=no) - - if test $ac_cv_prog_gcc_traditional = no; then - AC_EGREP_CPP($ac_pattern, [#include -Autoconf TCGETA], - ac_cv_prog_gcc_traditional=yes) - fi]) - if test $ac_cv_prog_gcc_traditional = yes; then - CC="$CC -traditional" - fi -fi -]) - -AC_DEFUN(AC_PROG_CC_C_O, -[if test "x$CC" != xcc; then - AC_MSG_CHECKING(whether $CC and cc understand -c and -o together) -else - AC_MSG_CHECKING(whether cc understands -c and -o together) -fi -set dummy $CC; ac_cc="`echo [$]2 | -changequote(, )dnl - sed -e 's/[^a-zA-Z0-9_]/_/g' -e 's/^[0-9]/_/'`" -changequote([, ])dnl -AC_CACHE_VAL(ac_cv_prog_cc_${ac_cc}_c_o, -[echo 'foo(){}' > conftest.c -# Make sure it works both with $CC and with simple cc. -# We do the test twice because some compilers refuse to overwrite an -# existing .o file with -o, though they will create one. -ac_try='${CC-cc} -c conftest.c -o conftest.o 1>&AC_FD_CC' -if AC_TRY_EVAL(ac_try) && - test -f conftest.o && AC_TRY_EVAL(ac_try); -then - eval ac_cv_prog_cc_${ac_cc}_c_o=yes - if test "x$CC" != xcc; then - # Test first that cc exists at all. - if AC_TRY_COMMAND(cc -c conftest.c 1>&AC_FD_CC); then - ac_try='cc -c conftest.c -o conftest.o 1>&AC_FD_CC' - if AC_TRY_EVAL(ac_try) && - test -f conftest.o && AC_TRY_EVAL(ac_try); - then - # cc works too. - : - else - # cc exists but doesn't like -o. - eval ac_cv_prog_cc_${ac_cc}_c_o=no - fi - fi - fi -else - eval ac_cv_prog_cc_${ac_cc}_c_o=no -fi -rm -f conftest* -])dnl -if eval "test \"`echo '$ac_cv_prog_cc_'${ac_cc}_c_o`\" = yes"; then - AC_MSG_RESULT(yes) -else - AC_MSG_RESULT(no) - AC_DEFINE(NO_MINUS_C_MINUS_O) -fi -]) - -dnl Test if the Fortran 77 compiler accepts the options `-c' and `-o' -dnl simultaneously, and define `F77_NO_MINUS_C_MINUS_O' if it does not. -dnl -dnl The usefulness of this macro is questionable, as I can't really see -dnl why anyone would use it. The only reason I include it is for -dnl completeness, since a similar test exists for the C compiler. -dnl -dnl AC_PROG_F77_C_O -AC_DEFUN(AC_PROG_F77_C_O, -[AC_BEFORE([$0], [AC_PROG_F77])dnl -AC_MSG_CHECKING(whether $F77 understand -c and -o together) -set dummy $F77; ac_f77="`echo [$]2 | -changequote(, )dnl -sed -e 's/[^a-zA-Z0-9_]/_/g' -e 's/^[0-9]/_/'`" -changequote([, ])dnl -AC_CACHE_VAL(ac_cv_prog_f77_${ac_f77}_c_o, -[cat > conftest.f << EOF - program conftest - end -EOF -# We do the `AC_TRY_EVAL' test twice because some compilers refuse to -# overwrite an existing `.o' file with `-o', although they will create -# one. -ac_try='$F77 $FFLAGS -c conftest.f -o conftest.o 1>&AC_FD_CC' -if AC_TRY_EVAL(ac_try) && test -f conftest.o && AC_TRY_EVAL(ac_try); then - eval ac_cv_prog_f77_${ac_f77}_c_o=yes -else - eval ac_cv_prog_f77_${ac_f77}_c_o=no -fi -rm -f conftest* -])dnl -if eval "test \"`echo '$ac_cv_prog_f77_'${ac_f77}_c_o`\" = yes"; then - AC_MSG_RESULT(yes) -else - AC_MSG_RESULT(no) - AC_DEFINE(F77_NO_MINUS_C_MINUS_O) -fi -]) - -dnl Define SET_MAKE to set ${MAKE} if make doesn't. -AC_DEFUN(AC_PROG_MAKE_SET, -[AC_MSG_CHECKING(whether ${MAKE-make} sets \${MAKE}) -set dummy ${MAKE-make}; ac_make=`echo "[$]2" | sed 'y%./+-%__p_%'` -AC_CACHE_VAL(ac_cv_prog_make_${ac_make}_set, -[cat > conftestmake <<\EOF -all: - @echo 'ac_maketemp="${MAKE}"' -EOF -changequote(, )dnl -# GNU make sometimes prints "make[1]: Entering...", which would confuse us. -eval `${MAKE-make} -f conftestmake 2>/dev/null | grep temp=` -changequote([, ])dnl -if test -n "$ac_maketemp"; then - eval ac_cv_prog_make_${ac_make}_set=yes -else - eval ac_cv_prog_make_${ac_make}_set=no -fi -rm -f conftestmake])dnl -if eval "test \"`echo '$ac_cv_prog_make_'${ac_make}_set`\" = yes"; then - AC_MSG_RESULT(yes) - SET_MAKE= -else - AC_MSG_RESULT(no) - SET_MAKE="MAKE=${MAKE-make}" -fi -AC_SUBST([SET_MAKE])dnl -]) - -AC_DEFUN(AC_PROG_RANLIB, -[AC_CHECK_PROG(RANLIB, ranlib, ranlib, :)]) - -dnl Check for mawk first since it's generally faster. -AC_DEFUN(AC_PROG_AWK, -[AC_CHECK_PROGS(AWK, mawk gawk nawk awk, )]) - -AC_DEFUN(AC_PROG_YACC, -[AC_CHECK_PROGS(YACC, 'bison -y' byacc, yacc)]) - -AC_DEFUN(AC_PROG_CPP, -[AC_MSG_CHECKING(how to run the C preprocessor) -# On Suns, sometimes $CPP names a directory. -if test -n "$CPP" && test -d "$CPP"; then - CPP= -fi -if test -z "$CPP"; then -AC_CACHE_VAL(ac_cv_prog_CPP, -[ # This must be in double quotes, not single quotes, because CPP may get - # substituted into the Makefile and "${CC-cc}" will confuse make. - CPP="${CC-cc} -E" - # On the NeXT, cc -E runs the code through the compiler's parser, - # not just through cpp. -dnl Use a header file that comes with gcc, so configuring glibc -dnl with a fresh cross-compiler works. - AC_TRY_CPP([#include -Syntax Error], , - CPP="${CC-cc} -E -traditional-cpp" - AC_TRY_CPP([#include -Syntax Error], , - CPP="${CC-cc} -nologo -E" - AC_TRY_CPP([#include -Syntax Error], , CPP=/lib/cpp))) - ac_cv_prog_CPP="$CPP"])dnl - CPP="$ac_cv_prog_CPP" -else - ac_cv_prog_CPP="$CPP" -fi -AC_MSG_RESULT($CPP) -AC_SUBST(CPP)dnl -]) - -AC_DEFUN(AC_PROG_CXXCPP, -[AC_MSG_CHECKING(how to run the C++ preprocessor) -if test -z "$CXXCPP"; then -AC_CACHE_VAL(ac_cv_prog_CXXCPP, -[AC_LANG_SAVE[]dnl -AC_LANG_CPLUSPLUS[]dnl - CXXCPP="${CXX-g++} -E" - AC_TRY_CPP([#include ], , CXXCPP=/lib/cpp) - ac_cv_prog_CXXCPP="$CXXCPP" -AC_LANG_RESTORE[]dnl -fi])dnl -CXXCPP="$ac_cv_prog_CXXCPP" -AC_MSG_RESULT($CXXCPP) -AC_SUBST(CXXCPP)dnl -]) - -dnl Require finding the C or C++ preprocessor, whichever is the -dnl current language. -AC_DEFUN(AC_REQUIRE_CPP, -[ifelse(AC_LANG, C, [AC_REQUIRE([AC_PROG_CPP])], [AC_REQUIRE([AC_PROG_CXXCPP])])]) - -AC_DEFUN(AC_PROG_LEX, -[AC_CHECK_PROG(LEX, flex, flex, lex) -if test -z "$LEXLIB" -then - case "$LEX" in - flex*) ac_lib=fl ;; - *) ac_lib=l ;; - esac - AC_CHECK_LIB($ac_lib, yywrap, LEXLIB="-l$ac_lib") -fi -AC_SUBST(LEXLIB)]) - -dnl Check if lex declares yytext as a char * by default, not a char[]. -undefine([AC_DECL_YYTEXT]) -AC_DEFUN(AC_DECL_YYTEXT, -[AC_REQUIRE_CPP()dnl -AC_REQUIRE([AC_PROG_LEX])dnl -AC_CACHE_CHECK(lex output file root, ac_cv_prog_lex_root, -[# The minimal lex program is just a single line: %%. But some broken lexes -# (Solaris, I think it was) want two %% lines, so accommodate them. -echo '%% -%%' | $LEX -if test -f lex.yy.c; then - ac_cv_prog_lex_root=lex.yy -elif test -f lexyy.c; then - ac_cv_prog_lex_root=lexyy -else - AC_MSG_ERROR(cannot find output from $LEX; giving up) -fi]) -LEX_OUTPUT_ROOT=$ac_cv_prog_lex_root -AC_SUBST(LEX_OUTPUT_ROOT)dnl - -AC_CACHE_CHECK(whether yytext is a pointer, ac_cv_prog_lex_yytext_pointer, -[# POSIX says lex can declare yytext either as a pointer or an array; the -# default is implementation-dependent. Figure out which it is, since -# not all implementations provide the %pointer and %array declarations. -ac_cv_prog_lex_yytext_pointer=no -echo 'extern char *yytext;' >>$LEX_OUTPUT_ROOT.c -ac_save_LIBS="$LIBS" -LIBS="$LIBS $LEXLIB" -AC_TRY_LINK(`cat $LEX_OUTPUT_ROOT.c`, , ac_cv_prog_lex_yytext_pointer=yes) -LIBS="$ac_save_LIBS" -rm -f "${LEX_OUTPUT_ROOT}.c" -]) -dnl -if test $ac_cv_prog_lex_yytext_pointer = yes; then - AC_DEFINE(YYTEXT_POINTER) -fi -]) - -AC_DEFUN(AC_PROG_INSTALL, -[AC_REQUIRE([AC_CONFIG_AUX_DIR_DEFAULT])dnl -# Find a good install program. We prefer a C program (faster), -# so one script is as good as another. But avoid the broken or -# incompatible versions: -# SysV /etc/install, /usr/sbin/install -# SunOS /usr/etc/install -# IRIX /sbin/install -# AIX /bin/install -# AIX 4 /usr/bin/installbsd, which doesn't work without a -g flag -# AFS /usr/afsws/bin/install, which mishandles nonexistent args -# SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff" -# ./install, which can be erroneously created by make from ./install.sh. -AC_MSG_CHECKING(for a BSD compatible install) -if test -z "$INSTALL"; then -AC_CACHE_VAL(ac_cv_path_install, -[ IFS="${IFS= }"; ac_save_IFS="$IFS"; IFS=":" - for ac_dir in $PATH; do - # Account for people who put trailing slashes in PATH elements. - case "$ac_dir/" in - /|./|.//|/etc/*|/usr/sbin/*|/usr/etc/*|/sbin/*|/usr/afsws/bin/*|/usr/ucb/*) ;; - *) - # OSF1 and SCO ODT 3.0 have their own names for install. - # Don't use installbsd from OSF since it installs stuff as root - # by default. - for ac_prog in ginstall scoinst install; do - if test -f $ac_dir/$ac_prog; then - if test $ac_prog = install && - grep dspmsg $ac_dir/$ac_prog >/dev/null 2>&1; then - # AIX install. It has an incompatible calling convention. - : - else - ac_cv_path_install="$ac_dir/$ac_prog -c" - break 2 - fi - fi - done - ;; - esac - done - IFS="$ac_save_IFS" -])dnl - if test "${ac_cv_path_install+set}" = set; then - INSTALL="$ac_cv_path_install" - else - # As a last resort, use the slow shell script. We don't cache a - # path for INSTALL within a source directory, because that will - # break other packages using the cache if that directory is - # removed, or if the path is relative. - INSTALL="$ac_install_sh" - fi -fi -dnl We do special magic for INSTALL instead of AC_SUBST, to get -dnl relative paths right. -AC_MSG_RESULT($INSTALL) - -# Use test -z because SunOS4 sh mishandles braces in ${var-val}. -# It thinks the first close brace ends the variable substitution. -test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}' -AC_SUBST(INSTALL_PROGRAM)dnl - -test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL_PROGRAM}' -AC_SUBST(INSTALL_SCRIPT)dnl - -test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644' -AC_SUBST(INSTALL_DATA)dnl -]) - -AC_DEFUN(AC_PROG_LN_S, -[AC_MSG_CHECKING(whether ln -s works) -AC_CACHE_VAL(ac_cv_prog_LN_S, -[rm -f conftestdata -if ln -s X conftestdata 2>/dev/null -then - rm -f conftestdata - ac_cv_prog_LN_S="ln -s" -else - ac_cv_prog_LN_S=ln -fi])dnl -LN_S="$ac_cv_prog_LN_S" -if test "$ac_cv_prog_LN_S" = "ln -s"; then - AC_MSG_RESULT(yes) -else - AC_MSG_RESULT(no) -fi -AC_SUBST(LN_S)dnl -]) - -define(AC_RSH, -[errprint(__file__:__line__: [$0] has been removed; replace it with equivalent code -)m4exit(4)]) - - -dnl ### Checks for header files - - -AC_DEFUN(AC_HEADER_STDC, -[AC_REQUIRE_CPP()dnl -AC_CACHE_CHECK(for ANSI C header files, ac_cv_header_stdc, -[AC_TRY_CPP([#include -#include -#include -#include ], ac_cv_header_stdc=yes, ac_cv_header_stdc=no) - -if test $ac_cv_header_stdc = yes; then - # SunOS 4.x string.h does not declare mem*, contrary to ANSI. -AC_EGREP_HEADER(memchr, string.h, , ac_cv_header_stdc=no) -fi - -if test $ac_cv_header_stdc = yes; then - # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI. -AC_EGREP_HEADER(free, stdlib.h, , ac_cv_header_stdc=no) -fi - -if test $ac_cv_header_stdc = yes; then - # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi. -AC_TRY_RUN([#include -#define ISLOWER(c) ('a' <= (c) && (c) <= 'z') -#define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c)) -#define XOR(e, f) (((e) && !(f)) || (!(e) && (f))) -int main () { int i; for (i = 0; i < 256; i++) -if (XOR (islower (i), ISLOWER (i)) || toupper (i) != TOUPPER (i)) exit(2); -exit (0); } -], , ac_cv_header_stdc=no, :) -fi]) -if test $ac_cv_header_stdc = yes; then - AC_DEFINE(STDC_HEADERS) -fi -]) - -AC_DEFUN(AC_UNISTD_H, -[AC_OBSOLETE([$0], [; instead use AC_CHECK_HEADERS(unistd.h)])dnl -AC_CHECK_HEADER(unistd.h, AC_DEFINE(HAVE_UNISTD_H))]) - -AC_DEFUN(AC_USG, -[AC_OBSOLETE([$0], - [; instead use AC_CHECK_HEADERS(string.h) and HAVE_STRING_H])dnl -AC_MSG_CHECKING([for BSD string and memory functions]) -AC_TRY_LINK([#include ], [rindex(0, 0); bzero(0, 0);], - [AC_MSG_RESULT(yes)], [AC_MSG_RESULT(no); AC_DEFINE(USG)])]) - - -dnl If memchr and the like aren't declared in , include . -dnl To avoid problems, don't check for gcc2 built-ins. -AC_DEFUN(AC_MEMORY_H, -[AC_OBSOLETE([$0], [; instead use AC_CHECK_HEADERS(memory.h) and HAVE_MEMORY_H])dnl -AC_MSG_CHECKING(whether string.h declares mem functions) -AC_EGREP_HEADER(memchr, string.h, ac_found=yes, ac_found=no) -AC_MSG_RESULT($ac_found) -if test $ac_found = no; then - AC_CHECK_HEADER(memory.h, [AC_DEFINE(NEED_MEMORY_H)]) -fi -]) - -AC_DEFUN(AC_HEADER_MAJOR, -[AC_CACHE_CHECK(whether sys/types.h defines makedev, - ac_cv_header_sys_types_h_makedev, -[AC_TRY_LINK([#include ], [return makedev(0, 0);], - ac_cv_header_sys_types_h_makedev=yes, ac_cv_header_sys_types_h_makedev=no) -]) - -if test $ac_cv_header_sys_types_h_makedev = no; then -AC_CHECK_HEADER(sys/mkdev.h, [AC_DEFINE(MAJOR_IN_MKDEV)]) - - if test $ac_cv_header_sys_mkdev_h = no; then -AC_CHECK_HEADER(sys/sysmacros.h, [AC_DEFINE(MAJOR_IN_SYSMACROS)]) - fi -fi -]) - -AC_DEFUN(AC_HEADER_DIRENT, -[ac_header_dirent=no -AC_CHECK_HEADERS_DIRENT(dirent.h sys/ndir.h sys/dir.h ndir.h, - [ac_header_dirent=$ac_hdr; break]) -# Two versions of opendir et al. are in -ldir and -lx on SCO Xenix. -if test $ac_header_dirent = dirent.h; then -AC_CHECK_LIB(dir, opendir, LIBS="$LIBS -ldir") -else -AC_CHECK_LIB(x, opendir, LIBS="$LIBS -lx") -fi -]) - -dnl Like AC_CHECK_HEADER, except also make sure that HEADER-FILE -dnl defines the type `DIR'. dirent.h on NextStep 3.2 doesn't. -dnl AC_CHECK_HEADER_DIRENT(HEADER-FILE, ACTION-IF-FOUND) -AC_DEFUN(AC_CHECK_HEADER_DIRENT, -[ac_safe=`echo "$1" | sed 'y%./+-%__p_%'` -AC_MSG_CHECKING([for $1 that defines DIR]) -AC_CACHE_VAL(ac_cv_header_dirent_$ac_safe, -[AC_TRY_COMPILE([#include -#include <$1>], [DIR *dirp = 0;], - eval "ac_cv_header_dirent_$ac_safe=yes", - eval "ac_cv_header_dirent_$ac_safe=no")])dnl -if eval "test \"`echo '$ac_cv_header_dirent_'$ac_safe`\" = yes"; then - AC_MSG_RESULT(yes) - $2 -else - AC_MSG_RESULT(no) -fi -]) - -dnl Like AC_CHECK_HEADERS, except succeed only for a HEADER-FILE that -dnl defines `DIR'. -dnl AC_CHECK_HEADERS_DIRENT(HEADER-FILE... [, ACTION]) -define(AC_CHECK_HEADERS_DIRENT, -[for ac_hdr in $1 -do -AC_CHECK_HEADER_DIRENT($ac_hdr, -[changequote(, )dnl - ac_tr_hdr=HAVE_`echo $ac_hdr | sed 'y%abcdefghijklmnopqrstuvwxyz./-%ABCDEFGHIJKLMNOPQRSTUVWXYZ___%'` -changequote([, ])dnl - AC_DEFINE_UNQUOTED($ac_tr_hdr) $2])dnl -done]) - -AC_DEFUN(AC_DIR_HEADER, -[AC_OBSOLETE([$0], [; instead use AC_HEADER_DIRENT])dnl -ac_header_dirent=no -for ac_hdr in dirent.h sys/ndir.h sys/dir.h ndir.h; do - AC_CHECK_HEADER_DIRENT($ac_hdr, [ac_header_dirent=$ac_hdr; break]) -done - -case "$ac_header_dirent" in -dirent.h) AC_DEFINE(DIRENT) ;; -sys/ndir.h) AC_DEFINE(SYSNDIR) ;; -sys/dir.h) AC_DEFINE(SYSDIR) ;; -ndir.h) AC_DEFINE(NDIR) ;; -esac - -AC_CACHE_CHECK(whether closedir returns void, ac_cv_func_closedir_void, -[AC_TRY_RUN([#include -#include <$ac_header_dirent> -int closedir(); main() { exit(closedir(opendir(".")) != 0); }], - ac_cv_func_closedir_void=no, ac_cv_func_closedir_void=yes, ac_cv_func_closedir_void=yes)]) -if test $ac_cv_func_closedir_void = yes; then - AC_DEFINE(VOID_CLOSEDIR) -fi -]) - -AC_DEFUN(AC_HEADER_STAT, -[AC_CACHE_CHECK(whether stat file-mode macros are broken, - ac_cv_header_stat_broken, -[AC_EGREP_CPP([You lose], [#include -#include - -#if defined(S_ISBLK) && defined(S_IFDIR) -# if S_ISBLK (S_IFDIR) -You lose. -# endif -#endif - -#if defined(S_ISBLK) && defined(S_IFCHR) -# if S_ISBLK (S_IFCHR) -You lose. -# endif -#endif - -#if defined(S_ISLNK) && defined(S_IFREG) -# if S_ISLNK (S_IFREG) -You lose. -# endif -#endif - -#if defined(S_ISSOCK) && defined(S_IFREG) -# if S_ISSOCK (S_IFREG) -You lose. -# endif -#endif -], ac_cv_header_stat_broken=yes, ac_cv_header_stat_broken=no)]) -if test $ac_cv_header_stat_broken = yes; then - AC_DEFINE(STAT_MACROS_BROKEN) -fi -]) - -AC_DEFUN(AC_DECL_SYS_SIGLIST, -[AC_CACHE_CHECK([for sys_siglist declaration in signal.h or unistd.h], - ac_cv_decl_sys_siglist, -[AC_TRY_COMPILE([#include -#include -/* NetBSD declares sys_siglist in unistd.h. */ -#ifdef HAVE_UNISTD_H -#include -#endif], [char *msg = *(sys_siglist + 1);], - ac_cv_decl_sys_siglist=yes, ac_cv_decl_sys_siglist=no)]) -if test $ac_cv_decl_sys_siglist = yes; then - AC_DEFINE(SYS_SIGLIST_DECLARED) -fi -]) - -AC_DEFUN(AC_HEADER_SYS_WAIT, -[AC_CACHE_CHECK([for sys/wait.h that is POSIX.1 compatible], - ac_cv_header_sys_wait_h, -[AC_TRY_COMPILE([#include -#include -#ifndef WEXITSTATUS -#define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8) -#endif -#ifndef WIFEXITED -#define WIFEXITED(stat_val) (((stat_val) & 255) == 0) -#endif], [int s; -wait (&s); -s = WIFEXITED (s) ? WEXITSTATUS (s) : 1;], -ac_cv_header_sys_wait_h=yes, ac_cv_header_sys_wait_h=no)]) -if test $ac_cv_header_sys_wait_h = yes; then - AC_DEFINE(HAVE_SYS_WAIT_H) -fi -]) - - -dnl ### Checks for typedefs - - -AC_DEFUN(AC_TYPE_GETGROUPS, -[AC_REQUIRE([AC_TYPE_UID_T])dnl -AC_CACHE_CHECK(type of array argument to getgroups, ac_cv_type_getgroups, -[AC_TRY_RUN( -changequote(<<, >>)dnl -<< -/* Thanks to Mike Rendell for this test. */ -#include -#define NGID 256 -#undef MAX -#define MAX(x, y) ((x) > (y) ? (x) : (y)) -main() -{ - gid_t gidset[NGID]; - int i, n; - union { gid_t gval; long lval; } val; - - val.lval = -1; - for (i = 0; i < NGID; i++) - gidset[i] = val.gval; - n = getgroups (sizeof (gidset) / MAX (sizeof (int), sizeof (gid_t)) - 1, - gidset); - /* Exit non-zero if getgroups seems to require an array of ints. This - happens when gid_t is short but getgroups modifies an array of ints. */ - exit ((n > 0 && gidset[n] != val.gval) ? 1 : 0); -} ->>, -changequote([, ])dnl - ac_cv_type_getgroups=gid_t, ac_cv_type_getgroups=int, - ac_cv_type_getgroups=cross) -if test $ac_cv_type_getgroups = cross; then - dnl When we can't run the test program (we are cross compiling), presume - dnl that has either an accurate prototype for getgroups or none. - dnl Old systems without prototypes probably use int. - AC_EGREP_HEADER([getgroups.*int.*gid_t], unistd.h, - ac_cv_type_getgroups=gid_t, ac_cv_type_getgroups=int) -fi]) -AC_DEFINE_UNQUOTED(GETGROUPS_T, $ac_cv_type_getgroups) -]) - -AC_DEFUN(AC_TYPE_UID_T, -[AC_CACHE_CHECK(for uid_t in sys/types.h, ac_cv_type_uid_t, -[AC_EGREP_HEADER(uid_t, sys/types.h, - ac_cv_type_uid_t=yes, ac_cv_type_uid_t=no)]) -if test $ac_cv_type_uid_t = no; then - AC_DEFINE(uid_t, int) - AC_DEFINE(gid_t, int) -fi -]) - -AC_DEFUN(AC_TYPE_SIZE_T, -[AC_CHECK_TYPE(size_t, unsigned)]) - -AC_DEFUN(AC_TYPE_PID_T, -[AC_CHECK_TYPE(pid_t, int)]) - -AC_DEFUN(AC_TYPE_OFF_T, -[AC_CHECK_TYPE(off_t, long)]) - -AC_DEFUN(AC_TYPE_MODE_T, -[AC_CHECK_TYPE(mode_t, int)]) - -dnl Note that identifiers starting with SIG are reserved by ANSI C. -AC_DEFUN(AC_TYPE_SIGNAL, -[AC_CACHE_CHECK([return type of signal handlers], ac_cv_type_signal, -[AC_TRY_COMPILE([#include -#include -#ifdef signal -#undef signal -#endif -#ifdef __cplusplus -extern "C" void (*signal (int, void (*)(int)))(int); -#else -void (*signal ()) (); -#endif -], -[int i;], ac_cv_type_signal=void, ac_cv_type_signal=int)]) -AC_DEFINE_UNQUOTED(RETSIGTYPE, $ac_cv_type_signal) -]) - - -dnl ### Checks for functions - - -AC_DEFUN(AC_FUNC_CLOSEDIR_VOID, -[AC_REQUIRE([AC_HEADER_DIRENT])dnl -AC_CACHE_CHECK(whether closedir returns void, ac_cv_func_closedir_void, -[AC_TRY_RUN([#include -#include <$ac_header_dirent> -int closedir(); main() { exit(closedir(opendir(".")) != 0); }], - ac_cv_func_closedir_void=no, ac_cv_func_closedir_void=yes, ac_cv_func_closedir_void=yes)]) -if test $ac_cv_func_closedir_void = yes; then - AC_DEFINE(CLOSEDIR_VOID) -fi -]) - -AC_DEFUN(AC_FUNC_FNMATCH, -[AC_CACHE_CHECK(for working fnmatch, ac_cv_func_fnmatch_works, -# Some versions of Solaris or SCO have a broken fnmatch function. -# So we run a test program. If we are cross-compiling, take no chance. -# Thanks to John Oleynick and Franc,ois Pinard for this test. -[AC_TRY_RUN([main() { exit (fnmatch ("a*", "abc", 0) != 0); }], -ac_cv_func_fnmatch_works=yes, ac_cv_func_fnmatch_works=no, -ac_cv_func_fnmatch_works=no)]) -if test $ac_cv_func_fnmatch_works = yes; then - AC_DEFINE(HAVE_FNMATCH) -fi -]) - -AC_DEFUN(AC_FUNC_MMAP, -[AC_CHECK_HEADERS(unistd.h) -AC_CHECK_FUNCS(getpagesize) -AC_CACHE_CHECK(for working mmap, ac_cv_func_mmap_fixed_mapped, -[AC_TRY_RUN([ -/* Thanks to Mike Haertel and Jim Avera for this test. - Here is a matrix of mmap possibilities: - mmap private not fixed - mmap private fixed at somewhere currently unmapped - mmap private fixed at somewhere already mapped - mmap shared not fixed - mmap shared fixed at somewhere currently unmapped - mmap shared fixed at somewhere already mapped - For private mappings, we should verify that changes cannot be read() - back from the file, nor mmap's back from the file at a different - address. (There have been systems where private was not correctly - implemented like the infamous i386 svr4.0, and systems where the - VM page cache was not coherent with the filesystem buffer cache - like early versions of FreeBSD and possibly contemporary NetBSD.) - For shared mappings, we should conversely verify that changes get - propogated back to all the places they're supposed to be. - - Grep wants private fixed already mapped. - The main things grep needs to know about mmap are: - * does it exist and is it safe to write into the mmap'd area - * how to use it (BSD variants) */ -#include -#include -#include - -/* This mess was copied from the GNU getpagesize.h. */ -#ifndef HAVE_GETPAGESIZE -# ifdef HAVE_UNISTD_H -# include -# endif - -/* Assume that all systems that can run configure have sys/param.h. */ -# ifndef HAVE_SYS_PARAM_H -# define HAVE_SYS_PARAM_H 1 -# endif - -# ifdef _SC_PAGESIZE -# define getpagesize() sysconf(_SC_PAGESIZE) -# else /* no _SC_PAGESIZE */ -# ifdef HAVE_SYS_PARAM_H -# include -# ifdef EXEC_PAGESIZE -# define getpagesize() EXEC_PAGESIZE -# else /* no EXEC_PAGESIZE */ -# ifdef NBPG -# define getpagesize() NBPG * CLSIZE -# ifndef CLSIZE -# define CLSIZE 1 -# endif /* no CLSIZE */ -# else /* no NBPG */ -# ifdef NBPC -# define getpagesize() NBPC -# else /* no NBPC */ -# ifdef PAGESIZE -# define getpagesize() PAGESIZE -# endif /* PAGESIZE */ -# endif /* no NBPC */ -# endif /* no NBPG */ -# endif /* no EXEC_PAGESIZE */ -# else /* no HAVE_SYS_PARAM_H */ -# define getpagesize() 8192 /* punt totally */ -# endif /* no HAVE_SYS_PARAM_H */ -# endif /* no _SC_PAGESIZE */ - -#endif /* no HAVE_GETPAGESIZE */ - -#ifdef __cplusplus -extern "C" { void *malloc(unsigned); } -#else -char *malloc(); -#endif - -int -main() -{ - char *data, *data2, *data3; - int i, pagesize; - int fd; - - pagesize = getpagesize(); - - /* - * First, make a file with some known garbage in it. - */ - data = malloc(pagesize); - if (!data) - exit(1); - for (i = 0; i < pagesize; ++i) - *(data + i) = rand(); - umask(0); - fd = creat("conftestmmap", 0600); - if (fd < 0) - exit(1); - if (write(fd, data, pagesize) != pagesize) - exit(1); - close(fd); - - /* - * Next, try to mmap the file at a fixed address which - * already has something else allocated at it. If we can, - * also make sure that we see the same garbage. - */ - fd = open("conftestmmap", O_RDWR); - if (fd < 0) - exit(1); - data2 = malloc(2 * pagesize); - if (!data2) - exit(1); - data2 += (pagesize - ((int) data2 & (pagesize - 1))) & (pagesize - 1); - if (data2 != mmap(data2, pagesize, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_FIXED, fd, 0L)) - exit(1); - for (i = 0; i < pagesize; ++i) - if (*(data + i) != *(data2 + i)) - exit(1); - - /* - * Finally, make sure that changes to the mapped area - * do not percolate back to the file as seen by read(). - * (This is a bug on some variants of i386 svr4.0.) - */ - for (i = 0; i < pagesize; ++i) - *(data2 + i) = *(data2 + i) + 1; - data3 = malloc(pagesize); - if (!data3) - exit(1); - if (read(fd, data3, pagesize) != pagesize) - exit(1); - for (i = 0; i < pagesize; ++i) - if (*(data + i) != *(data3 + i)) - exit(1); - close(fd); - unlink("conftestmmap"); - exit(0); -} -], ac_cv_func_mmap_fixed_mapped=yes, ac_cv_func_mmap_fixed_mapped=no, -ac_cv_func_mmap_fixed_mapped=no)]) -if test $ac_cv_func_mmap_fixed_mapped = yes; then - AC_DEFINE(HAVE_MMAP) -fi -]) - -AC_DEFUN(AC_FUNC_GETPGRP, -[AC_CACHE_CHECK(whether getpgrp takes no argument, ac_cv_func_getpgrp_void, -[AC_TRY_RUN([ -/* - * If this system has a BSD-style getpgrp(), - * which takes a pid argument, exit unsuccessfully. - * - * Snarfed from Chet Ramey's bash pgrp.c test program - */ -#include -#include - -int pid; -int pg1, pg2, pg3, pg4; -int ng, np, s, child; - -main() -{ - pid = getpid(); - pg1 = getpgrp(0); - pg2 = getpgrp(); - pg3 = getpgrp(pid); - pg4 = getpgrp(1); - - /* - * If all of these values are the same, it's pretty sure that - * we're on a system that ignores getpgrp's first argument. - */ - if (pg2 == pg4 && pg1 == pg3 && pg2 == pg3) - exit(0); - - child = fork(); - if (child < 0) - exit(1); - else if (child == 0) { - np = getpid(); - /* - * If this is Sys V, this will not work; pgrp will be - * set to np because setpgrp just changes a pgrp to be - * the same as the pid. - */ - setpgrp(np, pg1); - ng = getpgrp(0); /* Same result for Sys V and BSD */ - if (ng == pg1) { - exit(1); - } else { - exit(0); - } - } else { - wait(&s); - exit(s>>8); - } -} -], ac_cv_func_getpgrp_void=yes, ac_cv_func_getpgrp_void=no, - AC_MSG_ERROR(cannot check getpgrp if cross compiling)) -]) -if test $ac_cv_func_getpgrp_void = yes; then - AC_DEFINE(GETPGRP_VOID) -fi -]) - -AC_DEFUN(AC_FUNC_SETPGRP, -[AC_CACHE_CHECK(whether setpgrp takes no argument, ac_cv_func_setpgrp_void, -AC_TRY_RUN([ -#ifdef HAVE_UNISTD_H -#include -#endif - -/* - * If this system has a BSD-style setpgrp, which takes arguments, exit - * successfully. - */ -main() -{ - if (setpgrp(1,1) == -1) - exit(0); - else - exit(1); -} -], ac_cv_func_setpgrp_void=no, ac_cv_func_setpgrp_void=yes, - AC_MSG_ERROR(cannot check setpgrp if cross compiling)) -) -if test $ac_cv_func_setpgrp_void = yes; then - AC_DEFINE(SETPGRP_VOID) -fi -]) - -AC_DEFUN(AC_FUNC_VPRINTF, -[AC_CHECK_FUNC(vprintf, AC_DEFINE(HAVE_VPRINTF)) -if test "$ac_cv_func_vprintf" != yes; then -AC_CHECK_FUNC(_doprnt, AC_DEFINE(HAVE_DOPRNT)) -fi -]) - -AC_DEFUN(AC_FUNC_VFORK, -[AC_REQUIRE([AC_TYPE_PID_T])dnl -AC_CHECK_HEADER(vfork.h, AC_DEFINE(HAVE_VFORK_H)) -AC_CACHE_CHECK(for working vfork, ac_cv_func_vfork_works, -[AC_TRY_RUN([/* Thanks to Paul Eggert for this test. */ -#include -#include -#include -#ifdef HAVE_UNISTD_H -#include -#endif -#ifdef HAVE_VFORK_H -#include -#endif -/* On some sparc systems, changes by the child to local and incoming - argument registers are propagated back to the parent. - The compiler is told about this with #include , - but some compilers (e.g. gcc -O) don't grok . - Test for this by using a static variable whose address - is put into a register that is clobbered by the vfork. */ -static -#ifdef __cplusplus -sparc_address_test (int arg) -#else -sparc_address_test (arg) int arg; -#endif -{ - static pid_t child; - if (!child) { - child = vfork (); - if (child < 0) { - perror ("vfork"); - _exit(2); - } - if (!child) { - arg = getpid(); - write(-1, "", 0); - _exit (arg); - } - } -} -main() { - pid_t parent = getpid (); - pid_t child; - - sparc_address_test (); - - child = vfork (); - - if (child == 0) { - /* Here is another test for sparc vfork register problems. - This test uses lots of local variables, at least - as many local variables as main has allocated so far - including compiler temporaries. 4 locals are enough for - gcc 1.40.3 on a Solaris 4.1.3 sparc, but we use 8 to be safe. - A buggy compiler should reuse the register of parent - for one of the local variables, since it will think that - parent can't possibly be used any more in this routine. - Assigning to the local variable will thus munge parent - in the parent process. */ - pid_t - p = getpid(), p1 = getpid(), p2 = getpid(), p3 = getpid(), - p4 = getpid(), p5 = getpid(), p6 = getpid(), p7 = getpid(); - /* Convince the compiler that p..p7 are live; otherwise, it might - use the same hardware register for all 8 local variables. */ - if (p != p1 || p != p2 || p != p3 || p != p4 - || p != p5 || p != p6 || p != p7) - _exit(1); - - /* On some systems (e.g. IRIX 3.3), - vfork doesn't separate parent from child file descriptors. - If the child closes a descriptor before it execs or exits, - this munges the parent's descriptor as well. - Test for this by closing stdout in the child. */ - _exit(close(fileno(stdout)) != 0); - } else { - int status; - struct stat st; - - while (wait(&status) != child) - ; - exit( - /* Was there some problem with vforking? */ - child < 0 - - /* Did the child fail? (This shouldn't happen.) */ - || status - - /* Did the vfork/compiler bug occur? */ - || parent != getpid() - - /* Did the file descriptor bug occur? */ - || fstat(fileno(stdout), &st) != 0 - ); - } -}], -ac_cv_func_vfork_works=yes, ac_cv_func_vfork_works=no, AC_CHECK_FUNC(vfork) -ac_cv_func_vfork_works=$ac_cv_func_vfork)]) -if test $ac_cv_func_vfork_works = no; then - AC_DEFINE(vfork, fork) -fi -]) - -AC_DEFUN(AC_FUNC_WAIT3, -[AC_CACHE_CHECK(for wait3 that fills in rusage, ac_cv_func_wait3_rusage, -[AC_TRY_RUN([#include -#include -#include -#include -/* HP-UX has wait3 but does not fill in rusage at all. */ -main() { - struct rusage r; - int i; - /* Use a field that we can force nonzero -- - voluntary context switches. - For systems like NeXT and OSF/1 that don't set it, - also use the system CPU time. And page faults (I/O) for Linux. */ - r.ru_nvcsw = 0; - r.ru_stime.tv_sec = 0; - r.ru_stime.tv_usec = 0; - r.ru_majflt = r.ru_minflt = 0; - switch (fork()) { - case 0: /* Child. */ - sleep(1); /* Give up the CPU. */ - _exit(0); - case -1: _exit(0); /* What can we do? */ - default: /* Parent. */ - wait3(&i, 0, &r); - sleep(2); /* Avoid "text file busy" from rm on fast HP-UX machines. */ - exit(r.ru_nvcsw == 0 && r.ru_majflt == 0 && r.ru_minflt == 0 - && r.ru_stime.tv_sec == 0 && r.ru_stime.tv_usec == 0); - } -}], ac_cv_func_wait3_rusage=yes, ac_cv_func_wait3_rusage=no, -ac_cv_func_wait3_rusage=no)]) -if test $ac_cv_func_wait3_rusage = yes; then - AC_DEFINE(HAVE_WAIT3) -fi -]) - -AC_DEFUN(AC_FUNC_ALLOCA, -[AC_REQUIRE_CPP()dnl Set CPP; we run AC_EGREP_CPP conditionally. -# The Ultrix 4.2 mips builtin alloca declared by alloca.h only works -# for constant arguments. Useless! -AC_CACHE_CHECK([for working alloca.h], ac_cv_header_alloca_h, -[AC_TRY_LINK([#include ], [char *p = alloca(2 * sizeof(int));], - ac_cv_header_alloca_h=yes, ac_cv_header_alloca_h=no)]) -if test $ac_cv_header_alloca_h = yes; then - AC_DEFINE(HAVE_ALLOCA_H) -fi - -AC_CACHE_CHECK([for alloca], ac_cv_func_alloca_works, -[AC_TRY_LINK([ -#ifdef __GNUC__ -# define alloca __builtin_alloca -#else -# ifdef _MSC_VER -# include -# define alloca _alloca -# else -# if HAVE_ALLOCA_H -# include -# else -# ifdef _AIX - #pragma alloca -# else -# ifndef alloca /* predefined by HP cc +Olibcalls */ -char *alloca (); -# endif -# endif -# endif -# endif -#endif -], [char *p = (char *) alloca(1);], - ac_cv_func_alloca_works=yes, ac_cv_func_alloca_works=no)]) -if test $ac_cv_func_alloca_works = yes; then - AC_DEFINE(HAVE_ALLOCA) -fi - -if test $ac_cv_func_alloca_works = no; then - # The SVR3 libPW and SVR4 libucb both contain incompatible functions - # that cause trouble. Some versions do not even contain alloca or - # contain a buggy version. If you still want to use their alloca, - # use ar to extract alloca.o from them instead of compiling alloca.c. - ALLOCA=alloca.${ac_objext} - AC_DEFINE(C_ALLOCA) - -AC_CACHE_CHECK(whether alloca needs Cray hooks, ac_cv_os_cray, -[AC_EGREP_CPP(webecray, -[#if defined(CRAY) && ! defined(CRAY2) -webecray -#else -wenotbecray -#endif -], ac_cv_os_cray=yes, ac_cv_os_cray=no)]) -if test $ac_cv_os_cray = yes; then -for ac_func in _getb67 GETB67 getb67; do - AC_CHECK_FUNC($ac_func, [AC_DEFINE_UNQUOTED(CRAY_STACKSEG_END, $ac_func) - break]) -done -fi - -AC_CACHE_CHECK(stack direction for C alloca, ac_cv_c_stack_direction, -[AC_TRY_RUN([find_stack_direction () -{ - static char *addr = 0; - auto char dummy; - if (addr == 0) - { - addr = &dummy; - return find_stack_direction (); - } - else - return (&dummy > addr) ? 1 : -1; -} -main () -{ - exit (find_stack_direction() < 0); -}], ac_cv_c_stack_direction=1, ac_cv_c_stack_direction=-1, - ac_cv_c_stack_direction=0)]) -AC_DEFINE_UNQUOTED(STACK_DIRECTION, $ac_cv_c_stack_direction) -fi -AC_SUBST(ALLOCA)dnl -]) - -AC_DEFUN(AC_FUNC_GETLOADAVG, -[ac_have_func=no # yes means we've found a way to get the load average. - -# Some systems with -lutil have (and need) -lkvm as well, some do not. -# On Solaris, -lkvm requires nlist from -lelf, so check that first -# to get the right answer into the cache. -AC_CHECK_LIB(elf, elf_begin, LIBS="-lelf $LIBS") -AC_CHECK_LIB(kvm, kvm_open, LIBS="-lkvm $LIBS") -# Check for the 4.4BSD definition of getloadavg. -AC_CHECK_LIB(util, getloadavg, - [LIBS="-lutil $LIBS" ac_have_func=yes ac_cv_func_getloadavg_setgid=yes]) - -if test $ac_have_func = no; then - # There is a commonly available library for RS/6000 AIX. - # Since it is not a standard part of AIX, it might be installed locally. - ac_getloadavg_LIBS="$LIBS"; LIBS="-L/usr/local/lib $LIBS" - AC_CHECK_LIB(getloadavg, getloadavg, - LIBS="-lgetloadavg $LIBS", LIBS="$ac_getloadavg_LIBS") -fi - -# Make sure it is really in the library, if we think we found it. -AC_REPLACE_FUNCS(getloadavg) - -if test $ac_cv_func_getloadavg = yes; then - AC_DEFINE(HAVE_GETLOADAVG) - ac_have_func=yes -else - # Figure out what our getloadavg.c needs. - ac_have_func=no - AC_CHECK_HEADER(sys/dg_sys_info.h, - [ac_have_func=yes; AC_DEFINE(DGUX) - AC_CHECK_LIB(dgc, dg_sys_info)]) - - # We cannot check for , because Solaris 2 does not use dwarf (it - # uses stabs), but it is still SVR4. We cannot check for because - # Irix 4.0.5F has the header but not the library. - if test $ac_have_func = no && test $ac_cv_lib_elf_elf_begin = yes; then - ac_have_func=yes; AC_DEFINE(SVR4) - fi - - if test $ac_have_func = no; then - AC_CHECK_HEADER(inq_stats/cpustats.h, - [ac_have_func=yes; AC_DEFINE(UMAX) - AC_DEFINE(UMAX4_3)]) - fi - - if test $ac_have_func = no; then - AC_CHECK_HEADER(sys/cpustats.h, - [ac_have_func=yes; AC_DEFINE(UMAX)]) - fi - - if test $ac_have_func = no; then - AC_CHECK_HEADERS(mach/mach.h) - fi - - AC_CHECK_HEADER(nlist.h, - [AC_DEFINE(NLIST_STRUCT) - AC_CACHE_CHECK([for n_un in struct nlist], ac_cv_struct_nlist_n_un, - [AC_TRY_COMPILE([#include ], - [struct nlist n; n.n_un.n_name = 0;], - ac_cv_struct_nlist_n_un=yes, ac_cv_struct_nlist_n_un=no)]) - if test $ac_cv_struct_nlist_n_un = yes; then - AC_DEFINE(NLIST_NAME_UNION) - fi - ])dnl -fi # Do not have getloadavg in system libraries. - -# Some definitions of getloadavg require that the program be installed setgid. -dnl FIXME Don't hardwire the path of getloadavg.c in the top-level directory. -AC_CACHE_CHECK(whether getloadavg requires setgid, - ac_cv_func_getloadavg_setgid, -[AC_EGREP_CPP([Yowza Am I SETGID yet], -[#include "$srcdir/getloadavg.c" -#ifdef LDAV_PRIVILEGED -Yowza Am I SETGID yet -#endif], - ac_cv_func_getloadavg_setgid=yes, ac_cv_func_getloadavg_setgid=no)]) -if test $ac_cv_func_getloadavg_setgid = yes; then - NEED_SETGID=true; AC_DEFINE(GETLOADAVG_PRIVILEGED) -else - NEED_SETGID=false -fi -AC_SUBST(NEED_SETGID)dnl - -if test $ac_cv_func_getloadavg_setgid = yes; then - AC_CACHE_CHECK(group of /dev/kmem, ac_cv_group_kmem, -[changequote(, )dnl - # On Solaris, /dev/kmem is a symlink. Get info on the real file. - ac_ls_output=`ls -lgL /dev/kmem 2>/dev/null` - # If we got an error (system does not support symlinks), try without -L. - test -z "$ac_ls_output" && ac_ls_output=`ls -lg /dev/kmem` - ac_cv_group_kmem=`echo $ac_ls_output \ - | sed -ne 's/[ ][ ]*/ /g; - s/^.[sSrwx-]* *[0-9]* *\([^0-9]*\) *.*/\1/; - / /s/.* //;p;'` -changequote([, ])dnl -]) - KMEM_GROUP=$ac_cv_group_kmem -fi -AC_SUBST(KMEM_GROUP)dnl -]) - -AC_DEFUN(AC_FUNC_UTIME_NULL, -[AC_CACHE_CHECK(whether utime accepts a null argument, ac_cv_func_utime_null, -[rm -f conftestdata; > conftestdata -# Sequent interprets utime(file, 0) to mean use start of epoch. Wrong. -AC_TRY_RUN([#include -#include -main() { -struct stat s, t; -exit(!(stat ("conftestdata", &s) == 0 && utime("conftestdata", (long *)0) == 0 -&& stat("conftestdata", &t) == 0 && t.st_mtime >= s.st_mtime -&& t.st_mtime - s.st_mtime < 120)); -}], ac_cv_func_utime_null=yes, ac_cv_func_utime_null=no, - ac_cv_func_utime_null=no) -rm -f core core.* *.core]) -if test $ac_cv_func_utime_null = yes; then - AC_DEFINE(HAVE_UTIME_NULL) -fi -]) - -AC_DEFUN(AC_FUNC_STRCOLL, -[AC_CACHE_CHECK(for working strcoll, ac_cv_func_strcoll_works, -[AC_TRY_RUN([#include -main () -{ - exit (strcoll ("abc", "def") >= 0 || - strcoll ("ABC", "DEF") >= 0 || - strcoll ("123", "456") >= 0); -}], ac_cv_func_strcoll_works=yes, ac_cv_func_strcoll_works=no, -ac_cv_func_strcoll_works=no)]) -if test $ac_cv_func_strcoll_works = yes; then - AC_DEFINE(HAVE_STRCOLL) -fi -]) - -AC_DEFUN(AC_FUNC_SETVBUF_REVERSED, -[AC_CACHE_CHECK(whether setvbuf arguments are reversed, - ac_cv_func_setvbuf_reversed, -[AC_TRY_RUN([#include -/* If setvbuf has the reversed format, exit 0. */ -main () { - /* This call has the arguments reversed. - A reversed system may check and see that the address of main - is not _IOLBF, _IONBF, or _IOFBF, and return nonzero. */ - if (setvbuf(stdout, _IOLBF, (char *) main, BUFSIZ) != 0) - exit(1); - putc('\r', stdout); - exit(0); /* Non-reversed systems segv here. */ -}], ac_cv_func_setvbuf_reversed=yes, ac_cv_func_setvbuf_reversed=no) -rm -f core core.* *.core]) -if test $ac_cv_func_setvbuf_reversed = yes; then - AC_DEFINE(SETVBUF_REVERSED) -fi -]) - -AC_DEFUN(AC_FUNC_GETMNTENT, -[# getmntent is in -lsun on Irix 4, -lseq on Dynix/PTX, -lgen on Unixware. -AC_CHECK_LIB(sun, getmntent, LIBS="-lsun $LIBS", - [AC_CHECK_LIB(seq, getmntent, LIBS="-lseq $LIBS", - [AC_CHECK_LIB(gen, getmntent, LIBS="-lgen $LIBS")])]) -AC_CHECK_FUNC(getmntent, [AC_DEFINE(HAVE_GETMNTENT)])]) - -AC_DEFUN(AC_FUNC_STRFTIME, -[AC_CHECK_FUNC(strftime, [AC_DEFINE(HAVE_STRFTIME)], -[# strftime is in -lintl on SCO UNIX. -AC_CHECK_LIB(intl, strftime, -[AC_DEFINE(HAVE_STRFTIME) -LIBS="-lintl $LIBS"])])]) - -AC_DEFUN(AC_FUNC_MEMCMP, -[AC_CACHE_CHECK(for 8-bit clean memcmp, ac_cv_func_memcmp_clean, -[AC_TRY_RUN([ -main() -{ - char c0 = 0x40, c1 = 0x80, c2 = 0x81; - exit(memcmp(&c0, &c2, 1) < 0 && memcmp(&c1, &c2, 1) < 0 ? 0 : 1); -} -], ac_cv_func_memcmp_clean=yes, ac_cv_func_memcmp_clean=no, -ac_cv_func_memcmp_clean=no)]) -test $ac_cv_func_memcmp_clean = no && LIBOBJS="$LIBOBJS memcmp.${ac_objext}" -AC_SUBST(LIBOBJS)dnl -]) - -AC_DEFUN(AC_FUNC_SELECT_ARGTYPES, -[AC_MSG_CHECKING([types of arguments for select()]) - AC_CACHE_VAL(ac_cv_func_select_arg234,dnl - [AC_CACHE_VAL(ac_cv_func_select_arg1,dnl - [AC_CACHE_VAL(ac_cv_func_select_arg5,dnl - [for ac_cv_func_select_arg234 in 'fd_set *' 'int *' 'void *'; do - for ac_cv_func_select_arg1 in 'int' 'size_t' 'unsigned long' 'unsigned'; do - for ac_cv_func_select_arg5 in 'struct timeval *' 'const struct timeval *'; do - AC_TRY_COMPILE(dnl -[#ifdef HAVE_SYS_TYPES_H -#include -#endif -#ifdef HAVE_SYS_TIME_H -#include -#endif -#ifdef HAVE_SYS_SELECT_H -#include -#endif -#ifdef HAVE_SYS_SOCKET_H -#include -#endif -extern select ($ac_cv_func_select_arg1,$ac_cv_func_select_arg234,$ac_cv_func_select_arg234,$ac_cv_func_select_arg234,$ac_cv_func_select_arg5);],,dnl - [ac_not_found=no ; break 3],ac_not_found=yes) - done - done - done - ])dnl AC_CACHE_VAL - ])dnl AC_CACHE_VAL - ])dnl AC_CACHE_VAL - if test "$ac_not_found" = yes; then - ac_cv_func_select_arg1=int - ac_cv_func_select_arg234='int *' - ac_cv_func_select_arg5='struct timeval *' - fi - AC_MSG_RESULT([$ac_cv_func_select_arg1,$ac_cv_func_select_arg234,$ac_cv_func_select_arg5]) - AC_DEFINE_UNQUOTED(SELECT_TYPE_ARG1,$ac_cv_func_select_arg1) - AC_DEFINE_UNQUOTED(SELECT_TYPE_ARG234,($ac_cv_func_select_arg234)) - AC_DEFINE_UNQUOTED(SELECT_TYPE_ARG5,($ac_cv_func_select_arg5)) -]) - - -dnl ### Checks for structure members - - -AC_DEFUN(AC_HEADER_TIME, -[AC_CACHE_CHECK([whether time.h and sys/time.h may both be included], - ac_cv_header_time, -[AC_TRY_COMPILE([#include -#include -#include ], -[struct tm *tp;], ac_cv_header_time=yes, ac_cv_header_time=no)]) -if test $ac_cv_header_time = yes; then - AC_DEFINE(TIME_WITH_SYS_TIME) -fi -]) - -AC_DEFUN(AC_STRUCT_TM, -[AC_CACHE_CHECK([whether struct tm is in sys/time.h or time.h], - ac_cv_struct_tm, -[AC_TRY_COMPILE([#include -#include ], -[struct tm *tp; tp->tm_sec;], - ac_cv_struct_tm=time.h, ac_cv_struct_tm=sys/time.h)]) -if test $ac_cv_struct_tm = sys/time.h; then - AC_DEFINE(TM_IN_SYS_TIME) -fi -]) - -AC_DEFUN(AC_STRUCT_TIMEZONE, -[AC_REQUIRE([AC_STRUCT_TM])dnl -AC_CACHE_CHECK([for tm_zone in struct tm], ac_cv_struct_tm_zone, -[AC_TRY_COMPILE([#include -#include <$ac_cv_struct_tm>], [struct tm tm; tm.tm_zone;], - ac_cv_struct_tm_zone=yes, ac_cv_struct_tm_zone=no)]) -if test "$ac_cv_struct_tm_zone" = yes; then - AC_DEFINE(HAVE_TM_ZONE) -else - AC_CACHE_CHECK(for tzname, ac_cv_var_tzname, -[AC_TRY_LINK( -changequote(<<, >>)dnl -<<#include -#ifndef tzname /* For SGI. */ -extern char *tzname[]; /* RS6000 and others reject char **tzname. */ -#endif>>, -changequote([, ])dnl -[atoi(*tzname);], ac_cv_var_tzname=yes, ac_cv_var_tzname=no)]) - if test $ac_cv_var_tzname = yes; then - AC_DEFINE(HAVE_TZNAME) - fi -fi -]) - -AC_DEFUN(AC_STRUCT_ST_BLOCKS, -[AC_CACHE_CHECK([for st_blocks in struct stat], ac_cv_struct_st_blocks, -[AC_TRY_COMPILE([#include -#include ], [struct stat s; s.st_blocks;], -ac_cv_struct_st_blocks=yes, ac_cv_struct_st_blocks=no)]) -if test $ac_cv_struct_st_blocks = yes; then - AC_DEFINE(HAVE_ST_BLOCKS) -else - LIBOBJS="$LIBOBJS fileblocks.${ac_objext}" -fi -AC_SUBST(LIBOBJS)dnl -]) - -AC_DEFUN(AC_STRUCT_ST_BLKSIZE, -[AC_CACHE_CHECK([for st_blksize in struct stat], ac_cv_struct_st_blksize, -[AC_TRY_COMPILE([#include -#include ], [struct stat s; s.st_blksize;], -ac_cv_struct_st_blksize=yes, ac_cv_struct_st_blksize=no)]) -if test $ac_cv_struct_st_blksize = yes; then - AC_DEFINE(HAVE_ST_BLKSIZE) -fi -]) - -AC_DEFUN(AC_STRUCT_ST_RDEV, -[AC_CACHE_CHECK([for st_rdev in struct stat], ac_cv_struct_st_rdev, -[AC_TRY_COMPILE([#include -#include ], [struct stat s; s.st_rdev;], -ac_cv_struct_st_rdev=yes, ac_cv_struct_st_rdev=no)]) -if test $ac_cv_struct_st_rdev = yes; then - AC_DEFINE(HAVE_ST_RDEV) -fi -]) - - -dnl ### Checks for compiler characteristics - - -AC_DEFUN(AC_C_CROSS, -[AC_OBSOLETE([$0], [; it has been merged into AC_PROG_CC])]) - -AC_DEFUN(AC_C_CHAR_UNSIGNED, -[AC_CACHE_CHECK(whether char is unsigned, ac_cv_c_char_unsigned, -[if test "$GCC" = yes; then - # GCC predefines this symbol on systems where it applies. -AC_EGREP_CPP(yes, -[#ifdef __CHAR_UNSIGNED__ - yes -#endif -], ac_cv_c_char_unsigned=yes, ac_cv_c_char_unsigned=no) -else -AC_TRY_RUN( -[/* volatile prevents gcc2 from optimizing the test away on sparcs. */ -#if !defined(__STDC__) || __STDC__ != 1 -#define volatile -#endif -main() { - volatile char c = 255; exit(c < 0); -}], ac_cv_c_char_unsigned=yes, ac_cv_c_char_unsigned=no) -fi]) -if test $ac_cv_c_char_unsigned = yes && test "$GCC" != yes; then - AC_DEFINE(__CHAR_UNSIGNED__) -fi -]) - -AC_DEFUN(AC_C_LONG_DOUBLE, -[AC_CACHE_CHECK(for long double, ac_cv_c_long_double, -[if test "$GCC" = yes; then - ac_cv_c_long_double=yes -else -AC_TRY_RUN([int main() { -/* The Stardent Vistra knows sizeof(long double), but does not support it. */ -long double foo = 0.0; -/* On Ultrix 4.3 cc, long double is 4 and double is 8. */ -exit(sizeof(long double) < sizeof(double)); }], -ac_cv_c_long_double=yes, ac_cv_c_long_double=no) -fi]) -if test $ac_cv_c_long_double = yes; then - AC_DEFINE(HAVE_LONG_DOUBLE) -fi -]) - -AC_DEFUN(AC_INT_16_BITS, -[AC_OBSOLETE([$0], [; instead use AC_CHECK_SIZEOF(int)])dnl -AC_MSG_CHECKING(whether int is 16 bits) -AC_TRY_RUN([main() { exit(sizeof(int) != 2); }], - [AC_MSG_RESULT(yes) - AC_DEFINE(INT_16_BITS)], AC_MSG_RESULT(no)) -]) - -AC_DEFUN(AC_LONG_64_BITS, -[AC_OBSOLETE([$0], [; instead use AC_CHECK_SIZEOF(long)])dnl -AC_MSG_CHECKING(whether long int is 64 bits) -AC_TRY_RUN([main() { exit(sizeof(long int) != 8); }], - [AC_MSG_RESULT(yes) - AC_DEFINE(LONG_64_BITS)], AC_MSG_RESULT(no)) -]) - -AC_DEFUN(AC_C_BIGENDIAN, -[AC_CACHE_CHECK(whether byte ordering is bigendian, ac_cv_c_bigendian, -[ac_cv_c_bigendian=unknown -# See if sys/param.h defines the BYTE_ORDER macro. -AC_TRY_COMPILE([#include -#include ], [ -#if !BYTE_ORDER || !BIG_ENDIAN || !LITTLE_ENDIAN - bogus endian macros -#endif], [# It does; now see whether it defined to BIG_ENDIAN or not. -AC_TRY_COMPILE([#include -#include ], [ -#if BYTE_ORDER != BIG_ENDIAN - not big endian -#endif], ac_cv_c_bigendian=yes, ac_cv_c_bigendian=no)]) -if test $ac_cv_c_bigendian = unknown; then -AC_TRY_RUN([main () { - /* Are we little or big endian? From Harbison&Steele. */ - union - { - long l; - char c[sizeof (long)]; - } u; - u.l = 1; - exit (u.c[sizeof (long) - 1] == 1); -}], ac_cv_c_bigendian=no, ac_cv_c_bigendian=yes) -fi]) -if test $ac_cv_c_bigendian = yes; then - AC_DEFINE(WORDS_BIGENDIAN) -fi -]) - -dnl Do nothing if the compiler accepts the inline keyword. -dnl Otherwise define inline to __inline__ or __inline if one of those work, -dnl otherwise define inline to be empty. -AC_DEFUN(AC_C_INLINE, -[AC_CACHE_CHECK([for inline], ac_cv_c_inline, -[ac_cv_c_inline=no -for ac_kw in inline __inline__ __inline; do - AC_TRY_COMPILE(, [} $ac_kw foo() {], [ac_cv_c_inline=$ac_kw; break]) -done -]) -case "$ac_cv_c_inline" in - inline | yes) ;; - no) AC_DEFINE(inline, ) ;; - *) AC_DEFINE_UNQUOTED(inline, $ac_cv_c_inline) ;; -esac -]) - -AC_DEFUN(AC_C_CONST, -[dnl This message is consistent in form with the other checking messages, -dnl and with the result message. -AC_CACHE_CHECK([for working const], ac_cv_c_const, -[AC_TRY_COMPILE(, -changequote(<<, >>)dnl -<< -/* Ultrix mips cc rejects this. */ -typedef int charset[2]; const charset x; -/* SunOS 4.1.1 cc rejects this. */ -char const *const *ccp; -char **p; -/* NEC SVR4.0.2 mips cc rejects this. */ -struct point {int x, y;}; -static struct point const zero = {0,0}; -/* AIX XL C 1.02.0.0 rejects this. - It does not let you subtract one const X* pointer from another in an arm - of an if-expression whose if-part is not a constant expression */ -const char *g = "string"; -ccp = &g + (g ? g-g : 0); -/* HPUX 7.0 cc rejects these. */ -++ccp; -p = (char**) ccp; -ccp = (char const *const *) p; -{ /* SCO 3.2v4 cc rejects this. */ - char *t; - char const *s = 0 ? (char *) 0 : (char const *) 0; - - *t++ = 0; -} -{ /* Someone thinks the Sun supposedly-ANSI compiler will reject this. */ - int x[] = {25, 17}; - const int *foo = &x[0]; - ++foo; -} -{ /* Sun SC1.0 ANSI compiler rejects this -- but not the above. */ - typedef const int *iptr; - iptr p = 0; - ++p; -} -{ /* AIX XL C 1.02.0.0 rejects this saying - "k.c", line 2.27: 1506-025 (S) Operand must be a modifiable lvalue. */ - struct s { int j; const int *ap[3]; }; - struct s *b; b->j = 5; -} -{ /* ULTRIX-32 V3.1 (Rev 9) vcc rejects this */ - const int foo = 10; -} ->>, -changequote([, ])dnl -ac_cv_c_const=yes, ac_cv_c_const=no)]) -if test $ac_cv_c_const = no; then - AC_DEFINE(const, ) -fi -]) - -AC_DEFUN(AC_C_STRINGIZE, [ -AC_REQUIRE([AC_PROG_CPP]) -AC_MSG_CHECKING([for preprocessor stringizing operator]) -AC_CACHE_VAL(ac_cv_c_stringize, -AC_EGREP_CPP([#teststring],[ -#define x(y) #y - -char *s = x(teststring); -], ac_cv_c_stringize=no, ac_cv_c_stringize=yes)) -if test "${ac_cv_c_stringize}" = yes -then - AC_DEFINE(HAVE_STRINGIZE) -fi -AC_MSG_RESULT([${ac_cv_c_stringize}]) -])dnl - -define(AC_ARG_ARRAY, -[errprint(__file__:__line__: [$0] has been removed; don't do unportable things with arguments -)m4exit(4)]) - -dnl Check the object extension used by the compiler: typically .o or -dnl .obj. If this is called, some other behaviour will change, -dnl determined by ac_objext. -AC_DEFUN(AC_OBJEXT, -[AC_MSG_CHECKING([for object suffix]) -AC_CACHE_VAL(ac_cv_objext, -[rm -f conftest* -echo 'int i = 1;' > conftest.$ac_ext -if AC_TRY_EVAL(ac_compile); then - for ac_file in conftest.*; do - case $ac_file in - *.c) ;; - *) ac_cv_objext=`echo $ac_file | sed -e s/conftest.//` ;; - esac - done -else - AC_MSG_ERROR([installation or configuration problem; compiler does not work]) -fi -rm -f conftest*]) -AC_MSG_RESULT($ac_cv_objext) -OBJEXT=$ac_cv_objext -ac_objext=$ac_cv_objext -AC_SUBST(OBJEXT)]) - -dnl Determine the linker flags (e.g. `-L' and `-l') for the Fortran 77 -dnl intrinsic and run-time libraries that are required to successfully -dnl link a Fortran 77 program or shared library. The output variable -dnl FLIBS is set to these flags. -dnl -dnl This macro is intended to be used in those situations when it is -dnl necessary to mix, e.g. C++ and Fortran 77, source code into a single -dnl program or shared library. -dnl -dnl For example, if object files from a C++ and Fortran 77 compiler must -dnl be linked together, then the C++ compiler/linker must be used for -dnl linking (since special C++-ish things need to happen at link time -dnl like calling global constructors, instantiating templates, enabling -dnl exception support, etc.). -dnl -dnl However, the Fortran 77 intrinsic and run-time libraries must be -dnl linked in as well, but the C++ compiler/linker doesn't know how to -dnl add these Fortran 77 libraries. Hence, the macro -dnl `AC_F77_LIBRARY_LDFLAGS' was created to determine these Fortran 77 -dnl libraries. -dnl -dnl This macro was packaged in its current form by Matthew D. Langston -dnl . However, nearly all of this macro -dnl came from the `OCTAVE_FLIBS' macro in `octave-2.0.13/aclocal.m4', -dnl and full credit should go to John W. Eaton for writing this -dnl extremely useful macro. Thank you John. -dnl -dnl AC_F77_LIBRARY_LDFLAGS() -AC_DEFUN(AC_F77_LIBRARY_LDFLAGS, -[AC_MSG_CHECKING([for Fortran 77 libraries]) -AC_REQUIRE([AC_PROG_F77]) -AC_REQUIRE([AC_CANONICAL_HOST]) -AC_CACHE_VAL(ac_cv_flibs, -[changequote(, )dnl -dnl Write a minimal program and compile it with -v. I don't know what -dnl to do if your compiler doesn't have -v... -echo " END" > conftest.f -foutput=`${F77} -v -o conftest conftest.f 2>&1` -dnl -dnl The easiest thing to do for xlf output is to replace all the commas -dnl with spaces. Try to only do that if the output is really from xlf, -dnl since doing that causes problems on other systems. -dnl -xlf_p=`echo $foutput | grep xlfentry` -if test -n "$xlf_p"; then - foutput=`echo $foutput | sed 's/,/ /g'` -fi -dnl -ld_run_path=`echo $foutput | \ - sed -n -e 's/^.*LD_RUN_PATH *= *\([^ ]*\).*/\1/p'` -dnl -dnl We are only supposed to find this on Solaris systems... -dnl Uh, the run path should be absolute, shouldn't it? -dnl -case "$ld_run_path" in - /*) - if test "$ac_cv_prog_gcc" = yes; then - ld_run_path="-Xlinker -R -Xlinker $ld_run_path" - else - ld_run_path="-R $ld_run_path" - fi - ;; - *) - ld_run_path= - ;; -esac -dnl -flibs= -lflags= -dnl -dnl If want_arg is set, we know we want the arg to be added to the list, -dnl so we don't have to examine it. -dnl -want_arg= -dnl -for arg in $foutput; do - old_want_arg=$want_arg - want_arg= -dnl -dnl None of the options that take arguments expect the argument to -dnl start with a -, so pretend we didn't see anything special. -dnl - if test -n "$old_want_arg"; then - case "$arg" in - -*) - old_want_arg= - ;; - esac - fi - case "$old_want_arg" in - '') - case $arg in - /*.a) - exists=false - for f in $lflags; do - if test x$arg = x$f; then - exists=true - fi - done - if $exists; then - arg= - else - lflags="$lflags $arg" - fi - ;; - -bI:*) - exists=false - for f in $lflags; do - if test x$arg = x$f; then - exists=true - fi - done - if $exists; then - arg= - else - if test "$ac_cv_prog_gcc" = yes; then - lflags="$lflags -Xlinker $arg" - else - lflags="$lflags $arg" - fi - fi - ;; - -lang* | -lcrt0.o | -lc | -lgcc) - arg= - ;; - -[lLR]) - want_arg=$arg - arg= - ;; - -[lLR]*) - exists=false - for f in $lflags; do - if test x$arg = x$f; then - exists=true - fi - done - if $exists; then - arg= - else - case "$arg" in - -lkernel32) - case "$canonical_host_type" in - *-*-cygwin*) - arg= - ;; - *) - lflags="$lflags $arg" - ;; - esac - ;; - -lm) - ;; - *) - lflags="$lflags $arg" - ;; - esac - fi - ;; - -u) - want_arg=$arg - arg= - ;; - -Y) - want_arg=$arg - arg= - ;; - *) - arg= - ;; - esac - ;; - -[lLR]) - arg="$old_want_arg $arg" - ;; - -u) - arg="-u $arg" - ;; - -Y) -dnl -dnl Should probably try to ensure unique directory options here too. -dnl This probably only applies to Solaris systems, and then will only -dnl work with gcc... -dnl - arg=`echo $arg | sed -e 's%^P,%%'` - SAVE_IFS=$IFS - IFS=: - list= - for elt in $arg; do - list="$list -L$elt" - done - IFS=$SAVE_IFS - arg="$list" - ;; - esac -dnl - if test -n "$arg"; then - flibs="$flibs $arg" - fi -done -if test -n "$ld_run_path"; then - flibs_result="$ld_run_path $flibs" -else - flibs_result="$flibs" -fi -changequote([, ])dnl -ac_cv_flibs="$flibs_result"]) -FLIBS="$ac_cv_flibs" -AC_SUBST(FLIBS)dnl -AC_MSG_RESULT($FLIBS) -]) - - -dnl ### Checks for operating system services - - -AC_DEFUN(AC_SYS_INTERPRETER, -[# Pull the hash mark out of the macro call to avoid m4 problems. -ac_msg="whether #! works in shell scripts" -AC_CACHE_CHECK($ac_msg, ac_cv_sys_interpreter, -[echo '#! /bin/cat -exit 69 -' > conftest -chmod u+x conftest -(SHELL=/bin/sh; export SHELL; ./conftest >/dev/null) -if test $? -ne 69; then - ac_cv_sys_interpreter=yes -else - ac_cv_sys_interpreter=no -fi -rm -f conftest]) -interpval="$ac_cv_sys_interpreter" -]) - -define(AC_HAVE_POUNDBANG, -[errprint(__file__:__line__: [$0 has been replaced by AC_SYS_INTERPRETER, taking no arguments -])m4exit(4)]) - -AC_DEFUN(AC_SYS_LONG_FILE_NAMES, -[AC_CACHE_CHECK(for long file names, ac_cv_sys_long_file_names, -[ac_cv_sys_long_file_names=yes -# Test for long file names in all the places we know might matter: -# . the current directory, where building will happen -# $prefix/lib where we will be installing things -# $exec_prefix/lib likewise -# eval it to expand exec_prefix. -# $TMPDIR if set, where it might want to write temporary files -# if $TMPDIR is not set: -# /tmp where it might want to write temporary files -# /var/tmp likewise -# /usr/tmp likewise -if test -n "$TMPDIR" && test -d "$TMPDIR" && test -w "$TMPDIR"; then - ac_tmpdirs="$TMPDIR" -else - ac_tmpdirs='/tmp /var/tmp /usr/tmp' -fi -for ac_dir in . $ac_tmpdirs `eval echo $prefix/lib $exec_prefix/lib` ; do - test -d $ac_dir || continue - test -w $ac_dir || continue # It is less confusing to not echo anything here. - (echo 1 > $ac_dir/conftest9012345) 2>/dev/null - (echo 2 > $ac_dir/conftest9012346) 2>/dev/null - val=`cat $ac_dir/conftest9012345 2>/dev/null` - if test ! -f $ac_dir/conftest9012345 || test "$val" != 1; then - ac_cv_sys_long_file_names=no - rm -f $ac_dir/conftest9012345 $ac_dir/conftest9012346 2>/dev/null - break - fi - rm -f $ac_dir/conftest9012345 $ac_dir/conftest9012346 2>/dev/null -done]) -if test $ac_cv_sys_long_file_names = yes; then - AC_DEFINE(HAVE_LONG_FILE_NAMES) -fi -]) - -AC_DEFUN(AC_SYS_RESTARTABLE_SYSCALLS, -[AC_CACHE_CHECK(for restartable system calls, ac_cv_sys_restartable_syscalls, -[AC_TRY_RUN( -[/* Exit 0 (true) if wait returns something other than -1, - i.e. the pid of the child, which means that wait was restarted - after getting the signal. */ -#include -#include -ucatch (isig) { } -main () { - int i = fork (), status; - if (i == 0) { sleep (3); kill (getppid (), SIGINT); sleep (3); exit (0); } - signal (SIGINT, ucatch); - status = wait(&i); - if (status == -1) wait(&i); - exit (status == -1); -} -], ac_cv_sys_restartable_syscalls=yes, ac_cv_sys_restartable_syscalls=no)]) -if test $ac_cv_sys_restartable_syscalls = yes; then - AC_DEFINE(HAVE_RESTARTABLE_SYSCALLS) -fi -]) - -AC_DEFUN(AC_PATH_X, -[AC_REQUIRE_CPP()dnl Set CPP; we run AC_PATH_X_DIRECT conditionally. -# If we find X, set shell vars x_includes and x_libraries to the -# paths, otherwise set no_x=yes. -# Uses ac_ vars as temps to allow command line to override cache and checks. -# --without-x overrides everything else, but does not touch the cache. -AC_MSG_CHECKING(for X) - -AC_ARG_WITH(x, [ --with-x use the X Window System]) -# $have_x is `yes', `no', `disabled', or empty when we do not yet know. -if test "x$with_x" = xno; then - # The user explicitly disabled X. - have_x=disabled -else - if test "x$x_includes" != xNONE && test "x$x_libraries" != xNONE; then - # Both variables are already set. - have_x=yes - else -AC_CACHE_VAL(ac_cv_have_x, -[# One or both of the vars are not set, and there is no cached value. -ac_x_includes=NO ac_x_libraries=NO -AC_PATH_X_XMKMF -AC_PATH_X_DIRECT -if test "$ac_x_includes" = NO || test "$ac_x_libraries" = NO; then - # Didn't find X anywhere. Cache the known absence of X. - ac_cv_have_x="have_x=no" -else - # Record where we found X for the cache. - ac_cv_have_x="have_x=yes \ - ac_x_includes=$ac_x_includes ac_x_libraries=$ac_x_libraries" -fi])dnl - fi - eval "$ac_cv_have_x" -fi # $with_x != no - -if test "$have_x" != yes; then - AC_MSG_RESULT($have_x) - no_x=yes -else - # If each of the values was on the command line, it overrides each guess. - test "x$x_includes" = xNONE && x_includes=$ac_x_includes - test "x$x_libraries" = xNONE && x_libraries=$ac_x_libraries - # Update the cache value to reflect the command line values. - ac_cv_have_x="have_x=yes \ - ac_x_includes=$x_includes ac_x_libraries=$x_libraries" - AC_MSG_RESULT([libraries $x_libraries, headers $x_includes]) -fi -]) - -dnl Internal subroutine of AC_PATH_X. -dnl Set ac_x_includes and/or ac_x_libraries. -AC_DEFUN(AC_PATH_X_XMKMF, -[rm -fr conftestdir -if mkdir conftestdir; then - cd conftestdir - # Make sure to not put "make" in the Imakefile rules, since we grep it out. - cat > Imakefile <<'EOF' -acfindx: - @echo 'ac_im_incroot="${INCROOT}"; ac_im_usrlibdir="${USRLIBDIR}"; ac_im_libdir="${LIBDIR}"' -EOF - if (xmkmf) >/dev/null 2>/dev/null && test -f Makefile; then - # GNU make sometimes prints "make[1]: Entering...", which would confuse us. - eval `${MAKE-make} acfindx 2>/dev/null | grep -v make` - # Open Windows xmkmf reportedly sets LIBDIR instead of USRLIBDIR. - for ac_extension in a so sl; do - if test ! -f $ac_im_usrlibdir/libX11.$ac_extension && - test -f $ac_im_libdir/libX11.$ac_extension; then - ac_im_usrlibdir=$ac_im_libdir; break - fi - done - # Screen out bogus values from the imake configuration. They are - # bogus both because they are the default anyway, and because - # using them would break gcc on systems where it needs fixed includes. - case "$ac_im_incroot" in - /usr/include) ;; - *) test -f "$ac_im_incroot/X11/Xos.h" && ac_x_includes="$ac_im_incroot" ;; - esac - case "$ac_im_usrlibdir" in - /usr/lib | /lib) ;; - *) test -d "$ac_im_usrlibdir" && ac_x_libraries="$ac_im_usrlibdir" ;; - esac - fi - cd .. - rm -fr conftestdir -fi -]) - -dnl Internal subroutine of AC_PATH_X. -dnl Set ac_x_includes and/or ac_x_libraries. -AC_DEFUN(AC_PATH_X_DIRECT, -[if test "$ac_x_includes" = NO; then - # Guess where to find include files, by looking for this one X11 .h file. - test -z "$x_direct_test_include" && x_direct_test_include=X11/Intrinsic.h - - # First, try using that file with no special directory specified. -AC_TRY_CPP([#include <$x_direct_test_include>], -[# We can compile using X headers with no special include directory. -ac_x_includes=], -[# Look for the header file in a standard set of common directories. -# Check X11 before X11Rn because it is often a symlink to the current release. - for ac_dir in \ - /usr/X11/include \ - /usr/X11R6/include \ - /usr/X11R5/include \ - /usr/X11R4/include \ - \ - /usr/include/X11 \ - /usr/include/X11R6 \ - /usr/include/X11R5 \ - /usr/include/X11R4 \ - \ - /usr/local/X11/include \ - /usr/local/X11R6/include \ - /usr/local/X11R5/include \ - /usr/local/X11R4/include \ - \ - /usr/local/include/X11 \ - /usr/local/include/X11R6 \ - /usr/local/include/X11R5 \ - /usr/local/include/X11R4 \ - \ - /usr/X386/include \ - /usr/x386/include \ - /usr/XFree86/include/X11 \ - \ - /usr/include \ - /usr/local/include \ - /usr/unsupported/include \ - /usr/athena/include \ - /usr/local/x11r5/include \ - /usr/lpp/Xamples/include \ - \ - /usr/openwin/include \ - /usr/openwin/share/include \ - ; \ - do - if test -r "$ac_dir/$x_direct_test_include"; then - ac_x_includes=$ac_dir - break - fi - done]) -fi # $ac_x_includes = NO - -if test "$ac_x_libraries" = NO; then - # Check for the libraries. - - test -z "$x_direct_test_library" && x_direct_test_library=Xt - test -z "$x_direct_test_function" && x_direct_test_function=XtMalloc - - # See if we find them without any special options. - # Don't add to $LIBS permanently. - ac_save_LIBS="$LIBS" - LIBS="-l$x_direct_test_library $LIBS" -AC_TRY_LINK(, [${x_direct_test_function}()], -[LIBS="$ac_save_LIBS" -# We can link X programs with no special library path. -ac_x_libraries=], -[LIBS="$ac_save_LIBS" -# First see if replacing the include by lib works. -# Check X11 before X11Rn because it is often a symlink to the current release. -for ac_dir in `echo "$ac_x_includes" | sed s/include/lib/` \ - /usr/X11/lib \ - /usr/X11R6/lib \ - /usr/X11R5/lib \ - /usr/X11R4/lib \ - \ - /usr/lib/X11 \ - /usr/lib/X11R6 \ - /usr/lib/X11R5 \ - /usr/lib/X11R4 \ - \ - /usr/local/X11/lib \ - /usr/local/X11R6/lib \ - /usr/local/X11R5/lib \ - /usr/local/X11R4/lib \ - \ - /usr/local/lib/X11 \ - /usr/local/lib/X11R6 \ - /usr/local/lib/X11R5 \ - /usr/local/lib/X11R4 \ - \ - /usr/X386/lib \ - /usr/x386/lib \ - /usr/XFree86/lib/X11 \ - \ - /usr/lib \ - /usr/local/lib \ - /usr/unsupported/lib \ - /usr/athena/lib \ - /usr/local/x11r5/lib \ - /usr/lpp/Xamples/lib \ - /lib/usr/lib/X11 \ - \ - /usr/openwin/lib \ - /usr/openwin/share/lib \ - ; \ -do -dnl Don't even attempt the hair of trying to link an X program! - for ac_extension in a so sl; do - if test -r $ac_dir/lib${x_direct_test_library}.$ac_extension; then - ac_x_libraries=$ac_dir - break 2 - fi - done -done]) -fi # $ac_x_libraries = NO -]) - -dnl Find additional X libraries, magic flags, etc. -AC_DEFUN(AC_PATH_XTRA, -[AC_REQUIRE([AC_PATH_X])dnl -if test "$no_x" = yes; then - # Not all programs may use this symbol, but it does not hurt to define it. - AC_DEFINE(X_DISPLAY_MISSING) - X_CFLAGS= X_PRE_LIBS= X_LIBS= X_EXTRA_LIBS= -else - if test -n "$x_includes"; then - X_CFLAGS="$X_CFLAGS -I$x_includes" - fi - - # It would also be nice to do this for all -L options, not just this one. - if test -n "$x_libraries"; then - X_LIBS="$X_LIBS -L$x_libraries" -dnl FIXME banish uname from this macro! - # For Solaris; some versions of Sun CC require a space after -R and - # others require no space. Words are not sufficient . . . . - case "`(uname -sr) 2>/dev/null`" in - "SunOS 5"*) - AC_MSG_CHECKING(whether -R must be followed by a space) - ac_xsave_LIBS="$LIBS"; LIBS="$LIBS -R$x_libraries" - AC_TRY_LINK(, , ac_R_nospace=yes, ac_R_nospace=no) - if test $ac_R_nospace = yes; then - AC_MSG_RESULT(no) - X_LIBS="$X_LIBS -R$x_libraries" - else - LIBS="$ac_xsave_LIBS -R $x_libraries" - AC_TRY_LINK(, , ac_R_space=yes, ac_R_space=no) - if test $ac_R_space = yes; then - AC_MSG_RESULT(yes) - X_LIBS="$X_LIBS -R $x_libraries" - else - AC_MSG_RESULT(neither works) - fi - fi - LIBS="$ac_xsave_LIBS" - esac - fi - - # Check for system-dependent libraries X programs must link with. - # Do this before checking for the system-independent R6 libraries - # (-lICE), since we may need -lsocket or whatever for X linking. - - if test "$ISC" = yes; then - X_EXTRA_LIBS="$X_EXTRA_LIBS -lnsl_s -linet" - else - # Martyn.Johnson@cl.cam.ac.uk says this is needed for Ultrix, if the X - # libraries were built with DECnet support. And karl@cs.umb.edu says - # the Alpha needs dnet_stub (dnet does not exist). - AC_CHECK_LIB(dnet, dnet_ntoa, [X_EXTRA_LIBS="$X_EXTRA_LIBS -ldnet"]) - if test $ac_cv_lib_dnet_dnet_ntoa = no; then - AC_CHECK_LIB(dnet_stub, dnet_ntoa, - [X_EXTRA_LIBS="$X_EXTRA_LIBS -ldnet_stub"]) - fi - - # msh@cis.ufl.edu says -lnsl (and -lsocket) are needed for his 386/AT, - # to get the SysV transport functions. - # chad@anasazi.com says the Pyramis MIS-ES running DC/OSx (SVR4) - # needs -lnsl. - # The nsl library prevents programs from opening the X display - # on Irix 5.2, according to dickey@clark.net. - AC_CHECK_FUNC(gethostbyname) - if test $ac_cv_func_gethostbyname = no; then - AC_CHECK_LIB(nsl, gethostbyname, X_EXTRA_LIBS="$X_EXTRA_LIBS -lnsl") - fi - - # lieder@skyler.mavd.honeywell.com says without -lsocket, - # socket/setsockopt and other routines are undefined under SCO ODT - # 2.0. But -lsocket is broken on IRIX 5.2 (and is not necessary - # on later versions), says simon@lia.di.epfl.ch: it contains - # gethostby* variants that don't use the nameserver (or something). - # -lsocket must be given before -lnsl if both are needed. - # We assume that if connect needs -lnsl, so does gethostbyname. - AC_CHECK_FUNC(connect) - if test $ac_cv_func_connect = no; then - AC_CHECK_LIB(socket, connect, X_EXTRA_LIBS="-lsocket $X_EXTRA_LIBS", , - $X_EXTRA_LIBS) - fi - - # gomez@mi.uni-erlangen.de says -lposix is necessary on A/UX. - AC_CHECK_FUNC(remove) - if test $ac_cv_func_remove = no; then - AC_CHECK_LIB(posix, remove, X_EXTRA_LIBS="$X_EXTRA_LIBS -lposix") - fi - - # BSDI BSD/OS 2.1 needs -lipc for XOpenDisplay. - AC_CHECK_FUNC(shmat) - if test $ac_cv_func_shmat = no; then - AC_CHECK_LIB(ipc, shmat, X_EXTRA_LIBS="$X_EXTRA_LIBS -lipc") - fi - fi - - # Check for libraries that X11R6 Xt/Xaw programs need. - ac_save_LDFLAGS="$LDFLAGS" - test -n "$x_libraries" && LDFLAGS="$LDFLAGS -L$x_libraries" - # SM needs ICE to (dynamically) link under SunOS 4.x (so we have to - # check for ICE first), but we must link in the order -lSM -lICE or - # we get undefined symbols. So assume we have SM if we have ICE. - # These have to be linked with before -lX11, unlike the other - # libraries we check for below, so use a different variable. - # --interran@uluru.Stanford.EDU, kb@cs.umb.edu. - AC_CHECK_LIB(ICE, IceConnectionNumber, - [X_PRE_LIBS="$X_PRE_LIBS -lSM -lICE"], , $X_EXTRA_LIBS) - LDFLAGS="$ac_save_LDFLAGS" - -fi -AC_SUBST(X_CFLAGS)dnl -AC_SUBST(X_PRE_LIBS)dnl -AC_SUBST(X_LIBS)dnl -AC_SUBST(X_EXTRA_LIBS)dnl -]) - -dnl The old Cygwin32 macro is deprecated. -AC_DEFUN(AC_CYGWIN32, -[AC_OBSOLETE([$0], [; instead use AC_CYGWIN])dnl -AC_CYGWIN]) - -dnl Check for Cygwin. This is a way to set the right value for -dnl EXEEXT. -AC_DEFUN(AC_CYGWIN, -[AC_CACHE_CHECK(for Cygwin environment, ac_cv_cygwin, -[AC_TRY_COMPILE(,[ -#ifndef __CYGWIN__ -#define __CYGWIN__ __CYGWIN32__ -#endif -return __CYGWIN__;], -ac_cv_cygwin=yes, ac_cv_cygwin=no) -rm -f conftest*]) -CYGWIN= -test "$ac_cv_cygwin" = yes && CYGWIN=yes]) - -dnl Check for mingw32. This is another way to set the right value for -dnl EXEEXT. -AC_DEFUN(AC_MINGW32, -[AC_CACHE_CHECK(for mingw32 environment, ac_cv_mingw32, -[AC_TRY_COMPILE(,[return __MINGW32__;], -ac_cv_mingw32=yes, ac_cv_mingw32=no) -rm -f conftest*]) -MINGW32= -test "$ac_cv_mingw32" = yes && MINGW32=yes]) - -dnl Check for the extension used for executables. This knows that we -dnl add .exe for Cygwin or mingw32. Otherwise, it compiles a test -dnl executable. If this is called, the executable extensions will be -dnl automatically used by link commands run by the configure script. -AC_DEFUN(AC_EXEEXT, -[AC_REQUIRE([AC_CYGWIN]) -AC_REQUIRE([AC_MINGW32]) -AC_MSG_CHECKING([for executable suffix]) -AC_CACHE_VAL(ac_cv_exeext, -[if test "$CYGWIN" = yes || test "$MINGW32" = yes; then - ac_cv_exeext=.exe -else - rm -f conftest* - echo 'int main () { return 0; }' > conftest.$ac_ext - ac_cv_exeext= - if AC_TRY_EVAL(ac_link); then - for file in conftest.*; do - case $file in - *.c | *.o | *.obj) ;; - *) ac_cv_exeext=`echo $file | sed -e s/conftest//` ;; - esac - done - else - AC_MSG_ERROR([installation or configuration problem: compiler cannot create executables.]) - fi - rm -f conftest* - test x"${ac_cv_exeext}" = x && ac_cv_exeext=no -fi]) -EXEEXT="" -test x"${ac_cv_exeext}" != xno && EXEEXT=${ac_cv_exeext} -AC_MSG_RESULT(${ac_cv_exeext}) -dnl Setting ac_exeext will implicitly change the ac_link command. -ac_exeext=$EXEEXT -AC_SUBST(EXEEXT)]) - - -dnl ### Checks for UNIX variants -dnl These are kludges which should be replaced by a single POSIX check. -dnl They aren't cached, to discourage their use. - - -AC_DEFUN(AC_AIX, -[AC_BEFORE([$0], [AC_TRY_COMPILE])dnl -AC_BEFORE([$0], [AC_TRY_RUN])dnl -AC_MSG_CHECKING(for AIX) -AC_EGREP_CPP(yes, -[#ifdef _AIX - yes -#endif -], [AC_MSG_RESULT(yes); AC_DEFINE(_ALL_SOURCE)], AC_MSG_RESULT(no)) -]) - -AC_DEFUN(AC_MINIX, -[AC_BEFORE([$0], [AC_TRY_COMPILE])dnl -AC_BEFORE([$0], [AC_TRY_RUN])dnl -AC_CHECK_HEADER(minix/config.h, MINIX=yes, MINIX=) -if test "$MINIX" = yes; then - AC_DEFINE(_POSIX_SOURCE) - AC_DEFINE(_POSIX_1_SOURCE, 2) - AC_DEFINE(_MINIX) -fi -]) - -AC_DEFUN(AC_ISC_POSIX, -[AC_REQUIRE([AC_PROG_CC])dnl -AC_BEFORE([$0], [AC_TRY_COMPILE])dnl -AC_BEFORE([$0], [AC_TRY_RUN])dnl -AC_MSG_CHECKING(for POSIXized ISC) -if test -d /etc/conf/kconfig.d && - grep _POSIX_VERSION [/usr/include/sys/unistd.h] >/dev/null 2>&1 -then - AC_MSG_RESULT(yes) - ISC=yes # If later tests want to check for ISC. - AC_DEFINE(_POSIX_SOURCE) - if test "$GCC" = yes; then - CC="$CC -posix" - else - CC="$CC -Xp" - fi -else - AC_MSG_RESULT(no) - ISC= -fi -]) - -AC_DEFUN(AC_XENIX_DIR, -[AC_OBSOLETE([$0], [; instead use AC_HEADER_DIRENT])dnl -AC_REQUIRE([AC_DIR_HEADER])dnl -AC_MSG_CHECKING(for Xenix) -AC_EGREP_CPP(yes, -[#if defined(M_XENIX) && !defined(M_UNIX) - yes -#endif -], [AC_MSG_RESULT(yes); XENIX=yes], [AC_MSG_RESULT(no); XENIX=]) -if test "$XENIX" = yes; then - # Make sure -ldir precedes -lx. - test $ac_header_dirent = dirent.h && LIBS="-ldir $LIBS" - LIBS="$LIBS -lx" -fi -]) - -AC_DEFUN(AC_DYNIX_SEQ, -[AC_OBSOLETE([$0], [; instead use AC_FUNC_GETMNTENT])dnl -AC_CHECK_LIB(seq, getmntent, LIBS="-lseq $LIBS") -]) - -AC_DEFUN(AC_IRIX_SUN, -[AC_OBSOLETE([$0], [; instead use AC_FUNC_GETMNTENT or AC_CHECK_LIB(sun, getpwnam)])dnl -AC_CHECK_LIB(sun, getmntent, LIBS="-lsun $LIBS") -]) - -AC_DEFUN(AC_SCO_INTL, -[AC_OBSOLETE([$0], [; instead use AC_FUNC_STRFTIME])dnl -AC_CHECK_LIB(intl, strftime, LIBS="-lintl $LIBS") -]) diff --git a/build/autoconf/altoptions.m4 b/build/autoconf/altoptions.m4 deleted file mode 100644 index 3105de00659..00000000000 --- a/build/autoconf/altoptions.m4 +++ /dev/null @@ -1,72 +0,0 @@ -dnl This Source Code Form is subject to the terms of the Mozilla Public -dnl License, v. 2.0. If a copy of the MPL was not distributed with this -dnl file, You can obtain one at http://mozilla.org/MPL/2.0/. - -dnl altoptions.m4 - An alternative way of specifying command-line options. -dnl These macros are needed to support a menu-based configurator. -dnl This file also includes the macro, AM_READ_MYCONFIG, for reading -dnl the 'myconfig.m4' file. - -dnl Send comments, improvements, bugs to Steve Lamm (slamm@netscape.com). - - -dnl MOZ_ARG_ENABLE_BOOL( NAME, HELP, IF-YES [, IF-NO [, ELSE]]) -dnl MOZ_ARG_DISABLE_BOOL( NAME, HELP, IF-NO [, IF-YES [, ELSE]]) -dnl MOZ_ARG_ENABLE_STRING( NAME, HELP, IF-SET [, ELSE]) -dnl MOZ_ARG_WITH_BOOL( NAME, HELP, IF-YES [, IF-NO [, ELSE]) -dnl MOZ_ARG_WITH_STRING( NAME, HELP, IF-SET [, ELSE]) -dnl MOZ_READ_MYCONFIG() - Read in 'myconfig.sh' file - -define([MOZ_DIVERSION_ARGS], 12) - -AC_DEFUN([MOZ_ARG],[dnl -AC_DIVERT_PUSH(MOZ_DIVERSION_ARGS)dnl - '$1', -AC_DIVERT_POP()dnl -]) -AC_DEFUN([MOZ_AC_ARG_ENABLE],[MOZ_ARG([--enable-]translit([$1],[_],[-]))AC_ARG_ENABLE([$1], [$2], [$3], [$4])]) -AC_DEFUN([MOZ_AC_ARG_WITH],[MOZ_ARG([--with-]translit([$1],[_],[-]))AC_ARG_WITH([$1], [$2], [$3], [$4])]) - -dnl MOZ_TWO_STRING_TEST(NAME, VAL, STR1, IF-STR1, STR2, IF-STR2 [, ELSE]) -AC_DEFUN([MOZ_TWO_STRING_TEST], -[if test "[$2]" = "[$3]"; then - ifelse([$4], , :, [$4]) - elif test "[$2]" = "[$5]"; then - ifelse([$6], , :, [$6]) - else - ifelse([$7], , - [AC_MSG_ERROR([Option, [$1], does not take an argument ([$2]).])], - [$7]) - fi]) - -dnl MOZ_ARG_ENABLE_BOOL(NAME, HELP, IF-YES [, IF-NO [, ELSE]]) -AC_DEFUN([MOZ_ARG_ENABLE_BOOL], -[MOZ_AC_ARG_ENABLE([$1], [$2], - [MOZ_TWO_STRING_TEST([$1], [$enableval], yes, [$3], no, [$4])], - [$5])]) - -dnl MOZ_ARG_DISABLE_BOOL(NAME, HELP, IF-NO [, IF-YES [, ELSE]]) -AC_DEFUN([MOZ_ARG_DISABLE_BOOL], -[MOZ_AC_ARG_ENABLE([$1], [$2], - [MOZ_TWO_STRING_TEST([$1], [$enableval], no, [$3], yes, [$4])], - [$5])]) - -dnl MOZ_ARG_ENABLE_STRING(NAME, HELP, IF-SET [, ELSE]) -AC_DEFUN([MOZ_ARG_ENABLE_STRING], -[MOZ_AC_ARG_ENABLE([$1], [$2], [$3], [$4])]) - -dnl MOZ_ARG_WITH_BOOL(NAME, HELP, IF-YES [, IF-NO [, ELSE]) -AC_DEFUN([MOZ_ARG_WITH_BOOL], -[MOZ_AC_ARG_WITH([$1], [$2], - [MOZ_TWO_STRING_TEST([$1], [$withval], yes, [$3], no, [$4])], - [$5])]) - -dnl MOZ_ARG_WITH_STRING(NAME, HELP, IF-SET [, ELSE]) -AC_DEFUN([MOZ_ARG_WITH_STRING], -[MOZ_AC_ARG_WITH([$1], [$2], [$3], [$4])]) - -dnl MOZ_READ_MYCONFIG() - Read in 'myconfig.sh' file -AC_DEFUN([MOZ_READ_MOZCONFIG], -[AC_REQUIRE([AC_INIT_BINSH])dnl -. $OLD_CONFIGURE_VARS -]) diff --git a/build/autoconf/autoconf.m4 b/build/autoconf/autoconf.m4 deleted file mode 100644 index dde59ab380f..00000000000 --- a/build/autoconf/autoconf.m4 +++ /dev/null @@ -1,28 +0,0 @@ -dnl Driver that loads the Autoconf macro files. -dnl Requires GNU m4. -dnl This file is part of Autoconf. -dnl Copyright (C) 1994 Free Software Foundation, Inc. -dnl -dnl This program is free software; you can redistribute it and/or modify -dnl it under the terms of the GNU General Public License as published by -dnl the Free Software Foundation; either version 2, or (at your option) -dnl any later version. -dnl -dnl This program is distributed in the hope that it will be useful, -dnl but WITHOUT ANY WARRANTY; without even the implied warranty of -dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -dnl GNU General Public License for more details. -dnl -dnl You should have received a copy of the GNU General Public License -dnl along with this program; if not, write to the Free Software -dnl Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA -dnl 02111-1307, USA. -dnl -dnl Written by David MacKenzie. -dnl -include(acgeneral.m4)dnl -builtin(include, acspecific.m4)dnl -builtin(include, acoldnames.m4)dnl -dnl Do not sinclude acsite.m4 here, because it may not be installed -dnl yet when Autoconf is frozen. -dnl Do not sinclude ./aclocal.m4 here, to prevent it from being frozen. diff --git a/build/autoconf/autoconf.sh b/build/autoconf/autoconf.sh deleted file mode 100644 index ceb8a25b00a..00000000000 --- a/build/autoconf/autoconf.sh +++ /dev/null @@ -1,158 +0,0 @@ -#! @SHELL@ -# autoconf -- create `configure' using m4 macros -# Copyright (C) 1992, 1993, 1994, 1996 Free Software Foundation, Inc. - -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2, or (at your option) -# any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA -# 02111-1307, USA. - -# If given no args, create `configure' from template file `configure.in'. -# With one arg, create a configure script on standard output from -# the given template file. - -usage="\ -Usage: autoconf [-h] [--help] [-m dir] [--macrodir=dir] - [-l dir] [--localdir=dir] [--version] [template-file]" - -# NLS nuisances. -# Only set these to C if already set. These must not be set unconditionally -# because not all systems understand e.g. LANG=C (notably SCO). -# Fixing LC_MESSAGES prevents Solaris sh from translating var values in `set'! -# Non-C LC_CTYPE values break the ctype check. -if test "${LANG+set}" = set; then LANG=C; export LANG; fi -if test "${LC_ALL+set}" = set; then LC_ALL=C; export LC_ALL; fi -if test "${LC_MESSAGES+set}" = set; then LC_MESSAGES=C; export LC_MESSAGES; fi -if test "${LC_CTYPE+set}" = set; then LC_CTYPE=C; export LC_CTYPE; fi - -: ${AC_MACRODIR=@datadir@} -: ${M4=@M4@} -: ${AWK=@AWK@} -case "${M4}" in -/*) # Handle the case that m4 has moved since we were configured. - # It may have been found originally in a build directory. - test -f "${M4}" || M4=m4 ;; -esac - -: ${TMPDIR=/tmp} -tmpout=${TMPDIR}/acout.$$ -localdir= -show_version=no - -while test $# -gt 0 ; do - case "${1}" in - -h | --help | --h* ) - echo "${usage}" 1>&2; exit 0 ;; - --localdir=* | --l*=* ) - localdir="`echo \"${1}\" | sed -e 's/^[^=]*=//'`" - shift ;; - -l | --localdir | --l*) - shift - test $# -eq 0 && { echo "${usage}" 1>&2; exit 1; } - localdir="${1}" - shift ;; - --macrodir=* | --m*=* ) - AC_MACRODIR="`echo \"${1}\" | sed -e 's/^[^=]*=//'`" - shift ;; - -m | --macrodir | --m* ) - shift - test $# -eq 0 && { echo "${usage}" 1>&2; exit 1; } - AC_MACRODIR="${1}" - shift ;; - --version | --v* ) - show_version=yes; shift ;; - -- ) # Stop option processing - shift; break ;; - - ) # Use stdin as input. - break ;; - -* ) - echo "${usage}" 1>&2; exit 1 ;; - * ) - break ;; - esac -done - -if test $show_version = yes; then - version=`sed -n 's/define.AC_ACVERSION.[ ]*\([0-9.]*\).*/\1/p' \ - $AC_MACRODIR/acgeneral.m4` - echo "Autoconf version $version" - exit 0 -fi - -case $# in - 0) infile=configure.in ;; - 1) infile="$1" ;; - *) echo "$usage" >&2; exit 1 ;; -esac - -trap 'rm -f $tmpin $tmpout; exit 1' 1 2 15 - -tmpin=${TMPDIR}/acin.$$ # Always set this, to avoid bogus errors from some rm's. -if test z$infile = z-; then - infile=$tmpin - cat > $infile -elif test ! -r "$infile"; then - echo "autoconf: ${infile}: No such file or directory" >&2 - exit 1 -fi - -if test -n "$localdir"; then - use_localdir="-I$localdir -DAC_LOCALDIR=$localdir" -else - use_localdir= -fi - -# Use the frozen version of Autoconf if available. -r= f= -# Some non-GNU m4's don't reject the --help option, so give them /dev/null. -case `$M4 --help < /dev/null 2>&1` in -*reload-state*) test -r $AC_MACRODIR/autoconf.m4f && { r=--reload f=f; } ;; -*traditional*) ;; -*) echo Autoconf requires GNU m4 1.1 or later >&2; rm -f $tmpin; exit 1 ;; -esac - -$M4 -I$AC_MACRODIR $use_localdir $r autoconf.m4$f $infile > $tmpout || - { rm -f $tmpin $tmpout; exit 2; } - -# You could add your own prefixes to pattern if you wanted to check for -# them too, e.g. pattern='\(AC_\|ILT_\)', except that UNIX sed doesn't do -# alternation. -pattern="AC_" - -status=0 -if grep "^[^#]*${pattern}" $tmpout > /dev/null 2>&1; then - echo "autoconf: Undefined macros:" >&2 - sed -n "s/^[^#]*\\(${pattern}[_A-Za-z0-9]*\\).*/\\1/p" $tmpout | - while read macro; do - grep -n "^[^#]*$macro" $infile /dev/null - test $? -eq 1 && echo >&2 "***BUG in Autoconf--please report*** $macro" - done | sort -u >&2 - status=1 -fi - -if test $# -eq 0; then - echo "This case should not be reached." - exit 1 -fi - -# Put the real line numbers into the output to make config.log more helpful. -$AWK ' -/__oline__/ { printf "%d:", NR + 1 } - { print } -' $tmpout | sed ' -/__oline__/s/^\([0-9][0-9]*\):\(.*\)__oline__/\2\1/ -' - -rm -f $tmpout - -exit $status diff --git a/build/autoconf/config.status.m4 b/build/autoconf/config.status.m4 deleted file mode 100644 index e1f3a433777..00000000000 --- a/build/autoconf/config.status.m4 +++ /dev/null @@ -1,162 +0,0 @@ -dnl This Source Code Form is subject to the terms of the Mozilla Public -dnl License, v. 2.0. If a copy of the MPL was not distributed with this -dnl file, You can obtain one at http://mozilla.org/MPL/2.0/. - -dnl For use in AC_SUBST replacement -define([MOZ_DIVERSION_SUBST], 11) - -dnl Replace AC_SUBST to store values in a format suitable for python. -dnl The necessary comma after the tuple can't be put here because it -dnl can mess around with things like: -dnl AC_SOMETHING(foo,AC_SUBST(),bar) -define([AC_SUBST], -[ifdef([AC_SUBST_SET_$1], [m4_fatal([Cannot use AC_SUBST and AC_SUBST_SET on the same variable ($1)])], -[ifdef([AC_SUBST_LIST_$1], [m4_fatal([Cannot use AC_SUBST and AC_SUBST_LIST on the same variable ($1)])], -[ifdef([AC_SUBST_$1], , -[define([AC_SUBST_$1], )dnl -AC_DIVERT_PUSH(MOZ_DIVERSION_SUBST)dnl - (''' $1 ''', r''' [$]$1 ''') -AC_DIVERT_POP()dnl -])])])])]) - -dnl Like AC_SUBST, but makes the value available as a set in python, -dnl with values got from the value of the environment variable, split on -dnl whitespaces. -define([AC_SUBST_SET], -[ifdef([AC_SUBST_$1], [m4_fatal([Cannot use AC_SUBST and AC_SUBST_SET on the same variable ($1)])], -[ifdef([AC_SUBST_LIST_$1], [m4_fatal([Cannot use AC_SUBST_LIST and AC_SUBST_SET on the same variable ($1)])], -[ifdef([AC_SUBST_SET_$1], , -[define([AC_SUBST_SET_$1], )dnl -AC_DIVERT_PUSH(MOZ_DIVERSION_SUBST)dnl - (''' $1 ''', unique_list(split(r''' [$]$1 '''))) -AC_DIVERT_POP()dnl -])])])])]) - -dnl Like AC_SUBST, but makes the value available as a list in python, -dnl with values got from the value of the environment variable, split on -dnl whitespaces. -define([AC_SUBST_LIST], -[ifdef([AC_SUBST_$1], [m4_fatal([Cannot use AC_SUBST and AC_SUBST_LIST on the same variable ($1)])], -[ifdef([AC_SUBST_SET_$1], [m4_fatal([Cannot use AC_SUBST_SET and AC_SUBST_LIST on the same variable ($1)])], -[ifdef([AC_SUBST_LIST_$1], , -[define([AC_SUBST_LIST_$1], )dnl -AC_DIVERT_PUSH(MOZ_DIVERSION_SUBST)dnl - (''' $1 ''', list(split(r''' [$]$1 '''))) -AC_DIVERT_POP()dnl -])])])])]) - -dnl Ignore AC_SUBSTs for variables we don't have use for but that autoconf -dnl itself exports. -define([AC_SUBST_CFLAGS], ) -define([AC_SUBST_CPPFLAGS], ) -define([AC_SUBST_CXXFLAGS], ) -define([AC_SUBST_FFLAGS], ) -define([AC_SUBST_DEFS], ) -define([AC_SUBST_LDFLAGS], ) -define([AC_SUBST_LIBS], ) -define([AC_SUBST_exec_prefix], ) -define([AC_SUBST_prefix], ) -define([AC_SUBST_datadir], ) -define([AC_SUBST_libdir], ) -define([AC_SUBST_includedir], ) - -dnl Wrap AC_DEFINE to store values in a format suitable for python. -dnl autoconf's AC_DEFINE still needs to be used to fill confdefs.h, -dnl which is #included during some compile checks. -dnl The necessary comma after the tuple can't be put here because it -dnl can mess around with things like: -dnl AC_SOMETHING(foo,AC_DEFINE(),bar) -define([_MOZ_AC_DEFINE], defn([AC_DEFINE])) -define([AC_DEFINE], -[cat >> confdefs.pytmp <<\EOF - (''' $1 ''', ifelse($#, 2, [r''' $2 '''], $#, 3, [r''' $2 '''], ' 1 ')) -EOF -ifelse($#, 2, _MOZ_AC_DEFINE([$1], [$2]), $#, 3, _MOZ_AC_DEFINE([$1], [$2], [$3]),_MOZ_AC_DEFINE([$1]))dnl -]) - -dnl Wrap AC_DEFINE_UNQUOTED to store values in a format suitable for -dnl python. -define([_MOZ_AC_DEFINE_UNQUOTED], defn([AC_DEFINE_UNQUOTED])) -define([AC_DEFINE_UNQUOTED], -[cat >> confdefs.pytmp <>>)dnl -echo creating $CONFIG_STATUS - -cat > $CONFIG_STATUS <> $CONFIG_STATUS - rm confdefs.pytmp -fi -rm -f confdefs.h - -cat >> $CONFIG_STATUS <<\EOF -] - -substs = [ -EOF - -dnl The MOZ_DIVERSION_SUBST output diversion contains AC_SUBSTs, in the -dnl expected format, but lacks the final comma (see above). -sed 's/$/,/' >> $CONFIG_STATUS <> $CONFIG_STATUS -done - -cat >> $CONFIG_STATUS <<\EOF -] - -flags = [ -undivert(MOZ_DIVERSION_ARGS)dnl -] -EOF - -changequote([, ]) -]) - -define([m4_fatal],[ -errprint([$1 -]) -m4exit(1) -]) - -define([AC_OUTPUT], [ifelse($#_$1, 1_, [MOZ_CREATE_CONFIG_STATUS() -MOZ_RUN_CONFIG_STATUS()], -[m4_fatal([Use CONFIGURE_SUBST_FILES in moz.build files to create substituted files.])] -)]) - -define([AC_CONFIG_HEADER], -[m4_fatal([Use CONFIGURE_DEFINE_FILES in moz.build files to produce header files.]) -]) diff --git a/build/autoconf/hooks.m4 b/build/autoconf/hooks.m4 deleted file mode 100644 index 84d58205c10..00000000000 --- a/build/autoconf/hooks.m4 +++ /dev/null @@ -1,31 +0,0 @@ -dnl This Source Code Form is subject to the terms of the Mozilla Public -dnl License, v. 2.0. If a copy of the MPL was not distributed with this -dnl file, You can obtain one at http://mozilla.org/MPL/2.0/. - -dnl Wrap AC_INIT_PREPARE to add the above trap. -define([_MOZ_AC_INIT_PREPARE], defn([AC_INIT_PREPARE])) -define([AC_INIT_PREPARE], -[_MOZ_AC_INIT_PREPARE($1) - -test "x$prefix" = xNONE && prefix=$ac_default_prefix -# Let make expand exec_prefix. -test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' -]) - -dnl Print error messages in config.log as well as stderr -define([AC_MSG_ERROR], -[{ echo "configure: error: $1" 1>&2; echo "configure: error: $1" 1>&5; exit 1; }]) - -dnl Divert AC_TRY_COMPILER to make ac_cv_prog_*_works actually cached. -dnl This will allow to just skip the test when python configure has set -dnl the value for us. But since ac_cv_prog_*_cross is calculated at the same -dnl time, and has a different meaning as in python configure, we only want to -dnl use its value to display whether a cross-compile is happening. We forbid -dnl configure tests that would rely on ac_cv_prog_*_cross autoconf meaning -dnl (being able to execute the product of compilation), which are already bad -dnl for cross compiles anyways, so it's a win to get rid of them. -define([_MOZ_AC_TRY_COMPILER], defn([AC_TRY_COMPILER])) -define([AC_TRY_COMPILER], [AC_CACHE_VAL($2, _MOZ_AC_TRY_COMPILER($1, $2, $3))]) - -define([AC_TRY_RUN], [m4_fatal([AC_TRY_RUN is forbidden])]) -define([AC_CHECK_FILE], [m4_fatal([AC_CHECK_FILE is forbidden])]) diff --git a/build/docs/snap.rst b/build/docs/snap.rst index ec756b41655..5afc61e414e 100644 --- a/build/docs/snap.rst +++ b/build/docs/snap.rst @@ -70,6 +70,23 @@ Store`` automatic connection will not happen and this can result in a broken state. Inspecting ``snap connections firefox`` using a store-installed snap should get your an accurate list that you can replicate. +Cross-compilation +================= + +There is now support for cross-compilation for both ``armhf`` and ``arm64``. +To produce cross-compiled version locally: + + - follow the steps above for building, except you need to pass + ``--build-for=ARCH`` to ``snapcraft`` + - this needs ``snapcraft`` of at least v8.x + - make sure you uncomment the ``##CROSS-COMPILATION##`` lines due to Launchpad + limitations + +Builds on Treeherder as well as Try pushes are also available using +cross-compilation. Tests on Treeherder will also be supported when ARM-based +workers will be available, confere `Bug 1855463 +`_. + What CI coverage ================ diff --git a/build/moz.configure/init.configure b/build/moz.configure/init.configure index 09b7eb06416..4c66007e73b 100644 --- a/build/moz.configure/init.configure +++ b/build/moz.configure/init.configure @@ -65,11 +65,12 @@ set_config("DIST", build_environment.dist) option(env="MOZ_AUTOMATION", help="Enable options for automated builds") set_config("MOZ_AUTOMATION", depends_if("MOZ_AUTOMATION")(lambda x: True)) - -option(env="OLD_CONFIGURE", nargs=1, help="Path to the old configure script") - option(env="MOZCONFIG", nargs=1, help="Mozconfig location") +option(env="MOZ_AUTOMATION_MOZCONFIG", help="Indicates mozconfig from automated builds") +set_config( + "MOZ_AUTOMATION_MOZCONFIG", depends_if("MOZ_AUTOMATION_MOZCONFIG")(lambda x: True) +) # Read user mozconfig # ============================================================== @@ -78,34 +79,11 @@ option(env="MOZCONFIG", nargs=1, help="Mozconfig location") # be called when --help is passed, and the mozconfig wouldn't be read. -@depends("MOZCONFIG", "OLD_CONFIGURE", build_environment, "--help") +@depends("MOZCONFIG", build_environment, "--help") @imports(_from="mozbuild.mozconfig", _import="MozconfigLoader") @imports(_from="mozboot.mozconfig", _import="find_mozconfig") @imports("os") -def mozconfig(mozconfig, old_configure, build_env, help): - # Don't read the mozconfig for the js configure (yay backwards - # compatibility) - # While the long term goal is that js and top-level use the same configure - # and the same overall setup, including the possibility to use mozconfigs, - # figuring out what we want to do wrt mozconfig vs. command line and - # environment variable is not a clear-cut case, and it's more important to - # fix the immediate problem mozconfig causes to js developers by - # "temporarily" returning to the previous behavior of not loading the - # mozconfig for the js configure. - # Separately to the immediate problem for js developers, there is also the - # need to not load a mozconfig when running js configure as a subconfigure. - # Unfortunately, there is no direct way to tell whether the running - # configure is the js configure. The indirect way is to look at the - # OLD_CONFIGURE path, which points to js/src/old-configure. - # I expect we'll have figured things out for mozconfigs well before - # old-configure dies. - if ( - old_configure - and os.path.dirname(os.path.abspath(old_configure[0])).endswith("/js/src") - or (mozconfig and mozconfig[0] == os.devnull) - ): - return {"path": None} - +def mozconfig(mozconfig, build_env, help): topsrcdir = build_env.topsrcdir loader = MozconfigLoader(topsrcdir) mozconfig = mozconfig[0] if mozconfig else None @@ -155,6 +133,7 @@ def help_shell(help, shell): shell = help_shell | shell +set_config("SHELL", shell) # Python 3 @@ -235,10 +214,6 @@ def mozconfig_options(mozconfig, early_options, automation, help): for key, (_, value) in mozconfig["env"]["modified"].items(): add(key, value) os.environ[key] = value - for key, value in mozconfig["vars"]["added"].items(): - add(key, value) - for key, (_, value) in mozconfig["vars"]["modified"].items(): - add(key, value) @depends(build_environment, "--help") @@ -1326,6 +1301,17 @@ set_config("prefix", depends("--prefix")(lambda prefix: prefix[0])) # Unlike autoconf, we don't offer these as a customisation point. set_config("exec_prefix", depends("--prefix")(lambda prefix: prefix[0])) set_config("datadir", depends("--prefix")(lambda prefix: f"{prefix[0]}/share")) +set_config("bindir", depends("--prefix")(lambda prefix: f"{prefix[0]}/bin")) +set_config("sbindir", depends("--prefix")(lambda prefix: f"{prefix[0]}/sbin")) +set_config("infodir", depends("--prefix")(lambda prefix: f"{prefix[0]}/info")) +set_config("libexec", depends("--prefix")(lambda prefix: f"{prefix[0]}/libexec")) +set_config("localstatedir", depends("--prefix")(lambda prefix: f"{prefix[0]}/var")) +set_config("sharedstatedir", depends("--prefix")(lambda prefix: f"{prefix[0]}/com")) +set_config("sysconfdir", depends("--prefix")(lambda prefix: f"{prefix[0]}/etc")) +set_config("mandir", depends("--prefix")(lambda prefix: f"{prefix[0]}/man")) +set_config("oldincludedir", depends("--prefix")(lambda prefix: f"{prefix[0]}/include")) +set_config("top_srcdir", build_environment.topsrcdir) +set_config("program_transform_name", "s,x,x,") option( "--includedir", diff --git a/build/moz.configure/old.configure b/build/moz.configure/old.configure deleted file mode 100644 index 3101a9f0981..00000000000 --- a/build/moz.configure/old.configure +++ /dev/null @@ -1,395 +0,0 @@ -# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - - -m4 = check_prog( - "M4", - ( - "gm4", - "m4", - ), - paths=prefer_mozillabuild_path, -) - - -@depends(mozconfig) -def prepare_mozconfig(mozconfig): - if mozconfig["path"]: - items = {} - for key, value in mozconfig["vars"]["added"].items(): - items[key] = (value, "added") - for key, (old, value) in mozconfig["vars"]["modified"].items(): - items[key] = (value, "modified") - for t in ("env", "vars"): - for key in mozconfig[t]["removed"].keys(): - items[key] = (None, "removed " + t) - return items - - -@depends("OLD_CONFIGURE", build_project) -def old_configure(old_configure, build_project): - if not old_configure: - die("The OLD_CONFIGURE environment variable must be set") - - # os.path.abspath in the sandbox will ensure forward slashes on Windows, - # which is actually necessary because this path actually ends up literally - # as $0, and backslashes there breaks autoconf's detection of the source - # directory. - old_configure = os.path.abspath(old_configure[0]) - if build_project == "js": - old_configure_dir = os.path.dirname(old_configure) - if not old_configure_dir.endswith("/js/src"): - old_configure = os.path.join( - old_configure_dir, "js", "src", os.path.basename(old_configure) - ) - return old_configure - - -@depends(prepare_mozconfig) -@imports(_from="__builtin__", _import="open") -@imports(_from="__builtin__", _import="print") -@imports(_from="mozbuild.shellutil", _import="quote") -def prepare_configure(mozconfig): - assignments = {} - - with open("old-configure.vars", "w") as out: - log.debug("Injecting the following to old-configure:") - - def inject(command): - print(command, file=out) # noqa Python 2vs3 - log.debug("| %s", command) - - if mozconfig: - inject("# start of mozconfig values") - for key, (value, action) in sorted(mozconfig.items()): - if action.startswith("removed "): - inject("unset %s # from %s" % (key, action[len("removed ") :])) - else: - inject("%s=%s # %s" % (key, quote(value), action)) - assignments[key] = value - - inject("# end of mozconfig values") - - return namespace(assignments=assignments) - - -@template -def old_configure_options(*options): - for opt in options: - option(opt, nargs="*", help="Help missing for old configure options") - - @dependable - def all_options(): - return list(options) - - return depends(all_options, *options) - - -@old_configure_options( - "--cache-file", - "--x-includes", - "--x-libraries", -) -def prepare_configure_options(all_options, *options): - # old-configure only supports the options listed in @old_configure_options - # so we don't need to pass it every single option we've been passed. Only - # the ones that are not supported by python configure need to. - options = [ - value.format(name) - for name, value in zip(all_options, options) - if value.origin != "default" - ] - - return namespace(options=options, all_options=all_options) - - -@template -def old_configure_for(old_configure_path, extra_env=None): - if extra_env is None: - extra_env = dependable(None) - - @depends( - prepare_configure, - prepare_configure_options, - "--prefix", - "--includedir", - "--libdir", - prefer_mozillabuild_path, - altered_path, - extra_env, - build_environment, - old_configure_path, - awk, - m4, - shell, - "--cache-file", - ) - @imports(_from="__builtin__", _import="compile") - @imports(_from="__builtin__", _import="open") - @imports(_from="__builtin__", _import="OSError") - @imports("glob") - @imports("itertools") - @imports("logging") - @imports("os") - @imports("re") - @imports("subprocess") - @imports("sys") - @imports(_from="mozbuild.shellutil", _import="quote") - @imports(_from="mozbuild.shellutil", _import="split") - @imports(_from="tempfile", _import="NamedTemporaryFile") - @imports(_from="subprocess", _import="CalledProcessError") - @imports(_from="__builtin__", _import="exec") - def old_configure( - prepare_configure, - prepare_configure_options, - prefix, - includedir, - libdir, - prefer_mozillabuild_path, - altered_path, - extra_env, - build_env, - old_configure, - awk, - m4, - shell, - cache_file_option, - ): - if altered_path: - path = altered_path - else: - path = os.pathsep.join(prefer_mozillabuild_path) - - cxx = prepare_configure.assignments.get("CXX") - cc = prepare_configure.assignments.get("CC") - - if cc and cxx: - if cache_file_option: - config_cache = cache_file_option[0] - else: - config_cache = "config.cache" - - if os.path.exists(config_cache): - remove = False - with open(config_cache, "r") as cfg_cache: - cxx_pattern = re.compile( - r"^ac_cv_prog_CXX=\${ac_cv_prog_CXX='(.*)'}$" - ) - cc_pattern = re.compile(r"^ac_cv_prog_CC=\${ac_cv_prog_CC='(.*)'}$") - for line in cfg_cache: - m = cc_pattern.match(line) - if m and m.group(1) != cc: - remove = True - break - m = cxx_pattern.match(line) - if m and m.group(1) != cxx: - remove = True - break - - if remove: - log.info("invalidating config.cache") - os.remove(config_cache) - - refresh = True - if os.path.exists(old_configure): - mtime = os.path.getmtime(old_configure) - aclocal = os.path.join(build_env.topsrcdir, "build", "autoconf", "*.m4") - for input in itertools.chain( - ( - old_configure + ".in", - os.path.join(os.path.dirname(old_configure), "aclocal.m4"), - ), - glob.iglob(aclocal), - ): - if os.path.getmtime(input) > mtime: - break - else: - refresh = False - - if refresh: - autoconf = os.path.join( - build_env.topsrcdir, "build", "autoconf", "autoconf.sh" - ) - log.info("Refreshing %s with %s", old_configure, autoconf) - env = dict(os.environ) - env["M4"] = m4 - env["AWK"] = awk - env["AC_MACRODIR"] = os.path.join(build_env.topsrcdir, "build", "autoconf") - env["PATH"] = path - - try: - script = subprocess.check_output( - [ - shell, - autoconf, - "--localdir=%s" % os.path.dirname(old_configure), - old_configure + ".in", - ], - # Fix the working directory, so that when m4 is called, that - # includes of relative paths are deterministically resolved - # relative to the directory containing old-configure. - cwd=os.path.dirname(old_configure), - env=env, - ) - except CalledProcessError as exc: - die("autoconf exited with return code {}".format(exc.returncode)) - - if not script: - die( - "Generated old-configure is empty! Check that your autoconf 2.13 program works!" - ) - - # Make old-configure append to config.log, where we put our own log. - # This could be done with a m4 macro, but it's way easier this way - script = script.replace(b">./config.log", b">>${CONFIG_LOG=./config.log}") - - with NamedTemporaryFile( - mode="wb", - prefix=os.path.basename(old_configure), - dir=os.path.dirname(old_configure), - delete=False, - ) as fh: - fh.write(script) - - try: - os.rename(fh.name, old_configure) - except OSError: - try: - # Likely the file already existed (on Windows). Retry after removing it. - os.remove(old_configure) - os.rename(fh.name, old_configure) - except OSError as e: - die("Failed re-creating old-configure: %s" % e.message) - - old_configure_options = { - "prefix": prefix[0], - "includedir": includedir[0], - "libdir": libdir[0], - } - cmd = ( - [shell, old_configure] - + prepare_configure_options.options - + [f"--{k}={v}" for k, v in old_configure_options.items()] - ) - - env = dict(os.environ) - - # For debugging purpose, in case it's not what we'd expect. - log.debug("Running %s", quote(*cmd)) - - # Our logging goes to config.log, the same file old.configure uses. - # We can't share the handle on the file, so close it. - logger = logging.getLogger("moz.configure") - config_log = None - for handler in logger.handlers: - if isinstance(handler, logging.FileHandler): - config_log = handler - config_log.close() - logger.removeHandler(config_log) - env["CONFIG_LOG"] = config_log.baseFilename - log_size = os.path.getsize(config_log.baseFilename) - break - - env["PATH"] = path - - if extra_env: - env.update(extra_env) - - env["OLD_CONFIGURE_VARS"] = os.path.join( - build_env.topobjdir, "old-configure.vars" - ) - proc = subprocess.Popen( - cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env - ) - while True: - line = proc.stdout.readline() - if not line: - break - log.info(line.rstrip()) - - ret = proc.wait() - if ret: - with log.queue_debug(): - if config_log: - with open(config_log.baseFilename, "r") as fh: - fh.seek(log_size) - for line in fh: - log.debug(line.rstrip()) - log.error("old-configure failed") - sys.exit(ret) - - if config_log: - # Create a new handler in append mode - handler = logging.FileHandler(config_log.baseFilename, mode="a", delay=True) - handler.setFormatter(config_log.formatter) - logger.addHandler(handler) - - raw_config = { - "split": split, - "unique_list": unique_list, - } - with open("config.data", "r") as fh: - code = compile(fh.read(), "config.data", "exec") - exec(code, raw_config) - - # Ensure all the flags known to old-configure appear in the - # @old_configure_options above. - all_options = set(prepare_configure_options.all_options) - for flag in raw_config["flags"]: - if flag not in all_options: - die( - "Missing option in `@old_configure_options` in %s: %s", - __file__, - flag, - ) - - # If the code execution above fails, we want to keep the file around for - # debugging. - os.remove("config.data") - - return namespace( - **{ - c: [ - (k[1:-1], v[1:-1] if isinstance(v, str) else v) - for k, v in raw_config[c] - # Eventually we'll want to filter out all lowercase keys. (bug 1869127) - # For now, we only filter out the most problematic one that - # we know is unused. - if k != " target_cpu " - ] - for c in ("substs", "defines") - } - ) - - return old_configure - - -old_configure = old_configure_for(old_configure) -set_config("OLD_CONFIGURE_SUBSTS", old_configure.substs) -set_config("OLD_CONFIGURE_DEFINES", old_configure.defines) - - -# Assuming no other option is declared after this function, handle the -# env options that were injected by mozconfig_options by creating dummy -# Option instances and having the sandbox's CommandLineHelper handle -# them. We only do so for options that haven't been declared so far, -# which should be a proxy for the options that old-configure handles -# and that we don't know anything about. -@depends("--help") -@imports("__sandbox__") -@imports(_from="mozbuild.configure.options", _import="Option") -def remaining_mozconfig_options(_): - helper = __sandbox__._helper - for arg in list(helper): - if helper._origins[arg] != "mozconfig": - continue - name = arg.split("=", 1)[0] - if name.isupper() and name not in __sandbox__._options: - option = Option(env=name, nargs="*", help=name) - helper.handle(option) - - -# Please do not add anything after remaining_mozconfig_options() diff --git a/build/mozconfig.artifact.automation b/build/mozconfig.artifact.automation index fed5866b68e..feb1b2d3a88 100644 --- a/build/mozconfig.artifact.automation +++ b/build/mozconfig.artifact.automation @@ -1,6 +1,6 @@ # Common options for artifact builds to set automation steps. # This gets included before mozconfig.automation. -MOZ_AUTOMATION_BUILD_SYMBOLS=0 +export MOZ_AUTOMATION_BUILD_SYMBOLS=0 MOZ_AUTOMATION_PACKAGE_GENERATED_SOURCES=0 MOZ_AUTOMATION_ARTIFACT_BUILDS=1 diff --git a/build/rust/mozbuild/generate_buildconfig.py b/build/rust/mozbuild/generate_buildconfig.py index 2a3e6437154..42a784bbd48 100644 --- a/build/rust/mozbuild/generate_buildconfig.py +++ b/build/rust/mozbuild/generate_buildconfig.py @@ -107,7 +107,9 @@ def generate(output): # Write out some useful strings from the buildconfig. output.write(generate_string("MOZ_MACBUNDLE_ID")) + output.write(generate_string("MOZ_APP_BASENAME")) output.write(generate_string("MOZ_APP_NAME")) + output.write(generate_string("MOZ_APP_VENDOR")) # Write out some useful booleans from the buildconfig. output.write(generate_bool("MOZ_FOLD_LIBS")) diff --git a/configure.py b/configure.py index c7759d64328..4750a91eb46 100644 --- a/configure.py +++ b/configure.py @@ -73,9 +73,6 @@ def main(argv): config = {} - if "OLD_CONFIGURE" not in os.environ: - os.environ["OLD_CONFIGURE"] = os.path.join(base_dir, "old-configure") - sandbox = ConfigureSandbox(config, os.environ, argv) if not sandbox._help: @@ -144,28 +141,21 @@ def main(argv): buildstatus("START_configure config.status") logging.getLogger("moz.configure").info("Creating config.status") - - old_js_configure_substs = config.pop("OLD_JS_CONFIGURE_SUBSTS", None) - old_js_configure_defines = config.pop("OLD_JS_CONFIGURE_DEFINES", None) try: - if old_js_configure_substs or old_js_configure_defines: - js_config = config.copy() - pwd = os.getcwd() - try: - os.makedirs("js/src", exist_ok=True) - os.chdir("js/src") - js_config["OLD_CONFIGURE_SUBSTS"] = old_js_configure_substs - js_config["OLD_CONFIGURE_DEFINES"] = old_js_configure_defines - # The build system frontend expects $objdir/js/src/config.status - # to have $objdir/js/src as topobjdir. - # We want forward slashes on all platforms. - js_config["TOPOBJDIR"] += "/js/src" - ret = config_status(js_config, execute=False) - if ret: - return ret - finally: - os.chdir(pwd) - + js_config = config.copy() + pwd = os.getcwd() + try: + os.makedirs("js/src", exist_ok=True) + os.chdir("js/src") + # The build system frontend expects $objdir/js/src/config.status + # to have $objdir/js/src as topobjdir. + # We want forward slashes on all platforms. + js_config["TOPOBJDIR"] += "/js/src" + ret = config_status(js_config, execute=False) + if ret: + return ret + finally: + os.chdir(pwd) return config_status(config) finally: buildstatus("END_configure config.status") @@ -217,17 +207,11 @@ def config_status(config, execute=True): "TOPSRCDIR", "TOPOBJDIR", "CONFIG_STATUS_DEPS", - "OLD_CONFIGURE_SUBSTS", - "OLD_CONFIGURE_DEFINES", ) } - for k, v in config["OLD_CONFIGURE_SUBSTS"]: - sanitized_config["substs"][k] = sanitize_config(v) sanitized_config["defines"] = { k: sanitize_config(v) for k, v in config["DEFINES"].items() } - for k, v in config["OLD_CONFIGURE_DEFINES"]: - sanitized_config["defines"][k] = sanitize_config(v) sanitized_config["topsrcdir"] = config["TOPSRCDIR"] sanitized_config["topobjdir"] = config["TOPOBJDIR"] sanitized_config["mozconfig"] = config.get("MOZCONFIG") diff --git a/devtools/client/debugger/src/components/QuickOpenModal.js b/devtools/client/debugger/src/components/QuickOpenModal.js index 2f2c6fb79b1..fb9a5a36323 100644 --- a/devtools/client/debugger/src/components/QuickOpenModal.js +++ b/devtools/client/debugger/src/components/QuickOpenModal.js @@ -54,6 +54,8 @@ export class QuickOpenModal extends Component { // Put it on the class so it can be retrieved in tests static UPDATE_RESULTS_THROTTLE = 100; + #willUnmountCalled = false; + constructor(props) { super(props); this.state = { results: null, selectedIndex: 0 }; @@ -113,6 +115,10 @@ export class QuickOpenModal extends Component { } } + componentWillUnmount() { + this.#willUnmountCalled = true; + } + closeModal = () => { this.props.closeQuickOpen(); }; @@ -209,27 +215,37 @@ export class QuickOpenModal extends Component { ); }; - updateResults = throttle(query => { - if (this.isGotoQuery()) { - return; - } + updateResults = throttle(async query => { + try { + if (this.isGotoQuery()) { + return; + } - if (query == "" && !this.isShortcutQuery()) { - this.showTopSources(); - return; - } + if (query == "" && !this.isShortcutQuery()) { + this.showTopSources(); + return; + } - if (this.isSymbolSearch()) { - this.searchSymbols(query); - return; - } + if (this.isSymbolSearch()) { + await this.searchSymbols(query); + return; + } - if (this.isShortcutQuery()) { - this.searchShortcuts(query); - return; - } + if (this.isShortcutQuery()) { + this.searchShortcuts(query); + return; + } - this.searchSources(query); + this.searchSources(query); + } catch (e) { + // Due to throttling this might get scheduled after the component and the + // toolbox are destroyed. + if (this.#willUnmountCalled) { + console.warn("Throttled QuickOpen.updateResults failed", e); + } else { + throw e; + } + } }, QuickOpenModal.UPDATE_RESULTS_THROTTLE); setModifier = item => { diff --git a/devtools/client/inspector/rules/models/element-style.js b/devtools/client/inspector/rules/models/element-style.js index a2fb8aa3a41..08f245ce32b 100644 --- a/devtools/client/inspector/rules/models/element-style.js +++ b/devtools/client/inspector/rules/models/element-style.js @@ -141,10 +141,10 @@ class ElementStyle { this._maybeAddRule(entry, existingRules); } - // Store a list of all pseudo-element types found in the matching rules. + // Store a list of all (non-inherited) pseudo-element types found in the matching rules. this.pseudoElementTypes = new Set(); for (const rule of this.rules) { - if (rule.pseudoElement) { + if (rule.pseudoElement && !rule.inherited) { this.pseudoElementTypes.add(rule.pseudoElement); } } @@ -228,11 +228,17 @@ class ElementStyle { } /** - * Put pseudo elements in front of others. + * Put non inherited pseudo elements in front of others rules. */ _sortRulesForPseudoElement() { this.rules = this.rules.sort((a, b) => { - return (a.pseudoElement || "z") > (b.pseudoElement || "z"); + if ( + !a.inherited === !b.inherited && + !!a.pseudoElement !== !!b.pseudoElement + ) { + return (a.pseudoElement || "z") > (b.pseudoElement || "z") ? 1 : -1; + } + return 0; }); } @@ -499,21 +505,38 @@ class ElementStyle { * @returns Boolean */ _hasHigherPriorityThanEarlierProp(computedProp, earlierProp) { + if (!earlierProp) { + return false; + } + + if (computedProp.priority !== "important") { + return false; + } + + const rule = computedProp.textProp.rule; + const earlierRule = earlierProp.textProp.rule; + + // for only consider rules applying to the same node. + if (rule.inherited !== earlierRule.inherited) { + return false; + } + + // only consider rules applying on the same (inherited) pseudo element (e.g. ::details-content), + // or rules both not applying to pseudo elements + if (rule.pseudoElement !== earlierRule.pseudoElement) { + return false; + } + + // At this point, the computed prop is important, and it applies to the same element + // (or pseudo element) than the earlier prop. return ( - earlierProp && - computedProp.priority === "important" && - (earlierProp.priority !== "important" || - // Even if the earlier property was important, if the current rule is in a layer - // it will take precedence, unless the earlier property rule was in the same layer, - // or if the earlier declaration is in the style attribute (https://www.w3.org/TR/css-cascade-5/#style-attr). - (computedProp.textProp.rule?.isInLayer() && - computedProp.textProp.rule.isInDifferentLayer( - earlierProp.textProp.rule - ) && - earlierProp.textProp.rule.domRule.type !== ELEMENT_STYLE)) && - // For !important only consider rules applying to the same parent node. - computedProp.textProp.rule.inherited == - earlierProp.textProp.rule.inherited + earlierProp.priority !== "important" || + // Even if the earlier property was important, if the current rule is in a layer + // it will take precedence, unless the earlier property rule was in the same layer… + (rule?.isInLayer() && + rule.isInDifferentLayer(earlierRule) && + // … or if the earlier declaration is in the style attribute (https://www.w3.org/TR/css-cascade-5/#style-attr). + earlierRule.domRule.type !== ELEMENT_STYLE) ); } @@ -584,6 +607,7 @@ class ElementStyle { } const isNestedDeclarations = rule.domRule.isNestedDeclarations; + const isInherited = !!rule.inherited; // Style rules must be considered only when they have selectors that match the node. // When renaming a selector, the unmatched rule lingers in the Rule view, but it no @@ -599,14 +623,22 @@ class ElementStyle { // impossible, for example with ::selection or ::first-line). // Loosening the strict check on matched selectors ensures these declarations // participate in the algorithm below to mark them as overridden. - const isPseudoElementRule = - rule.pseudoElement !== "" && rule.pseudoElement === pseudo; + const isMatchingPseudoElementRule = + rule.pseudoElement !== "" && + rule.pseudoElement === pseudo && + // Inherited pseudo element rules don't appear in the "Pseudo elements" section, + // so they should be considered style rules. + !isInherited; + const isInheritedPseudoElementRule = + rule.pseudoElement !== "" && isInherited; const isElementStyle = rule.domRule.type === ELEMENT_STYLE; const filterCondition = isNestedDeclarations || - (pseudo === "" ? isStyleRule || isElementStyle : isPseudoElementRule); + (pseudo && isMatchingPseudoElementRule) || + (pseudo === "" && + (isStyleRule || isElementStyle || isInheritedPseudoElementRule)); // Collect all relevant CSS declarations (aka TextProperty instances). if (filterCondition) { diff --git a/devtools/client/inspector/rules/models/rule.js b/devtools/client/inspector/rules/models/rule.js index d0289ee620b..368b77cec30 100644 --- a/devtools/client/inspector/rules/models/rule.js +++ b/devtools/client/inspector/rules/models/rule.js @@ -53,11 +53,11 @@ class Rule { this.compatibilityIssues = null; this.matchedSelectorIndexes = options.matchedSelectorIndexes || []; - this.pseudoElement = options.pseudoElement || ""; this.isSystem = options.isSystem; this.isUnmatched = options.isUnmatched || false; this.darkColorScheme = options.darkColorScheme; this.inherited = options.inherited || null; + this.pseudoElement = options.pseudoElement || ""; this.keyframes = options.keyframes || null; this.userAdded = options.rule.userAdded; @@ -135,6 +135,9 @@ class Rule { if (this.inherited.id) { eltText += "#" + this.inherited.id; } + if (CssLogic.ELEMENT_BACKED_PSEUDO_ELEMENTS.has(this.pseudoElement)) { + eltText += this.pseudoElement; + } this._inheritedSource = STYLE_INSPECTOR_L10N.getFormatStr( "rule.inheritedFrom", eltText diff --git a/devtools/client/inspector/rules/rules.js b/devtools/client/inspector/rules/rules.js index fc0c7f67de3..0a84d107cd5 100644 --- a/devtools/client/inspector/rules/rules.js +++ b/devtools/client/inspector/rules/rules.js @@ -1411,12 +1411,11 @@ CssRuleView.prototype = { _createEditors() { // Run through the current list of rules, attaching // their editors in order. Create editors if needed. + let lastInherited = null; let lastInheritedSource = ""; - let lastKeyframes = null; - let seenPseudoElement = false; let seenNormalElement = false; let seenSearchTerm = false; - let container = null; + const containers = new Map(); if (!this._elementStyle.rules) { return Promise.resolve(); @@ -1443,8 +1442,14 @@ CssRuleView.prototype = { } } + const isNonInheritedPseudo = !!rule.pseudoElement && !rule.inherited; + // Only print header for this element if there are pseudo elements - if (seenPseudoElement && !seenNormalElement && !rule.pseudoElement) { + if ( + containers.has(PSEUDO_ELEMENTS_CONTAINER_ID) && + !seenNormalElement && + !rule.pseudoElement + ) { seenNormalElement = true; const div = this.styleDocument.createElementNS(HTML_NS, "div"); div.className = RULE_VIEW_HEADER_CLASSNAME; @@ -1453,8 +1458,19 @@ CssRuleView.prototype = { this.element.appendChild(div); } - const inheritedSource = rule.inherited; - if (inheritedSource && inheritedSource !== lastInheritedSource) { + const { inherited, inheritedSource } = rule; + // We need to check both `inherited` (a NodeFront) and `inheritedSource` (string), + // as element-backed pseudo element rules (e.g. `::details-content`) can have the same + // `inherited` property as a regular rule (e.g. on `
    `), but the element is + // to be considered as a child of the binding element. + // e.g. we want to have + // This element + // Inherited by details::details-content + // Inherited by details + if ( + inherited && + (inherited !== lastInherited || inheritedSource !== lastInheritedSource) + ) { const div = this.styleDocument.createElementNS(HTML_NS, "div"); div.classList.add( RULE_VIEW_HEADER_CLASSNAME, @@ -1463,30 +1479,45 @@ CssRuleView.prototype = { div.setAttribute("role", "heading"); div.setAttribute("aria-level", "3"); div.textContent = rule.inheritedSource; + lastInherited = inherited; lastInheritedSource = inheritedSource; this.element.appendChild(div); } - if (!seenPseudoElement && rule.pseudoElement) { - seenPseudoElement = true; - container = this.createExpandableContainer( - this.pseudoElementLabel, - PSEUDO_ELEMENTS_CONTAINER_ID, - true - ); - } - const keyframes = rule.keyframes; - if (keyframes && keyframes !== lastKeyframes) { - lastKeyframes = keyframes; - container = this.createExpandableContainer( - rule.keyframesName, - `keyframes-container-${keyframes.name}` - ); + + let containerKey = null; + + // Don't display inherited pseudo element rules (e.g. ::details-content) inside + // the pseudo element container + if (isNonInheritedPseudo) { + containerKey = PSEUDO_ELEMENTS_CONTAINER_ID; + if (!containers.has(containerKey)) { + containers.set( + containerKey, + this.createExpandableContainer( + this.pseudoElementLabel, + containerKey, + true + ) + ); + } + } else if (keyframes) { + containerKey = keyframes; + if (!containers.has(containerKey)) { + containers.set( + containerKey, + this.createExpandableContainer( + rule.keyframesName, + `keyframes-container-${keyframes.name}` + ) + ); + } } rule.editor.element.setAttribute("role", "article"); - if (container && (rule.pseudoElement || keyframes)) { + const container = containers.get(containerKey); + if (container) { container.appendChild(rule.editor.element); } else { this.element.appendChild(rule.editor.element); diff --git a/devtools/client/inspector/rules/test/browser_part2.toml b/devtools/client/inspector/rules/test/browser_part2.toml index 12881a9b601..6f5b0e96731 100644 --- a/devtools/client/inspector/rules/test/browser_part2.toml +++ b/devtools/client/inspector/rules/test/browser_part2.toml @@ -55,6 +55,11 @@ support-files = [ "!/devtools/client/shared/test/highlighter-test-actor.js", "!/devtools/client/webconsole/test/browser/shared-head.js", ] +prefs = [ + # This pref has to be set for all tests since calls to InspectorUtils.getCSSPseudoElementNames + # is cached and the cache is populated by other tests before the one that is checking ::details-content + "layout.css.details-content.enabled=true", +] ["browser_rules_css-compatibility-add-rename-rule.js"] @@ -149,6 +154,8 @@ fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and ["browser_rules_inherited-custom-properties.js"] +["browser_rules_inherited-element-backed-pseudo-elements.js"] + ["browser_rules_inherited-properties_01.js"] ["browser_rules_inherited-properties_02.js"] diff --git a/devtools/client/inspector/rules/test/browser_rules_inherited-element-backed-pseudo-elements.js b/devtools/client/inspector/rules/test/browser_rules_inherited-element-backed-pseudo-elements.js new file mode 100644 index 00000000000..0a37f573738 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_inherited-element-backed-pseudo-elements.js @@ -0,0 +1,313 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that inherited element-backed pseudo element rules are properly displayed in +// the Rules view and that declarations are properly overridden. + +const TEST_URI = ` + +
    + + Top-level summary +
    + nested summary + details in summary +

    child of details in summary

    +
    +
    + top-level details + not a real summary +

    in top-level details

    + /* don't use an id so the "inherited from" section would have the same text as the + section for the parent details. This will assert that we do get separate inhertied + section for those different "levels" */ +
    + nested details summary + nested details +

    child of nested details

    +
    +
    +
    + s +
    hello +
    +`; + +add_task(async function () { + await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + const { inspector, view } = await openRuleView(); + + await selectNode("summary", inspector); + Assert.deepEqual( + getInheritedHeadersText(view), + ["Inherited from details"], + "There's no inherited ::details-content header when top-level is selected" + ); + + await selectNode("body > details > p", inspector); + Assert.deepEqual( + getInheritedHeadersText(view), + ["Inherited from details::details-content", "Inherited from details"], + "Got expected inherited headers when children of top-level
    is selected" + ); + + is( + view.element.querySelector( + ".ruleview-header + #pseudo-elements-container .ruleview-selector-pseudo-class" + ).textContent, + "::after", + "The ::after pseudo element rules is properly displayed in its section" + ); + + ok( + !isPropertyOverridden(view, 6, { color: "dodgerblue" }), + "color property in ::details-content is not overridden" + ); + ok( + isPropertyOverridden(view, 7, { color: "forestgreen" }), + "color property in lower specificity ::details-content is overridden" + ); + ok( + isPropertyOverridden(view, 9, { color: "gold" }), + "color property in details is overridden" + ); + + checkCSSVariableOutput( + view, + "p", + "outline-color", + "inspector-variable", + "tomato" + ); + + info("Check rules and declarations for details in summary"); + await selectNode("details#in-summary", inspector); + Assert.deepEqual( + getInheritedHeadersText(view), + ["Inherited from summary"], + "Got expected inherited headers when
    in is selected" + ); + + await selectNode("details#in-summary summary", inspector); + Assert.deepEqual( + getInheritedHeadersText(view), + ["Inherited from details#in-summary"], + "Got expected inherited headers when nested is selected" + ); + + await selectNode("details#in-summary p", inspector); + Assert.deepEqual( + getInheritedHeadersText(view), + [ + "Inherited from details#in-summary::details-content", + "Inherited from details#in-summary", + "Inherited from summary", + ], + "Got expected inherited headers when nested
    child is selected" + ); + + is( + view.element.querySelector( + ".ruleview-header + #pseudo-elements-container .ruleview-selector-pseudo-class" + ).textContent, + "::after", + "The ::after pseudo element rules is properly displayed in its section" + ); + + ok( + !isPropertyOverridden(view, 6, { color: "brown" }), + "color property in #detail#in-summary::details-content is not overridden" + ); + ok( + isPropertyOverridden(view, 7, { color: "dodgerblue" }), + "color property in ::details-content is overridden" + ); + ok( + isPropertyOverridden(view, 9, { color: "cyan" }), + "color property in details#in-summary is overridden" + ); + ok( + isPropertyOverridden(view, 10, { color: "gold" }), + "color property in details is overridden" + ); + ok( + isPropertyOverridden(view, 12, { color: "violet" }), + "color property in summary is overridden" + ); + + checkCSSVariableOutput( + view, + "p", + "outline-color", + "inspector-variable", + "rebeccapurple" + ); + + info("Check rules and declarations for second summary inside details"); + // when a
    element has multiple children, only the first one is + // actually interactive. The other ones are placed inside the ::details-content + await selectNode("summary#non-functional-summary", inspector); + Assert.deepEqual( + getInheritedHeadersText(view), + ["Inherited from details::details-content", "Inherited from details"], + "Got expected inherited headers when non functional summary is selected" + ); + + ok( + !isPropertyOverridden(view, 1, { color: "violet" }), + "color property in summary is not overridden when non functional summary is selected" + ); + ok( + isPropertyOverridden(view, 3, { color: "dodgerblue" }), + "color property in details::details-content is overridden when non functional summary is selected" + ); + ok( + isPropertyOverridden(view, 4, { color: "forestgreen" }), + "color property in :where(body > details)::details-content is overridden when non functional summary is selected" + ); + ok( + isPropertyOverridden(view, 6, { color: "gold" }), + "color property in details is overridden when non functional summary is selected" + ); + + info("Check rules and declarations for details in details"); + await selectNode("details.in-details", inspector); + Assert.deepEqual( + getInheritedHeadersText(view), + ["Inherited from details::details-content"], + "Got expected inherited headers when
    in
    is selected" + ); + ok( + !isPropertyOverridden(view, 4, { color: "gold" }), + "color property in details is not overridden for details in details" + ); + ok( + isPropertyOverridden(view, 6, { color: "forestgreen" }), + "color property in where(body > details)::details-content is overridden for details in details" + ); + + info("Check rules and declarations for children of details in details"); + await selectNode("details.in-details p", inspector); + Assert.deepEqual( + getInheritedHeadersText(view), + [ + // this is for the body > details > details::details-content pseudo + "Inherited from details::details-content", + // this is for the body > details::details-content pseudo + "Inherited from details::details-content", + "Inherited from details", + ], + "Got expected inherited headers when children
    in
    is selected" + ); + + ok( + !isPropertyOverridden(view, 6, { color: "dodgerblue" }), + "color property in inherited details::details-content is not overridden for child of details in details" + ); + ok( + isPropertyOverridden(view, 8, { color: "forestgreen" }), + "color property in inherited :where(body > details)::details-content is overridden for child of details in details" + ); + ok( + isPropertyOverridden(view, 10, { color: "gold" }), + "color property in inherited details is overridden for child of details in details" + ); + + info( + "Check that properties in inherited element-backed pseudo element rules are properly picked when using !important" + ); + await selectNode("#vip article", inspector); + Assert.deepEqual( + getInheritedHeadersText(view), + [ + "Inherited from details#vip::details-content", + "Inherited from details#vip", + ], + "Got expected inherited headers" + ); + + ok( + isPropertyOverridden(view, 2, { color: "red" }), + "non-important color property in #vip::details-content is overridden" + ); + ok( + !isPropertyOverridden(view, 3, { color: "blue" }), + "important color property in #vip::details-content is not overridden" + ); + ok( + isPropertyOverridden(view, 4, { color: "dodgerblue" }), + "non important color property in details::details-content is overridden" + ); + ok( + isPropertyOverridden(view, 5, { color: "forestgreen" }), + "non important color property in :where(body > details)::details-content is overridden" + ); + ok( + isPropertyOverridden(view, 7, { color: "gold" }), + "non important color property in details is overridden" + ); +}); + +function getInheritedHeadersText(view) { + return [...view.element.querySelectorAll(".ruleview-header-inherited")].map( + el => el.textContent + ); +} + +function isPropertyOverridden(view, ruleIndex, property) { + return getTextProperty( + view, + ruleIndex, + property + ).editor.element.classList.contains("ruleview-overridden"); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_pseudo-element_01.js b/devtools/client/inspector/rules/test/browser_rules_pseudo-element_01.js index 61a35e87038..d29d3d8191b 100644 --- a/devtools/client/inspector/rules/test/browser_rules_pseudo-element_01.js +++ b/devtools/client/inspector/rules/test/browser_rules_pseudo-element_01.js @@ -384,38 +384,37 @@ async function testCustomHighlight(inspector, view) { is( highlightRules[0].pseudoElement, - "::highlight(filter)", - "First highlight rule is for the filter highlight" + "::highlight(search)", + "First highlight rule is for the search highlight" ); - is( highlightRules[1].pseudoElement, "::highlight(search)", - "Second highlight rule is for the search highlight" + "Second highlight rule is also for the search highlight" ); is( highlightRules[2].pseudoElement, - "::highlight(search)", - "Third highlight rule is also for the search highlight" + "::highlight(filter)", + "Third highlight rule is for the filter highlight" ); is(highlightRules.length, 3, "Got all 3 active rules, but not unused one"); // Check that properties are marked as overridden only when they're on the same Highlight is( convertTextPropsToString(highlightRules[0].textProps), - `background-color: purple`, - "Got expected properties for filter highlight" - ); - is( - convertTextPropsToString(highlightRules[1].textProps), `color: white`, "Got expected properties for first search highlight" ); is( - convertTextPropsToString(highlightRules[2].textProps), + convertTextPropsToString(highlightRules[1].textProps), `background-color: tomato; ~~color: gold~~`, "Got expected properties for second search highlight, `color` is marked as overridden" ); + is( + convertTextPropsToString(highlightRules[2].textProps), + `background-color: purple`, + "Got expected properties for filter highlight" + ); assertGutters(view); } diff --git a/devtools/server/actors/page-style.js b/devtools/server/actors/page-style.js index 8f4b17ec5a9..f22454483d3 100644 --- a/devtools/server/actors/page-style.js +++ b/devtools/server/actors/page-style.js @@ -704,10 +704,10 @@ class PageStyleActor extends Actor { ); // Now any pseudos. - if (showElementStyles && !options.skipPseudo) { + if (!pseudo && !options.skipPseudo) { const relevantPseudoElements = []; for (const readPseudo of PSEUDO_ELEMENTS) { - if (!this._pseudoIsRelevant(bindingElement, readPseudo)) { + if (!this._pseudoIsRelevant(bindingElement, readPseudo, inherited)) { continue; } @@ -732,7 +732,17 @@ class PageStyleActor extends Actor { inherited, options ); - rules.push(...pseudoRules); + // inherited element backed pseudo element rules (e.g. `::details-content`) should + // not be at the same "level" as rules inherited from the binding element (e.g. `
    `), + // so we need to put them before the "regular" rules. + if ( + SharedCssLogic.ELEMENT_BACKED_PSEUDO_ELEMENTS.has(readPseudo) && + inherited + ) { + rules.unshift(...pseudoRules); + } else { + rules.push(...pseudoRules); + } } } @@ -787,7 +797,7 @@ class PageStyleActor extends Actor { } // eslint-disable-next-line complexity - _pseudoIsRelevant(node, pseudo) { + _pseudoIsRelevant(node, pseudo, inherited = false) { switch (pseudo) { case "::after": case "::before": @@ -796,35 +806,65 @@ class PageStyleActor extends Actor { case "::selection": case "::highlight": case "::target-text": - return true; + return !inherited; case "::marker": - return this._nodeIsListItem(node); + return !inherited && this._nodeIsListItem(node); case "::backdrop": - return node.matches(":modal, :popover-open"); + return !inherited && node.matches(":modal, :popover-open"); case "::cue": - return node.nodeName == "VIDEO"; + return !inherited && node.nodeName == "VIDEO"; case "::file-selector-button": - return node.nodeName == "INPUT" && node.type == "file"; - case "::details-content": - return node.nodeName == "DETAILS"; + return !inherited && node.nodeName == "INPUT" && node.type == "file"; + case "::details-content": { + const isDetailsNode = node.nodeName == "DETAILS"; + if (!isDetailsNode) { + return false; + } + + if (!inherited) { + return true; + } + + // If we're getting rules on a parent element, we need to check if the selected + // element is inside the ::details-content of node + // We traverse the flattened parent tree until we find the that implements + // the pseudo element, as it's easier to handle edge cases like nested
    , + // multiple , etc … + let traversedNode = this.selectedElement; + while (traversedNode) { + if ( + // if we found the implementing the pseudo element + traversedNode.implementedPseudoElement === "::details-content" && + // and its parent
    element is the element we're evaluating + traversedNode.flattenedTreeParentNode === node + ) { + // then include the ::details-content rules from that element + return true; + } + // otherwise keep looking up the tree + traversedNode = traversedNode.flattenedTreeParentNode; + } + + return false; + } case "::placeholder": case "::-moz-placeholder": - return this._nodeIsTextfieldLike(node); + return !inherited && this._nodeIsTextfieldLike(node); case "::-moz-focus-inner": - return this._nodeIsButtonLike(node); + return !inherited && this._nodeIsButtonLike(node); case "::-moz-meter-bar": - return node.nodeName == "METER"; + return !inherited && node.nodeName == "METER"; case "::-moz-progress-bar": - return node.nodeName == "PROGRESS"; + return !inherited && node.nodeName == "PROGRESS"; case "::-moz-color-swatch": - return node.nodeName == "INPUT" && node.type == "color"; + return !inherited && node.nodeName == "INPUT" && node.type == "color"; case "::-moz-range-progress": case "::-moz-range-thumb": case "::-moz-range-track": case "::slider-fill": case "::slider-thumb": case "::slider-track": - return node.nodeName == "INPUT" && node.type == "range"; + return !inherited && node.nodeName == "INPUT" && node.type == "range"; case "::view-transition": case "::view-transition-group": case "::view-transition-image-pair": diff --git a/devtools/shared/inspector/css-logic.js b/devtools/shared/inspector/css-logic.js index e1901f43003..f72b89463ee 100644 --- a/devtools/shared/inspector/css-logic.js +++ b/devtools/shared/inspector/css-logic.js @@ -839,3 +839,8 @@ function isCssVariable(input) { return !!input.match(IS_VARIABLE_TOKEN); } exports.isCssVariable = isCssVariable; + +exports.ELEMENT_BACKED_PSEUDO_ELEMENTS = new Set([ + "::details-content", + "::file-selector-button", +]); diff --git a/dom/events/Event.cpp b/dom/events/Event.cpp index e06b00ea40a..f04de5c4e43 100644 --- a/dom/events/Event.cpp +++ b/dom/events/Event.cpp @@ -110,9 +110,9 @@ void Event::InitPresContextData(nsPresContext* aPresContext) { mPresContext = aPresContext; // Get the explicit original target (if it's anonymous make it null) { - nsCOMPtr content = GetTargetFromFrame(); + nsIContent* content = GetTargetFromFrame(); if (content && !content->IsInNativeAnonymousSubtree()) { - mExplicitOriginalTarget = std::move(content); + mExplicitOriginalTarget = content; } else { mExplicitOriginalTarget = nullptr; } @@ -312,7 +312,7 @@ void Event::ComposedPath(nsTArray>& aPath) { // // Get the actual event target node (may have been retargeted for mouse events) // -already_AddRefed Event::GetTargetFromFrame() { +nsIContent* Event::GetTargetFromFrame() { if (!mPresContext) { return nullptr; } @@ -324,9 +324,7 @@ already_AddRefed Event::GetTargetFromFrame() { } // get the real content - nsCOMPtr realEventContent; - targetFrame->GetContentForEvent(mEvent, getter_AddRefs(realEventContent)); - return realEventContent.forget(); + return targetFrame->GetContentForEvent(mEvent); } EventTarget* Event::GetExplicitOriginalTarget() const { diff --git a/dom/events/Event.h b/dom/events/Event.h index 10121b61770..9d3af8149b8 100644 --- a/dom/events/Event.h +++ b/dom/events/Event.h @@ -400,7 +400,7 @@ class Event : public nsISupports, public nsWrapperCache { protected: // Internal helper functions void SetEventType(const nsAString& aEventTypeArg); - already_AddRefed GetTargetFromFrame(); + nsIContent* GetTargetFromFrame(); friend class EventMessageAutoOverride; friend class PopupBlocker; diff --git a/dom/events/EventStateManager.cpp b/dom/events/EventStateManager.cpp index 4eaf8a461bb..a37fe4feb95 100644 --- a/dom/events/EventStateManager.cpp +++ b/dom/events/EventStateManager.cpp @@ -905,9 +905,7 @@ nsresult EventStateManager::PreHandleEvent(nsPresContext* aPresContext, "aTargetFrame should be related with aTargetContent"); #if DEBUG if (aTargetFrame && aTargetFrame->IsGeneratedContentFrame()) { - nsCOMPtr targetContent; - aTargetFrame->GetContentForEvent(aEvent, getter_AddRefs(targetContent)); - MOZ_ASSERT(aTargetContent == targetContent, + MOZ_ASSERT(aTargetContent == aTargetFrame->GetContentForEvent(aEvent), "Unexpected target for generated content frame!"); } #endif @@ -2422,9 +2420,7 @@ void EventStateManager::BeginTrackingDragGesture(nsPresContext* aPresContext, SetGestureDownPoint(inDownEvent); if (inDownFrame) { - inDownFrame->GetContentForEvent(inDownEvent, - getter_AddRefs(mGestureDownContent)); - + mGestureDownContent = inDownFrame->GetContentForEvent(inDownEvent); mGestureDownFrameOwner = inDownFrame->GetContent(); if (!mGestureDownFrameOwner) { mGestureDownFrameOwner = mGestureDownContent; @@ -2530,8 +2526,7 @@ void EventStateManager::MaybeFirePointerCancel(WidgetInputEvent* aEvent) { return; } - nsCOMPtr content; - targetFrame->GetContentForEvent(aEvent, getter_AddRefs(content)); + nsCOMPtr content = targetFrame->GetContentForEvent(aEvent); // XXX If there is no proper event target, should we retarget ePointerCancel // somewhere else? if (NS_WARN_IF(!content)) { @@ -2660,12 +2655,13 @@ void EventStateManager::GenerateDragGesture(nsPresContext* aPresContext, RefPtr selection; RefPtr remoteDragStartData; - nsCOMPtr eventContent, targetContent; nsCOMPtr principal; nsCOMPtr csp; nsCOMPtr cookieJarSettings; + nsCOMPtr eventContent = + mCurrentTarget->GetContentForEvent(aEvent); + nsCOMPtr targetContent; bool allowEmptyDataTransfer = false; - mCurrentTarget->GetContentForEvent(aEvent, getter_AddRefs(eventContent)); if (eventContent) { // If the content is a text node in a password field, we shouldn't // allow to drag its raw text. Note that we've supported drag from @@ -3927,7 +3923,7 @@ nsresult EventStateManager::PostHandleEvent(nsPresContext* aPresContext, nsCOMPtr newFocus; bool suppressBlur = false; if (mCurrentTarget) { - mCurrentTarget->GetContentForEvent(aEvent, getter_AddRefs(newFocus)); + newFocus = mCurrentTarget->GetContentForEvent(aEvent); activeContent = mCurrentTarget->GetContent(); // In some cases, we do not want to even blur the current focused @@ -4487,9 +4483,8 @@ nsresult EventStateManager::PostHandleEvent(nsPresContext* aPresContext, case eMouseEnterIntoWidget: if (mCurrentTarget) { - nsCOMPtr targetContent; - mCurrentTarget->GetContentForEvent(aEvent, - getter_AddRefs(targetContent)); + nsCOMPtr targetContent = + mCurrentTarget->GetContentForEvent(aEvent); SetContentState(targetContent, ElementState::HOVER); } break; @@ -4501,9 +4496,8 @@ nsresult EventStateManager::PostHandleEvent(nsPresContext* aPresContext, #ifdef XP_MACOSX case eMouseActivate: if (mCurrentTarget) { - nsCOMPtr targetContent; - mCurrentTarget->GetContentForEvent(aEvent, - getter_AddRefs(targetContent)); + nsCOMPtr targetContent = + mCurrentTarget->GetContentForEvent(aEvent); if (!NodeAllowsClickThrough(targetContent)) { *aStatus = nsEventStatus_eConsumeNoDefault; } @@ -5711,9 +5705,8 @@ void EventStateManager::GenerateDragDropEnterExit(nsPresContext* aPresContext, // We'll need the content, too, to check if it changed separately from // the frames. nsCOMPtr lastContent; - nsCOMPtr targetContent; - mCurrentTarget->GetContentForEvent(aDragEvent, - getter_AddRefs(targetContent)); + nsCOMPtr targetContent = + mCurrentTarget->GetContentForEvent(aDragEvent); if (targetContent && targetContent->IsText()) { targetContent = targetContent->GetFlattenedTreeParent(); } @@ -5721,8 +5714,7 @@ void EventStateManager::GenerateDragDropEnterExit(nsPresContext* aPresContext, if (sLastDragOverFrame) { // The frame has changed but the content may not have. Check before // dispatching to content - sLastDragOverFrame->GetContentForEvent(aDragEvent, - getter_AddRefs(lastContent)); + lastContent = sLastDragOverFrame->GetContentForEvent(aDragEvent); if (lastContent && lastContent->IsText()) { lastContent = lastContent->GetFlattenedTreeParent(); } @@ -5767,9 +5759,8 @@ void EventStateManager::GenerateDragDropEnterExit(nsPresContext* aPresContext, case eDragExit: { // This is actually the window mouse exit event. if (sLastDragOverFrame) { - nsCOMPtr lastContent; - sLastDragOverFrame->GetContentForEvent(aDragEvent, - getter_AddRefs(lastContent)); + nsCOMPtr lastContent = + sLastDragOverFrame->GetContentForEvent(aDragEvent); RefPtr lastDragOverFramePresContext = sLastDragOverFrame->PresContext(); @@ -5866,7 +5857,7 @@ nsresult EventStateManager::SetClickCount(WidgetMouseEvent* aEvent, nsIContent* aOverrideClickTarget) { nsCOMPtr mouseContent = aOverrideClickTarget; if (!mouseContent && mCurrentTarget) { - mCurrentTarget->GetContentForEvent(aEvent, getter_AddRefs(mouseContent)); + mouseContent = mCurrentTarget->GetContentForEvent(aEvent); } if (mouseContent && mouseContent->IsText()) { nsINode* parent = mouseContent->GetFlattenedTreeParentNode(); @@ -6295,7 +6286,7 @@ already_AddRefed EventStateManager::GetEventTargetContent( // Some events here may set mCurrentTarget but not set the corresponding // event target in the PresShell. if (!content && mCurrentTarget) { - mCurrentTarget->GetContentForEvent(aEvent, getter_AddRefs(content)); + content = mCurrentTarget->GetContentForEvent(aEvent); } return content.forget(); diff --git a/dom/events/PointerEventHandler.cpp b/dom/events/PointerEventHandler.cpp index 00d5357818e..9c39569f5c4 100644 --- a/dom/events/PointerEventHandler.cpp +++ b/dom/events/PointerEventHandler.cpp @@ -457,8 +457,7 @@ void PointerEventHandler::ImplicitlyCapturePointer(nsIFrame* aFrame, // We only implicitly capture the pointer for touch device. return; } - nsCOMPtr target; - aFrame->GetContentForEvent(aEvent, getter_AddRefs(target)); + nsIContent* target = aFrame->GetContentForEvent(aEvent); while (target && !target->IsElement()) { target = target->GetParent(); } diff --git a/dom/ipc/BrowserParent.cpp b/dom/ipc/BrowserParent.cpp index c508af8cef1..34485a73c37 100644 --- a/dom/ipc/BrowserParent.cpp +++ b/dom/ipc/BrowserParent.cpp @@ -843,20 +843,8 @@ void BrowserParent::ActorDestroy(ActorDestroyReason why) { nsCOMPtr principal = GetContentPrincipal(); if (principal) { - nsAutoCString crash_reason; - CrashReporter::GetAnnotation(OtherPid(), - CrashReporter::Annotation::MozCrashReason, - crash_reason); - // FIXME(arenevier): Find a less fragile way to identify that a crash - // was caused by OOM - bool is_oom = false; - if (crash_reason == "OOM" || crash_reason == "OOM!" || - StringBeginsWith(crash_reason, "[unhandlable oom]"_ns) || - StringBeginsWith(crash_reason, "Unhandlable OOM"_ns)) { - is_oom = true; - } - - CrashReport::Deliver(principal, is_oom); + // TODO: Flag out-of-memory crashes appropriately. + CrashReport::Deliver(principal, /* aIsOOM */ false); } } } diff --git a/dom/media/ipc/MFCDMParent.cpp b/dom/media/ipc/MFCDMParent.cpp index 1b65f5d2696..8a4d3349074 100644 --- a/dom/media/ipc/MFCDMParent.cpp +++ b/dom/media/ipc/MFCDMParent.cpp @@ -652,12 +652,54 @@ static bool FactorySupports(ComPtr& aFactory, MF_MEDIA_ENGINE_CANPLAY canPlay; spDrmTypeSupport->IsTypeSupportedEx(SysAllocString(contentType.get()), keySystem, &canPlay); - const bool support = + bool support = canPlay != MF_MEDIA_ENGINE_CANPLAY::MF_MEDIA_ENGINE_CANPLAY_NOT_SUPPORTED; MFCDM_PARENT_SLOG("IsTypeSupportedEx=%d (key-system=%ls, content-type=%s)", support, keySystem, NS_ConvertUTF16toUTF8(contentType).get()); + if (aIsHWSecure && support) { + // For HWDRM, `IsTypeSupportedEx` might still return the wrong answer on + // certain devices, so we need to create a dummy CDM to see if the HWDRM + // is really usable or not. + nsTArray dummyInitDataType{nsString(u"cenc"), + nsString(u"keyids")}; + nsString mimeType(u"video/mp4;codecs=\""); + mimeType.AppendASCII(aVideoCodec); + MFCDMMediaCapability dummyVideoCapability{ + mimeType, + {CryptoScheme::None}, // No specific scheme + nsString(u"3000")}; + MFCDMInitParamsIPDL dummyParam{ + nsString(u"dummy"), + dummyInitDataType, + KeySystemConfig::Requirement::Required /* distinctiveID */, + KeySystemConfig::Requirement::Required /* persistent */, + {} /* audio capabilities */, + {dummyVideoCapability} /* video capabilities */, + }; + ComPtr dummyCDM = nullptr; + if (FAILED(CreateContentDecryptionModule( + aFactory, MapKeySystem(aKeySystem), dummyParam, dummyCDM)) || + !dummyCDM) { + if (IsBeingProfiledOrLogEnabled()) { + nsPrintfCString msg( + "HWDRM actually not supported (key-system=%ls, content-type=%s)", + keySystem, NS_ConvertUTF16toUTF8(contentType).get()); + PROFILER_MARKER_TEXT("MFCDMParent::FailedToUseHWDRM", MEDIA_PLAYBACK, + {}, msg); + MFCDM_PARENT_SLOG("%s", msg.get()); + } + support = false; + } + MFCDM_PARENT_SLOG( + "After HWDRM creation check, support=%d (key-system=%ls, " + "content-type=%s)", + support, keySystem, NS_ConvertUTF16toUTF8(contentType).get()); + if (dummyCDM) { + SHUTDOWN_IF_POSSIBLE(dummyCDM); + } + } return support; } diff --git a/dom/media/platforms/wmf/DXVA2Manager.cpp b/dom/media/platforms/wmf/DXVA2Manager.cpp index 81803d1a6f1..4f76a8691d1 100644 --- a/dom/media/platforms/wmf/DXVA2Manager.cpp +++ b/dom/media/platforms/wmf/DXVA2Manager.cpp @@ -669,9 +669,8 @@ D3D11DXVA2Manager::InitInternal(layers::KnowsCompositor* aKnowsCompositor, } } - auto* fencesHolderMap = layers::CompositeProcessD3D11FencesHolderMap::Get(); - const bool useFence = - fencesHolderMap && layers::FenceD3D11::IsSupported(mDevice); + // XXX enable fence + const bool useFence = false; if (useFence) { mWriteFence = layers::FenceD3D11::Create(mDevice); } diff --git a/dom/quota/test/xpcshell/xpcshell.toml b/dom/quota/test/xpcshell/xpcshell.toml index da551137cb4..e936dcc3671 100644 --- a/dom/quota/test/xpcshell/xpcshell.toml +++ b/dom/quota/test/xpcshell/xpcshell.toml @@ -150,6 +150,7 @@ skip-if = ["inc_origin_init"] ["test_specialOrigins.js"] ["test_storagePressure.js"] +run-if = ["!condprof"] # Bug 1960788 skip-if = ["inc_origin_init"] ["test_tempMetadataCleanup.js"] diff --git a/dom/webgpu/Instance.cpp b/dom/webgpu/Instance.cpp index fae22ed80e4..cb129cdcd50 100644 --- a/dom/webgpu/Instance.cpp +++ b/dom/webgpu/Instance.cpp @@ -6,6 +6,7 @@ #include "Instance.h" #include "Adapter.h" +#include "js/Value.h" #include "mozilla/Assertions.h" #include "mozilla/ErrorResult.h" #include "mozilla/gfx/Logging.h" @@ -19,6 +20,7 @@ #include "mozilla/gfx/gfxVars.h" #include "mozilla/StaticPrefs_dom.h" #include "nsString.h" +#include "nsStringFwd.h" #ifndef EARLY_BETA_OR_EARLIER # include "mozilla/dom/WorkerPrivate.h" @@ -103,37 +105,36 @@ already_AddRefed Instance::RequestAdapter( // - // Check if we should allow the request. - const auto errStr = [&]() -> std::optional { -#ifndef EARLY_BETA_OR_EARLIER - if (true) { - return "WebGPU is not yet available in Release or late Beta builds."; + std::optional rejectionMessage = {}; + const auto rejectIf = [&rejectionMessage](bool condition, + const char* message) { + if (condition && !rejectionMessage.has_value()) { + rejectionMessage = message; } + }; - // NOTE: Deliberately left after the above check so that we only enter - // here if it's removed. Above is a more informative diagnostic, while the - // check is still present. - // - // Follow-up to remove this check: - // - if (dom::WorkerPrivate* wp = dom::GetCurrentThreadWorkerPrivate()) { - if (wp->IsServiceWorker()) { - return "WebGPU in service workers is not yet available in Release or " - "late Beta builds; see " - "."; - } - } +#ifndef EARLY_BETA_OR_EARLIER + rejectIf(true, "WebGPU is not yet available in Release or late Beta builds."); + + // NOTE: Deliberately left after the above check so that we only enter + // here if it's removed. Above is a more informative diagnostic, while the + // check is still present. + // + // Follow-up to remove this check: + // + if (dom::WorkerPrivate* wp = dom::GetCurrentThreadWorkerPrivate()) { + rejectIf(wp->IsServiceWorker(), + "WebGPU in service workers is not yet available in Release or " + "late Beta builds; see " + "."); + } #endif - if (!gfx::gfxVars::AllowWebGPU()) { - return "WebGPU is disabled by blocklist."; - } - if (!StaticPrefs::dom_webgpu_enabled()) { - return "WebGPU is disabled because the `dom.webgpu.enabled` pref. is set " - "to `false`."; - } - return {}; - }(); - if (errStr) { - promise->MaybeRejectWithNotSupportedError(ToCString(*errStr)); + rejectIf(!gfx::gfxVars::AllowWebGPU(), "WebGPU is disabled by blocklist."); + rejectIf(!StaticPrefs::dom_webgpu_enabled(), + "WebGPU is disabled because the `dom.webgpu.enabled` pref. is set " + "to `false`."); + if (rejectionMessage) { + promise->MaybeRejectWithNotSupportedError(ToCString(*rejectionMessage)); return promise.forget(); } @@ -155,6 +156,31 @@ already_AddRefed Instance::RequestAdapter( RefPtr instance = this; + if (aOptions.mFeatureLevel.EqualsASCII("core")) { + // Good! That's all we support. + } else if (aOptions.mFeatureLevel.EqualsASCII("compatibility")) { + dom::AutoJSAPI api; + if (api.Init(mOwner)) { + JS::WarnUTF8(api.cx(), + "User requested a WebGPU adapter with `featureLevel: " + "\"compatibility\"`, which is not yet supported; returning " + "a \"core\"-defaulting adapter for now. Subscribe to " + "" + " for updates on its development in Firefox."); + } + } else { + NS_ConvertUTF16toUTF8 featureLevel(aOptions.mFeatureLevel); + dom::AutoJSAPI api; + if (api.Init(mOwner)) { + JS::WarnUTF8(api.cx(), + "expected one of `\"core\"` or `\"compatibility\"` for " + "`GPUAdapter.featureLevel`, got %s", + featureLevel.get()); + } + promise->MaybeResolve(JS::NullValue()); + return promise.forget(); + } + bridge->InstanceRequestAdapter(aOptions)->Then( GetCurrentSerialEventTarget(), __func__, [promise, instance, bridge](ipc::ByteBuf aInfoBuf) { diff --git a/dom/webgpu/tests/cts/vendor/src/main.rs b/dom/webgpu/tests/cts/vendor/src/main.rs index 1f6695af139..0663cf35715 100644 --- a/dom/webgpu/tests/cts/vendor/src/main.rs +++ b/dom/webgpu/tests/cts/vendor/src/main.rs @@ -1,4 +1,5 @@ use std::{ + borrow::Cow, collections::{BTreeMap, BTreeSet}, env::set_current_dir, path::PathBuf, @@ -208,7 +209,7 @@ fn run(args: CliArgs) -> miette::Result<()> { "[^']*?:\*)", "'>", "$" )) @@ -264,52 +265,51 @@ fn run(args: CliArgs) -> miette::Result<()> { let mut failed_writing = false; let mut cts_cases_by_spec_file_dir = BTreeMap::<_, BTreeMap<_, BTreeSet<_>>>::new(); for (path, worker_type, meta) in cts_cases { - let case_dir = { - // Context: We want to mirror CTS upstream's `src/webgpu/**/*.spec.ts` paths as - // entire WPT tests, with each subtest being a WPT variant. Here's a diagram of - // a CTS path to explain why the logic below is correct: - // - // ```sh - // webgpu:this,is,the,spec.ts,file,path:subtest_in_file:… - // \____/ \___________________________/^\_____________/ - // test `*.spec.ts` file path | | - // \__________________________________/| | - // | | | - // We want this… | …but not this. CTS upstream generates - // | this too, but we don't want to divide - // second ':' character here---/ here (yet). - // ``` - let subtest_and_later_start_idx = - match path.match_indices(':').nth(1).map(|(idx, _s)| idx) { - Some(some) => some, - None => { - failed_writing = true; - log::error!( - concat!( - "failed to split suite and test path segments ", - "from CTS path `{}`" - ), - path - ); - continue; - } - }; - let slashed = path[..subtest_and_later_start_idx].replace([':', ','], "/"); - cts_tests_dir.child(slashed) - }; - if !cts_cases_by_spec_file_dir - .entry(case_dir) - .or_default() - .entry(worker_type) - .or_default() - .insert(meta) - { - log::warn!("duplicate entry {meta:?} detected") + macro_rules! insert { + ($path:expr, $meta:expr $(,)?) => {{ + let dir = cts_tests_dir.child($path); + if !cts_cases_by_spec_file_dir + .entry(dir) + .or_default() + .entry(worker_type) + .or_default() + .insert($meta) + { + log::warn!("duplicate entry {meta:?} detected") + } + }}; } + + // Context: We want to mirror CTS upstream's `src/webgpu/**/*.spec.ts` paths as + // entire WPT tests, with each subtest being a WPT variant. Here's a diagram of + // a CTS path to explain why the logic below is correct: + // + // ```sh + // webgpu:this,is,the,spec.ts,file,path:test_in_file:… + // \____/ \___________________________/^\__________/ + // test `*.spec.ts` file path | | + // \__________________________________/| | + // | | | + // We want this… | …but not this. CTS upstream generates + // | this too, but we don't want to divide + // second ':' character here---/ here (yet). + // ``` + let (test_path, _cases) = match split_at_nth_colon(2, &path) { + Ok(ok) => ok, + Err(e) => { + failed_writing = true; + log::error!("{e}"); + continue; + } + }; + let (test_group_path, _test_name) = test_path.rsplit_once(':').unwrap(); + + let slashed = test_group_path.replace([':', ','], "/"); + insert!(&slashed, meta.into()); } struct WptEntry<'a> { - cases: BTreeSet<&'a str>, + cases: BTreeSet>, timeout_length: TimeoutLength, } #[derive(Clone, Copy, Debug)] @@ -322,7 +322,7 @@ fn run(args: CliArgs) -> miette::Result<()> { fn insert_with_default_name<'a>( split_cases: &mut BTreeMap, WptEntry<'a>>, spec_file_dir: fs::Child<'a>, - cases: BTreeMap, BTreeSet<&'a str>>, + cases: BTreeMap, BTreeSet>>, timeout_length: TimeoutLength, ) { for (worker_type, cases) in cases { @@ -458,3 +458,12 @@ fn run(args: CliArgs) -> miette::Result<()> { Ok(()) } + +fn split_at_nth_colon(nth: usize, path: &str) -> miette::Result<(&str, &str)> { + path.match_indices(':') + .nth(nth) + .map(|(idx, s)| (&path[..idx], &path[idx + s.len()..])) + .ok_or_else(move || { + miette::diagnostic!("failed to split at colon {nth} from CTS path `{path}`").into() + }) +} diff --git a/dom/webidl/WebGPU.webidl b/dom/webidl/WebGPU.webidl index 84ac4401566..e884f32589a 100644 --- a/dom/webidl/WebGPU.webidl +++ b/dom/webidl/WebGPU.webidl @@ -104,6 +104,7 @@ interface GPU { }; dictionary GPURequestAdapterOptions { + DOMString featureLevel = "core"; GPUPowerPreference powerPreference; boolean forceFallbackAdapter = false; }; diff --git a/gfx/layers/D3D11ShareHandleImage.cpp b/gfx/layers/D3D11ShareHandleImage.cpp index e47c79db110..3fada51cd17 100644 --- a/gfx/layers/D3D11ShareHandleImage.cpp +++ b/gfx/layers/D3D11ShareHandleImage.cpp @@ -190,8 +190,8 @@ already_AddRefed D3D11RecycleAllocator::CreateOrRecycleClient( mImageDevice = device; auto* fencesHolderMap = CompositeProcessD3D11FencesHolderMap::Get(); - const bool useFence = - fencesHolderMap && FenceD3D11::IsSupported(mImageDevice); + // XXX enable fence + const bool useFence = false; TextureAllocationFlags allocFlags = TextureAllocationFlags::ALLOC_DEFAULT; if (!useFence && (StaticPrefs::media_wmf_use_sync_texture_AtStartup() || mDevice == DeviceManagerDx::Get()->GetCompositorDevice())) { @@ -210,7 +210,6 @@ already_AddRefed D3D11RecycleAllocator::CreateOrRecycleClient( if (textureClient) { auto* textureData = textureClient->GetInternalData()->AsD3D11TextureData(); MOZ_ASSERT(textureData); - auto* fencesHolderMap = CompositeProcessD3D11FencesHolderMap::Get(); if (textureData && textureData->mFencesHolderId.isSome() && fencesHolderMap) { fencesHolderMap->WaitAllFencesAndForget( diff --git a/gfx/layers/d3d11/TextureD3D11.cpp b/gfx/layers/d3d11/TextureD3D11.cpp index 65df7029b56..acb3cc8661a 100644 --- a/gfx/layers/d3d11/TextureD3D11.cpp +++ b/gfx/layers/d3d11/TextureD3D11.cpp @@ -480,8 +480,6 @@ already_AddRefed D3D11TextureData::CreateTextureClient( auto* fencesHolderMap = layers::CompositeProcessD3D11FencesHolderMap::Get(); fencesHolderId = Some(CompositeProcessFencesHolderId::GetNext()); fencesHolderMap->Register(fencesHolderId.ref()); - } else { - MOZ_ASSERT_UNREACHABLE("unexpected to be called"); } D3D11TextureData* data = new D3D11TextureData( diff --git a/gfx/layers/ipc/CompositorBridgeParent.cpp b/gfx/layers/ipc/CompositorBridgeParent.cpp index dd5e96a7220..baca0b8ba1a 100644 --- a/gfx/layers/ipc/CompositorBridgeParent.cpp +++ b/gfx/layers/ipc/CompositorBridgeParent.cpp @@ -1439,7 +1439,7 @@ CompositorBridgeParent::LayerTreeState::GetCompositorController() const { return mParent; } -void CompositorBridgeParent::NotifyDidSceneBuild( +void CompositorBridgeParent::ScheduleFrameAfterSceneBuild( RefPtr aInfo) { MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); if (mPaused) { @@ -1447,7 +1447,7 @@ void CompositorBridgeParent::NotifyDidSceneBuild( } if (mWrBridge) { - mWrBridge->NotifyDidSceneBuild(aInfo); + mWrBridge->ScheduleFrameAfterSceneBuild(aInfo); } } diff --git a/gfx/layers/ipc/CompositorBridgeParent.h b/gfx/layers/ipc/CompositorBridgeParent.h index 735aa743bdf..dcc26c4bedb 100644 --- a/gfx/layers/ipc/CompositorBridgeParent.h +++ b/gfx/layers/ipc/CompositorBridgeParent.h @@ -334,7 +334,8 @@ class CompositorBridgeParent final : public CompositorBridgeParentBase, TimeStamp& aCompositeStart, TimeStamp& aRenderStart, TimeStamp& aCompositeEnd, wr::RendererStats* aStats = nullptr); - void NotifyDidSceneBuild(RefPtr aInfo); + void ScheduleFrameAfterSceneBuild( + RefPtr aInfo); RefPtr GetAsyncImagePipelineManager() const; PCompositorWidgetParent* AllocPCompositorWidgetParent( diff --git a/gfx/layers/wr/WebRenderBridgeParent.cpp b/gfx/layers/wr/WebRenderBridgeParent.cpp index ef78f2b7b62..52016224c62 100644 --- a/gfx/layers/wr/WebRenderBridgeParent.cpp +++ b/gfx/layers/wr/WebRenderBridgeParent.cpp @@ -2526,7 +2526,7 @@ void WebRenderBridgeParent::NotifySceneBuiltForEpoch( } } -void WebRenderBridgeParent::NotifyDidSceneBuild( +void WebRenderBridgeParent::ScheduleFrameAfterSceneBuild( RefPtr aInfo) { MOZ_ASSERT(IsRootWebRenderBridgeParent()); if (!mCompositorScheduler) { diff --git a/gfx/layers/wr/WebRenderBridgeParent.h b/gfx/layers/wr/WebRenderBridgeParent.h index 42a4451ae42..ecef1b812b0 100644 --- a/gfx/layers/wr/WebRenderBridgeParent.h +++ b/gfx/layers/wr/WebRenderBridgeParent.h @@ -279,7 +279,8 @@ class WebRenderBridgeParent final : public PWebRenderBridgeParent, */ void ScheduleForcedGenerateFrame(wr::RenderReasons aReasons); - void NotifyDidSceneBuild(RefPtr aInfo); + void ScheduleFrameAfterSceneBuild( + RefPtr aInfo); wr::Epoch UpdateWebRender( CompositorVsyncScheduler* aScheduler, RefPtr&& aApi, diff --git a/gfx/thebes/gfxFont.cpp b/gfx/thebes/gfxFont.cpp index c2f2124f01d..665685a632e 100644 --- a/gfx/thebes/gfxFont.cpp +++ b/gfx/thebes/gfxFont.cpp @@ -1342,8 +1342,12 @@ static const hb_tag_t defaultFeatures[] = { void gfxFont::CheckForFeaturesInvolvingSpace() const { gfxFontEntry::SpaceFeatures flags = gfxFontEntry::SpaceFeatures::None; + // mFontEntry->mHasSpaceFeatures is a std::atomic<>, so we set it with + // `exchange` to avoid a potential data race. It's ok if two threads both + // try to set it; they'll end up with the same value, so it doesn't matter + // that one will overwrite the other. auto setFlags = - MakeScopeExit([&]() { mFontEntry->mHasSpaceFeatures = flags; }); + MakeScopeExit([&]() { mFontEntry->mHasSpaceFeatures.exchange(flags); }); bool log = LOG_FONTINIT_ENABLED(); TimeStamp start; diff --git a/gfx/webrender_bindings/RenderThread.cpp b/gfx/webrender_bindings/RenderThread.cpp index b58ed5ead75..2232c801371 100644 --- a/gfx/webrender_bindings/RenderThread.cpp +++ b/gfx/webrender_bindings/RenderThread.cpp @@ -437,9 +437,9 @@ void RenderThread::WrNotifierEvent_WakeUp(WrWindowId aWindowId, PostWrNotifierEvents(aWindowId, info); } -void RenderThread::WrNotifierEvent_NewFrameReady(WrWindowId aWindowId, - bool aCompositeNeeded, - FramePublishId aPublishId) { +void RenderThread::WrNotifierEvent_NewFrameReady( + WrWindowId aWindowId, wr::FramePublishId aPublishId, + const wr::FrameReadyParams* aParams) { auto windows = mWindowInfos.Lock(); auto it = windows->find(AsUint64(aWindowId)); if (it == windows->end()) { @@ -449,7 +449,7 @@ void RenderThread::WrNotifierEvent_NewFrameReady(WrWindowId aWindowId, WindowInfo* info = it->second.get(); info->mPendingWrNotifierEvents.emplace( - WrNotifierEvent::NewFrameReady(aCompositeNeeded, aPublishId)); + WrNotifierEvent::NewFrameReady(aPublishId, aParams)); PostWrNotifierEvents(aWindowId, info); } @@ -529,12 +529,12 @@ void RenderThread::HandleWrNotifierEvents(WrWindowId aWindowId) { auto& front = events->front(); switch (front.mTag) { case WrNotifierEvent::Tag::WakeUp: - WrNotifierEvent_HandleWakeUp(aWindowId, front.CompositeNeeded()); + WrNotifierEvent_HandleWakeUp(aWindowId, front.FrameReadyParams()); handleNext = false; break; case WrNotifierEvent::Tag::NewFrameReady: - WrNotifierEvent_HandleNewFrameReady(aWindowId, front.CompositeNeeded(), - front.PublishId()); + WrNotifierEvent_HandleNewFrameReady(aWindowId, front.PublishId(), + front.FrameReadyParams()); handleNext = false; break; case WrNotifierEvent::Tag::ExternalEvent: @@ -558,21 +558,21 @@ void RenderThread::HandleWrNotifierEvents(WrWindowId aWindowId) { } } -void RenderThread::WrNotifierEvent_HandleWakeUp(wr::WindowId aWindowId, - bool aCompositeNeeded) { +void RenderThread::WrNotifierEvent_HandleWakeUp( + wr::WindowId aWindowId, const wr::FrameReadyParams& aParams) { MOZ_ASSERT(IsInRenderThread()); bool isTrackedFrame = false; - HandleFrameOneDoc(aWindowId, aCompositeNeeded, isTrackedFrame, Nothing()); + HandleFrameOneDoc(aWindowId, aParams, isTrackedFrame, Nothing()); } void RenderThread::WrNotifierEvent_HandleNewFrameReady( - wr::WindowId aWindowId, bool aCompositeNeeded, FramePublishId aPublishId) { + wr::WindowId aWindowId, wr::FramePublishId aPublishId, + const wr::FrameReadyParams& aParams) { MOZ_ASSERT(IsInRenderThread()); bool isTrackedFrame = true; - HandleFrameOneDoc(aWindowId, aCompositeNeeded, isTrackedFrame, - Some(aPublishId)); + HandleFrameOneDoc(aWindowId, aParams, isTrackedFrame, Some(aPublishId)); } void RenderThread::WrNotifierEvent_HandleExternalEvent( @@ -601,7 +601,8 @@ Maybe RenderThread::EndRecordingForWindow( return renderer->EndRecording(); } -void RenderThread::HandleFrameOneDoc(wr::WindowId aWindowId, bool aRender, +void RenderThread::HandleFrameOneDoc(wr::WindowId aWindowId, + const wr::FrameReadyParams& aParams, bool aTrackedFrame, Maybe aPublishId) { MOZ_ASSERT(IsInRenderThread()); @@ -610,14 +611,15 @@ void RenderThread::HandleFrameOneDoc(wr::WindowId aWindowId, bool aRender, return; } - HandleFrameOneDocInner(aWindowId, aRender, aTrackedFrame, aPublishId); + HandleFrameOneDocInner(aWindowId, aParams, aTrackedFrame, aPublishId); if (aTrackedFrame) { DecPendingFrameCount(aWindowId); } } -void RenderThread::HandleFrameOneDocInner(wr::WindowId aWindowId, bool aRender, +void RenderThread::HandleFrameOneDocInner(wr::WindowId aWindowId, + const wr::FrameReadyParams& aParams, bool aTrackedFrame, Maybe aPublishId) { if (IsDestroyed(aWindowId)) { @@ -628,7 +630,6 @@ void RenderThread::HandleFrameOneDocInner(wr::WindowId aWindowId, bool aRender, return; } - bool render = aRender; PendingFrameInfo frame; if (aTrackedFrame) { // scope lock @@ -663,7 +664,7 @@ void RenderThread::HandleFrameOneDocInner(wr::WindowId aWindowId, bool aRender, RendererStats stats = {0}; - UpdateAndRender(aWindowId, frame.mStartId, frame.mStartTime, render, + UpdateAndRender(aWindowId, frame.mStartId, frame.mStartTime, aParams, /* aReadbackSize */ Nothing(), /* aReadbackFormat */ Nothing(), /* aReadbackBuffer */ Nothing(), &stats); @@ -812,14 +813,14 @@ void RenderThread::SetFramePublishId(wr::WindowId aWindowId, void RenderThread::UpdateAndRender( wr::WindowId aWindowId, const VsyncId& aStartId, - const TimeStamp& aStartTime, bool aRender, + const TimeStamp& aStartTime, const wr::FrameReadyParams& aParams, const Maybe& aReadbackSize, const Maybe& aReadbackFormat, const Maybe>& aReadbackBuffer, RendererStats* aStats, bool* aNeedsYFlip) { AUTO_PROFILER_LABEL("RenderThread::UpdateAndRender", GRAPHICS); MOZ_ASSERT(IsInRenderThread()); - MOZ_ASSERT(aRender || aReadbackBuffer.isNothing()); + MOZ_ASSERT(aParams.render || aReadbackBuffer.isNothing()); auto it = mRenderers.find(aWindowId); MOZ_ASSERT(it != mRenderers.end()); @@ -836,20 +837,22 @@ void RenderThread::UpdateAndRender( "Paint", markerName.c_str(), geckoprofiler::category::GRAPHICS, Some(renderer->GetCompositorBridge()->GetInnerWindowId())); + bool render = aParams.render; if (renderer->IsPaused()) { - aRender = false; + render = false; } LOG("RenderThread::UpdateAndRender() aWindowId %" PRIx64 " aRender %d", - AsUint64(aWindowId), aRender); + AsUint64(aWindowId), render); layers::CompositorThread()->Dispatch( NewRunnableFunction("NotifyDidStartRenderRunnable", &NotifyDidStartRender, renderer->GetCompositorBridge())); wr::RenderedFrameId latestFrameId; - if (aRender) { - latestFrameId = renderer->UpdateAndRender( - aReadbackSize, aReadbackFormat, aReadbackBuffer, aNeedsYFlip, aStats); + if (render) { + latestFrameId = renderer->UpdateAndRender(aReadbackSize, aReadbackFormat, + aReadbackBuffer, aNeedsYFlip, + aParams, aStats); } else { renderer->Update(); } @@ -864,7 +867,7 @@ void RenderThread::UpdateAndRender( layers::CompositorThread()->Dispatch( NewRunnableFunction("NotifyDidRenderRunnable", &NotifyDidRender, renderer->GetCompositorBridge(), info, aStartId, - aStartTime, start, end, aRender, *aStats)); + aStartTime, start, end, render, *aStats)); RefPtr fence; @@ -1738,14 +1741,13 @@ void wr_notifier_wake_up(mozilla::wr::WrWindowId aWindowId, aCompositeNeeded); } -void wr_notifier_new_frame_ready(mozilla::wr::WrWindowId aWindowId, - bool aCompositeNeeded, - mozilla::wr::FramePublishId aPublishId) { +void wr_notifier_new_frame_ready(wr::WrWindowId aWindowId, + wr::FramePublishId aPublishId, + const wr::FrameReadyParams* aParams) { auto* renderThread = mozilla::wr::RenderThread::Get(); renderThread->DecPendingFrameBuildCount(aWindowId); - renderThread->WrNotifierEvent_NewFrameReady(aWindowId, aCompositeNeeded, - aPublishId); + renderThread->WrNotifierEvent_NewFrameReady(aWindowId, aPublishId, aParams); } void wr_notifier_external_event(mozilla::wr::WrWindowId aWindowId, @@ -1769,22 +1771,24 @@ void wr_schedule_render(mozilla::wr::WrWindowId aWindowId, "NotifyScheduleRender", &NotifyScheduleRender, aWindowId, aReasons)); } -static void NotifyDidSceneBuild( +static void ScheduleFrameAfterSceneBuild( mozilla::wr::WrWindowId aWindowId, const RefPtr& aInfo) { RefPtr cbp = mozilla::layers:: CompositorBridgeParent::GetCompositorBridgeParentFromWindowId(aWindowId); if (cbp) { - cbp->NotifyDidSceneBuild(aInfo); + cbp->ScheduleFrameAfterSceneBuild(aInfo); } } -void wr_finished_scene_build(mozilla::wr::WrWindowId aWindowId, - mozilla::wr::WrPipelineInfo* aPipelineInfo) { +void wr_schedule_frame_after_scene_build( + mozilla::wr::WrWindowId aWindowId, + mozilla::wr::WrPipelineInfo* aPipelineInfo) { RefPtr info = new wr::WebRenderPipelineInfo(); info->Raw() = std::move(*aPipelineInfo); - layers::CompositorThread()->Dispatch(NewRunnableFunction( - "NotifyDidSceneBuild", &NotifyDidSceneBuild, aWindowId, info)); + layers::CompositorThread()->Dispatch( + NewRunnableFunction("ScheduleFrameAfterSceneBuild", + &ScheduleFrameAfterSceneBuild, aWindowId, info)); } } // extern C diff --git a/gfx/webrender_bindings/RenderThread.h b/gfx/webrender_bindings/RenderThread.h index 680696b7bfe..000b9e8256f 100644 --- a/gfx/webrender_bindings/RenderThread.h +++ b/gfx/webrender_bindings/RenderThread.h @@ -209,7 +209,8 @@ class RenderThread final { /// Can only be called from the render thread. void UpdateAndRender(wr::WindowId aWindowId, const VsyncId& aStartId, - const TimeStamp& aStartTime, bool aRender, + const TimeStamp& aStartTime, + const wr::FrameReadyParams& aParams, const Maybe& aReadbackSize, const Maybe& aReadbackFormat, const Maybe>& aReadbackBuffer, @@ -274,8 +275,8 @@ class RenderThread final { // RenderNotifier implementation void WrNotifierEvent_WakeUp(WrWindowId aWindowId, bool aCompositeNeeded); void WrNotifierEvent_NewFrameReady(WrWindowId aWindowId, - bool aCompositeNeeded, - FramePublishId aPublishId); + wr::FramePublishId aPublishId, + const wr::FrameReadyParams* aParams); void WrNotifierEvent_ExternalEvent(WrWindowId aWindowId, size_t aRawEvent); /// Can be called from any thread. @@ -371,34 +372,41 @@ class RenderThread final { const Tag mTag; private: - WrNotifierEvent(const Tag aTag, const bool aCompositeNeeded) - : mTag(aTag), mCompositeNeeded(aCompositeNeeded) { - MOZ_ASSERT(mTag == Tag::WakeUp); - } - WrNotifierEvent(const Tag aTag, bool aCompositeNeeded, - FramePublishId aPublishId) - : mTag(aTag), - mCompositeNeeded(aCompositeNeeded), - mPublishId(aPublishId) { + WrNotifierEvent(const Tag aTag, wr::FramePublishId aPublishId, + wr::FrameReadyParams aParams) + : mTag(aTag), mPublishId(aPublishId), mParams(aParams) { MOZ_ASSERT(mTag == Tag::NewFrameReady); } + WrNotifierEvent(const Tag aTag, wr::FrameReadyParams aParams) + : mTag(aTag), mParams(aParams) { + MOZ_ASSERT(mTag == Tag::WakeUp); + } WrNotifierEvent(const Tag aTag, UniquePtr&& aRendererEvent) : mTag(aTag), mRendererEvent(std::move(aRendererEvent)) { MOZ_ASSERT(mTag == Tag::ExternalEvent); } - const bool mCompositeNeeded = false; - const FramePublishId mPublishId = FramePublishId::INVALID; + const wr::FramePublishId mPublishId = wr::FramePublishId::INVALID; + const wr::FrameReadyParams mParams = { + .present = false, + .render = false, + .scrolled = false, + }; UniquePtr mRendererEvent; public: static WrNotifierEvent WakeUp(const bool aCompositeNeeded) { - return WrNotifierEvent(Tag::WakeUp, aCompositeNeeded); + wr::FrameReadyParams params = { + .present = aCompositeNeeded, + .render = aCompositeNeeded, + .scrolled = false, + }; + return WrNotifierEvent(Tag::WakeUp, params); } - static WrNotifierEvent NewFrameReady(const bool aCompositeNeeded, - const FramePublishId aPublishId) { - return WrNotifierEvent(Tag::NewFrameReady, aCompositeNeeded, aPublishId); + static WrNotifierEvent NewFrameReady(FramePublishId aPublishId, + const wr::FrameReadyParams* aParams) { + return WrNotifierEvent(Tag::NewFrameReady, aPublishId, *aParams); } static WrNotifierEvent ExternalEvent( @@ -406,18 +414,16 @@ class RenderThread final { return WrNotifierEvent(Tag::ExternalEvent, std::move(aRendererEvent)); } - bool CompositeNeeded() { - if (mTag == Tag::WakeUp || mTag == Tag::NewFrameReady) { - return mCompositeNeeded; - } - MOZ_ASSERT_UNREACHABLE("unexpected to be called"); - return false; + const wr::FrameReadyParams& FrameReadyParams() const { + MOZ_ASSERT(mTag == Tag::NewFrameReady || mTag == Tag::WakeUp, + "Unexpected NotiferEvent tag"); + return mParams; } FramePublishId PublishId() { if (mTag == Tag::NewFrameReady) { return mPublishId; } - MOZ_ASSERT_UNREACHABLE("unexpected to be called"); + MOZ_ASSERT_UNREACHABLE("Unexpected NotiferEvent tag"); return FramePublishId::INVALID; } UniquePtr ExternalEvent() { @@ -425,14 +431,15 @@ class RenderThread final { MOZ_ASSERT(mRendererEvent); return std::move(mRendererEvent); } - MOZ_ASSERT_UNREACHABLE("unexpected to be called"); + MOZ_ASSERT_UNREACHABLE("Unexpected NotiferEvent tag"); return nullptr; } }; explicit RenderThread(RefPtr aThread); - void HandleFrameOneDocInner(wr::WindowId aWindowId, bool aRender, + void HandleFrameOneDocInner(wr::WindowId aWindowId, + const wr::FrameReadyParams& aParams, bool aTrackedFrame, Maybe aPublishId); @@ -441,7 +448,7 @@ class RenderThread final { void InitDeviceTask(); void PostResumeShaderWarmupRunnable(); void ResumeShaderWarmup(); - void HandleFrameOneDoc(wr::WindowId aWindowId, bool aRender, + void HandleFrameOneDoc(wr::WindowId aWindowId, const wr::FrameReadyParams&, bool aTrackedFrame, Maybe aPublishId); void RunEvent(wr::WindowId aWindowId, UniquePtr aEvent, bool aViaWebRender); @@ -463,10 +470,10 @@ class RenderThread final { void PostWrNotifierEvents(WrWindowId aWindowId, WindowInfo* aInfo); void HandleWrNotifierEvents(WrWindowId aWindowId); void WrNotifierEvent_HandleWakeUp(wr::WindowId aWindowId, - bool aCompositeNeeded); + const wr::FrameReadyParams& aParams); void WrNotifierEvent_HandleNewFrameReady(wr::WindowId aWindowId, - bool aCompositeNeeded, - FramePublishId aPublishId); + wr::FramePublishId aPublishId, + const wr::FrameReadyParams& aParams); void WrNotifierEvent_HandleExternalEvent( wr::WindowId aWindowId, UniquePtr aRendererEvent); diff --git a/gfx/webrender_bindings/RendererOGL.cpp b/gfx/webrender_bindings/RendererOGL.cpp index ccd8bb80fab..48675f811d2 100644 --- a/gfx/webrender_bindings/RendererOGL.cpp +++ b/gfx/webrender_bindings/RendererOGL.cpp @@ -162,38 +162,56 @@ RenderedFrameId RendererOGL::UpdateAndRender( const Maybe& aReadbackSize, const Maybe& aReadbackFormat, const Maybe>& aReadbackBuffer, bool* aNeedsYFlip, - RendererStats* aOutStats) { + const wr::FrameReadyParams& aFrameParams, RendererStats* aOutStats) { mozilla::widget::WidgetRenderingContext widgetContext; #if defined(XP_MACOSX) widgetContext.mGL = mCompositor->gl(); #endif - if (!mCompositor->GetWidget()->PreRender(&widgetContext)) { - // XXX This could cause oom in webrender since pending_texture_updates is - // not handled. It needs to be addressed. - return RenderedFrameId(); - } - // XXX set clear color if MOZ_WIDGET_ANDROID is defined. + // If present is false, WebRender needs to render some offscreen content + // but we don't want to touch the window, so we avoid most interactions + // with mCompositor. + bool present = aFrameParams.present; - if (mThread->IsHandlingDeviceReset() || !mCompositor->BeginFrame()) { + LayoutDeviceIntSize size(0, 0); + auto bufferAge = 0; + bool fullRender = false; + + bool beginFrame = !mThread->IsHandlingDeviceReset(); + + if (beginFrame && present) { + if (!mCompositor->GetWidget()->PreRender(&widgetContext)) { + // XXX This could cause oom in webrender since pending_texture_updates is + // not handled. It needs to be addressed. + return RenderedFrameId(); + } + // XXX set clear color if MOZ_WIDGET_ANDROID is defined. + + if (!mCompositor->BeginFrame()) { + beginFrame = false; + } + + size = mCompositor->GetBufferSize(); + bufferAge = mCompositor->GetBufferAge(); + + fullRender = mCompositor->RequestFullRender(); + // When we're rendering to an external target, we want to render everything. + if (mCompositor->UsePartialPresent() && + (aReadbackBuffer.isSome() || + layers::ProfilerScreenshots::IsEnabled())) { + fullRender = true; + } + } + + if (!beginFrame) { CheckGraphicsResetStatus(gfx::DeviceResetDetectPlace::WR_BEGIN_FRAME, /* aForce */ true); - mCompositor->GetWidget()->PostRender(&widgetContext); return RenderedFrameId(); } - auto size = mCompositor->GetBufferSize(); - auto bufferAge = mCompositor->GetBufferAge(); - wr_renderer_update(mRenderer); - bool fullRender = mCompositor->RequestFullRender(); - // When we're rendering to an external target, we want to render everything. - if (mCompositor->UsePartialPresent() && - (aReadbackBuffer.isSome() || layers::ProfilerScreenshots::IsEnabled())) { - fullRender = true; - } if (fullRender) { wr_renderer_force_redraw(mRenderer); } @@ -203,42 +221,47 @@ RenderedFrameId RendererOGL::UpdateAndRender( bufferAge, aOutStats, &dirtyRects); FlushPipelineInfo(); if (!rendered) { - mCompositor->CancelFrame(); + if (present) { + mCompositor->CancelFrame(); + mCompositor->GetWidget()->PostRender(&widgetContext); + } RenderThread::Get()->HandleWebRenderError(WebRenderError::RENDER); - mCompositor->GetWidget()->PostRender(&widgetContext); return RenderedFrameId(); } - if (aReadbackBuffer.isSome()) { - MOZ_ASSERT(aReadbackSize.isSome()); - MOZ_ASSERT(aReadbackFormat.isSome()); - if (!mCompositor->MaybeReadback(aReadbackSize.ref(), aReadbackFormat.ref(), - aReadbackBuffer.ref(), aNeedsYFlip)) { - wr_renderer_readback(mRenderer, aReadbackSize.ref().width, - aReadbackSize.ref().height, aReadbackFormat.ref(), - &aReadbackBuffer.ref()[0], - aReadbackBuffer.ref().length()); - if (aNeedsYFlip) { - *aNeedsYFlip = !mCompositor->SurfaceOriginIsTopLeft(); + RenderedFrameId frameId; + + if (present) { + if (aReadbackBuffer.isSome()) { + MOZ_ASSERT(aReadbackSize.isSome()); + MOZ_ASSERT(aReadbackFormat.isSome()); + if (!mCompositor->MaybeReadback(aReadbackSize.ref(), + aReadbackFormat.ref(), + aReadbackBuffer.ref(), aNeedsYFlip)) { + wr_renderer_readback(mRenderer, aReadbackSize.ref().width, + aReadbackSize.ref().height, aReadbackFormat.ref(), + &aReadbackBuffer.ref()[0], + aReadbackBuffer.ref().length()); + if (aNeedsYFlip != nullptr) { + *aNeedsYFlip = !mCompositor->SurfaceOriginIsTopLeft(); + } } } - } - if (size.Width() != 0 && size.Height() != 0) { - if (!mCompositor->MaybeGrabScreenshot(size.ToUnknownSize())) { - mScreenshotGrabber.MaybeGrabScreenshot(this, size.ToUnknownSize()); + if (size.Width() != 0 && size.Height() != 0) { + if (!mCompositor->MaybeGrabScreenshot(size.ToUnknownSize())) { + mScreenshotGrabber.MaybeGrabScreenshot(this, size.ToUnknownSize()); + } } + + // Frame recording must happen before EndFrame, as we must ensure we read + // the contents of the back buffer before any calls to SwapBuffers which + // might invalidate it. + MaybeRecordFrame(mLastPipelineInfo); + frameId = mCompositor->EndFrame(dirtyRects); + mCompositor->GetWidget()->PostRender(&widgetContext); } - // Frame recording must happen before EndFrame, as we must ensure we read the - // contents of the back buffer before any calls to SwapBuffers which might - // invalidate it. - MaybeRecordFrame(mLastPipelineInfo); - - RenderedFrameId frameId = mCompositor->EndFrame(dirtyRects); - - mCompositor->GetWidget()->PostRender(&widgetContext); - #if defined(ENABLE_FRAME_LATENCY_LOG) if (mFrameStartTime) { uint32_t latencyMs = @@ -249,8 +272,10 @@ RenderedFrameId RendererOGL::UpdateAndRender( mFrameStartTime = TimeStamp(); #endif - if (!mCompositor->MaybeProcessScreenshotQueue()) { - mScreenshotGrabber.MaybeProcessQueue(this); + if (present) { + if (!mCompositor->MaybeProcessScreenshotQueue()) { + mScreenshotGrabber.MaybeProcessQueue(this); + } } // TODO: Flush pending actions such as texture deletions/unlocks and diff --git a/gfx/webrender_bindings/RendererOGL.h b/gfx/webrender_bindings/RendererOGL.h index 3ee8745c72a..8784fbe435e 100644 --- a/gfx/webrender_bindings/RendererOGL.h +++ b/gfx/webrender_bindings/RendererOGL.h @@ -65,7 +65,9 @@ class RendererOGL { RenderedFrameId UpdateAndRender(const Maybe& aReadbackSize, const Maybe& aReadbackFormat, const Maybe>& aReadbackBuffer, - bool* aNeedsYFlip, RendererStats* aOutStats); + bool* aNeedsYFlip, + const wr::FrameReadyParams& aFrameParams, + RendererStats* aOutStats); /// This can be called on the render thread only. void WaitForGPU(); diff --git a/gfx/webrender_bindings/WebRenderAPI.cpp b/gfx/webrender_bindings/WebRenderAPI.cpp index 9f7c43b33d1..7622f5dccdd 100644 --- a/gfx/webrender_bindings/WebRenderAPI.cpp +++ b/gfx/webrender_bindings/WebRenderAPI.cpp @@ -708,8 +708,13 @@ void WebRenderAPI::Readback(const TimeStamp& aStartTime, gfx::IntSize size, void Run(RenderThread& aRenderThread, WindowId aWindowId) override { RendererStats stats = {0}; - aRenderThread.UpdateAndRender(aWindowId, VsyncId(), mStartTime, - /* aRender */ true, Some(mSize), + wr::FrameReadyParams params = { + .present = true, + .render = true, + .scrolled = false, + }; + aRenderThread.UpdateAndRender(aWindowId, VsyncId(), mStartTime, params, + Some(mSize), wr::SurfaceFormatToImageFormat(mFormat), Some(mBuffer), &stats, mNeedsYFlip); layers::AutoCompleteTask complete(mTask); diff --git a/gfx/webrender_bindings/src/bindings.rs b/gfx/webrender_bindings/src/bindings.rs index 0b0bc51232e..e4dfb9163fb 100644 --- a/gfx/webrender_bindings/src/bindings.rs +++ b/gfx/webrender_bindings/src/bindings.rs @@ -557,11 +557,11 @@ unsafe impl Send for CppNotifier {} extern "C" { fn wr_notifier_wake_up(window_id: WrWindowId, composite_needed: bool); - fn wr_notifier_new_frame_ready(window_id: WrWindowId, composite_needed: bool, publish_id: FramePublishId); + fn wr_notifier_new_frame_ready(window_id: WrWindowId, publish_id: FramePublishId, params: &FrameReadyParams); fn wr_notifier_external_event(window_id: WrWindowId, raw_event: usize); fn wr_schedule_render(window_id: WrWindowId, reasons: RenderReasons); // NOTE: This moves away from pipeline_info. - fn wr_finished_scene_build(window_id: WrWindowId, pipeline_info: &mut WrPipelineInfo); + fn wr_schedule_frame_after_scene_build(window_id: WrWindowId, pipeline_info: &mut WrPipelineInfo); fn wr_transaction_notification_notified(handler: usize, when: Checkpoint); } @@ -579,9 +579,9 @@ impl RenderNotifier for CppNotifier { } } - fn new_frame_ready(&self, _: DocumentId, _scrolled: bool, composite_needed: bool, publish_id: FramePublishId) { + fn new_frame_ready(&self, _: DocumentId, publish_id: FramePublishId, params: &FrameReadyParams) { unsafe { - wr_notifier_new_frame_ready(self.window_id, composite_needed, publish_id); + wr_notifier_new_frame_ready(self.window_id, publish_id, params); } } @@ -1028,16 +1028,15 @@ impl SceneBuilderHooks for APZCallbacks { } } - fn post_scene_swap(&self, _document_ids: &Vec, info: PipelineInfo) { + fn post_scene_swap(&self, _document_ids: &Vec, info: PipelineInfo, schedule_frame: bool) { let mut info = WrPipelineInfo::new(&info); unsafe { apz_post_scene_swap(self.window_id, &info); } - // After a scene swap we should schedule a render for the next vsync, - // otherwise there's no guarantee that the new scene will get rendered - // anytime soon - unsafe { wr_finished_scene_build(self.window_id, &mut info) } + if schedule_frame { + unsafe { wr_schedule_frame_after_scene_build(self.window_id, &mut info) } + } gecko_profiler_end_marker("SceneBuilding"); } diff --git a/gfx/wr/example-compositor/compositor/src/main.rs b/gfx/wr/example-compositor/compositor/src/main.rs index eecaf219ab1..dcaea5d4e54 100644 --- a/gfx/wr/example-compositor/compositor/src/main.rs +++ b/gfx/wr/example-compositor/compositor/src/main.rs @@ -209,7 +209,7 @@ impl RenderNotifier for Notifier { fn wake_up(&self, _composite_needed: bool) {} - fn new_frame_ready(&self, _: DocumentId, _scrolled: bool, _composite_needed: bool, _: FramePublishId) { + fn new_frame_ready(&self, _: DocumentId, _: FramePublishId, _params: &FrameReadyParams) { self.tx.send(()).ok(); } } diff --git a/gfx/wr/examples/common/boilerplate.rs b/gfx/wr/examples/common/boilerplate.rs index 05e62a52e94..2632e08cd1e 100644 --- a/gfx/wr/examples/common/boilerplate.rs +++ b/gfx/wr/examples/common/boilerplate.rs @@ -38,10 +38,9 @@ impl RenderNotifier for Notifier { fn new_frame_ready(&self, _: DocumentId, - _scrolled: bool, - composite_needed: bool, - _: FramePublishId) { - self.wake_up(composite_needed); + _: FramePublishId, + params: &FrameReadyParams) { + self.wake_up(params.render); } } diff --git a/gfx/wr/examples/multiwindow.rs b/gfx/wr/examples/multiwindow.rs index f4dfb409b62..b9a67bd9433 100644 --- a/gfx/wr/examples/multiwindow.rs +++ b/gfx/wr/examples/multiwindow.rs @@ -43,10 +43,9 @@ impl RenderNotifier for Notifier { fn new_frame_ready(&self, _: DocumentId, - _scrolled: bool, - composite_needed: bool, - _: FramePublishId) { - self.wake_up(composite_needed); + _: FramePublishId, + params: &FrameReadyParams) { + self.wake_up(params.render); } } diff --git a/gfx/wr/webrender/src/clip.rs b/gfx/wr/webrender/src/clip.rs index 3b5ad7f92a7..2e768d28e72 100644 --- a/gfx/wr/webrender/src/clip.rs +++ b/gfx/wr/webrender/src/clip.rs @@ -288,6 +288,18 @@ impl ClipTree { &self.nodes[id.0 as usize] } + pub fn get_parent(&self, id: ClipNodeId) -> Option { + // Invalid ids point to the first item in the nodes vector which + // has an invalid id for the parent so we don't need to handle + // `id` being invalid separately. + let parent = self.nodes[id.0 as usize].parent; + if parent == ClipNodeId::NONE { + return None; + } + + return Some(parent) + } + /// Retrieve a clip tree leaf by id pub fn get_leaf(&self, id: ClipLeafId) -> &ClipTreeLeaf { &self.leaves[id.0 as usize] @@ -716,6 +728,10 @@ impl ClipTreeBuilder { false } + pub fn get_parent(&self, id: ClipNodeId) -> Option { + self.tree.get_parent(id) + } + /// Finalize building and return the clip-tree pub fn finalize(&mut self) -> ClipTree { // Note: After this, the builder's clip tree does not hold allocations and diff --git a/gfx/wr/webrender/src/render_backend.rs b/gfx/wr/webrender/src/render_backend.rs index 8663b1b9b69..a636b13c5b9 100644 --- a/gfx/wr/webrender/src/render_backend.rs +++ b/gfx/wr/webrender/src/render_backend.rs @@ -1590,7 +1590,12 @@ impl RenderBackend { } else if render_frame { doc.rendered_frame_is_valid = true; } - self.notifier.new_frame_ready(document_id, scroll, render_frame, self.frame_publish_id); + let params = api::FrameReadyParams { + present, + render: render_frame, + scrolled: scroll, + }; + self.notifier.new_frame_ready(document_id, self.frame_publish_id, ¶ms); } if !doc.hit_tester_is_valid { @@ -1974,7 +1979,12 @@ impl RenderBackend { ); self.result_tx.send(msg_publish).unwrap(); - self.notifier.new_frame_ready(id, false, true, self.frame_publish_id); + let params = api::FrameReadyParams { + present: true, + render: true, + scrolled: false, + }; + self.notifier.new_frame_ready(id, self.frame_publish_id, ¶ms); // We deserialized the state of the frame so we don't want to build // it (but we do want to update the scene builder's state) diff --git a/gfx/wr/webrender/src/renderer/init.rs b/gfx/wr/webrender/src/renderer/init.rs index 7a503a2611f..f8df42c1e92 100644 --- a/gfx/wr/webrender/src/renderer/init.rs +++ b/gfx/wr/webrender/src/renderer/init.rs @@ -76,7 +76,7 @@ pub trait SceneBuilderHooks { /// This is called after each scene swap occurs. The PipelineInfo contains /// the updated epochs and pipelines removed in the new scene compared to /// the old scene. - fn post_scene_swap(&self, document_id: &Vec, info: PipelineInfo); + fn post_scene_swap(&self, document_id: &Vec, info: PipelineInfo, schedule_frame: bool); /// This is called after a resource update operation on the scene builder /// thread, in the case where resource updates were applied without a scene /// build. diff --git a/gfx/wr/webrender/src/renderer/mod.rs b/gfx/wr/webrender/src/renderer/mod.rs index 4e6a4a88591..43f25c6e81d 100644 --- a/gfx/wr/webrender/src/renderer/mod.rs +++ b/gfx/wr/webrender/src/renderer/mod.rs @@ -1274,10 +1274,16 @@ impl Renderer { .remove(&doc_id) .unwrap(); + let size = if !device_size.is_empty() { + Some(device_size) + } else { + None + }; + let result = self.render_impl( doc_id, &mut doc, - Some(device_size), + size, buffer_age, ); @@ -3127,7 +3133,7 @@ impl Renderer { &mut self.renderer_errors, &mut self.profile, ); - + ( textures, instance ) }, ResolvedExternalSurfaceColorData::Rgb{ ref plane, .. } => { diff --git a/gfx/wr/webrender/src/scene_builder_thread.rs b/gfx/wr/webrender/src/scene_builder_thread.rs index a7bd3cfc8a0..ed8ef7187fa 100644 --- a/gfx/wr/webrender/src/scene_builder_thread.rs +++ b/gfx/wr/webrender/src/scene_builder_thread.rs @@ -726,6 +726,13 @@ impl SceneBuilderThread { Vec::new() }; + // Unless a transaction generates a frame immediately, the compositor should + // schedule one whenever appropriate (probably at the next vsync) to present + // the changes in the scene. + let compositor_should_schedule_a_frame = !txns.iter().any(|txn| { + txn.render_frame + }); + #[cfg(feature = "capture")] match self.capture_config { Some(ref config) => self.send(SceneBuilderResult::CapturedTransactions(txns, config.clone(), result_tx)), @@ -740,7 +747,8 @@ impl SceneBuilderThread { let swap_result = result_rx.unwrap().recv(); Telemetry::stop_and_accumulate_sceneswap_time(timer_id); self.hooks.as_ref().unwrap().post_scene_swap(&document_ids, - pipeline_info); + pipeline_info, + compositor_should_schedule_a_frame); // Once the hook is done, allow the RB thread to resume if let Ok(SceneSwapResult::Complete(resume_tx)) = swap_result { resume_tx.send(()).ok(); diff --git a/gfx/wr/webrender/src/scene_building.rs b/gfx/wr/webrender/src/scene_building.rs index d45e514422c..85498b5d883 100644 --- a/gfx/wr/webrender/src/scene_building.rs +++ b/gfx/wr/webrender/src/scene_building.rs @@ -751,19 +751,43 @@ impl<'a> SceneBuilder<'a> { // scroll event (for tile caching to work usefully we specifically // want to draw things even if they are outside the viewport). let mut shared_clip_node_id = None; - for cluster in &prim_list.clusters { - for prim_instance in &prim_instances[cluster.prim_range()] { - let leaf = clip_tree_builder.get_leaf(prim_instance.clip_leaf_id); - shared_clip_node_id = match shared_clip_node_id { - Some(current) => { - Some(clip_tree_builder.find_lowest_common_ancestor( - current, - leaf.node_id, - )) - } - None => Some(leaf.node_id) - }; + // Snapshot picture are special. All clips belonging to parents + // *must* be extracted from the snapshot, so we rely on this optimization + // taking out parent clips and it overrides other conditions. + // In addition we need to ensure that only parent clips are extracted. + let is_snapshot = pictures[pic_index.0].snapshot.is_some(); + + if is_snapshot { + // In the general case, if all of the children of a picture share the + // same clips, then these clips are hoisted up in the parent picture, + // however we rely on child clips of snapshotted pictures to be baked + // into the snapshot. + // Snapshotted pictures use the parent of their clip node (if any) + // as the clip root, to ensure that the parent clip hierarchy is + // extracted from clip chains inside the snapshot, and to make sure + // that child clips of the snapshots are not hoisted out of the + // snapshot even when all children of the snapshotted picture share + // a clip. + if let Some(idx) = prim_index { + let clip_node = clip_tree_builder.get_leaf(prim_instances[idx].clip_leaf_id).node_id; + shared_clip_node_id = clip_tree_builder.get_parent(clip_node); + } + } else { + for cluster in &prim_list.clusters { + for prim_instance in &prim_instances[cluster.prim_range()] { + let leaf = clip_tree_builder.get_leaf(prim_instance.clip_leaf_id); + + shared_clip_node_id = match shared_clip_node_id { + Some(current) => { + Some(clip_tree_builder.find_lowest_common_ancestor( + current, + leaf.node_id, + )) + } + None => Some(leaf.node_id) + }; + } } } @@ -792,11 +816,6 @@ impl<'a> SceneBuilder<'a> { _ => false, }; - // Snapshot picture are special. All clips belonging to parents - // *must* be extracted from the snapshot, so we rely on this optimization - // taking out parent clips and it overrides other conditions. - let is_snapshot = pictures[pic_index.0].snapshot.is_some(); - // It is only safe to apply this optimisation if the old pic clip node // is the direct parent of the new LCA node. If this is not the case // then there could be other more restrictive clips in between the two diff --git a/gfx/wr/webrender_api/src/lib.rs b/gfx/wr/webrender_api/src/lib.rs index 736fed42a01..dbdfb687a8c 100644 --- a/gfx/wr/webrender_api/src/lib.rs +++ b/gfx/wr/webrender_api/src/lib.rs @@ -259,6 +259,13 @@ pub struct MinimapData { pub root_content_scroll_id: u64 } +#[repr(C)] +pub struct FrameReadyParams { + pub present: bool, + pub render: bool, + pub scrolled: bool, +} + /// A handler to integrate WebRender with the thread that contains the `Renderer`. pub trait RenderNotifier: Send { /// @@ -270,7 +277,7 @@ pub trait RenderNotifier: Send { composite_needed: bool, ); /// Notify the thread containing the `Renderer` that a new frame is ready. - fn new_frame_ready(&self, _: DocumentId, scrolled: bool, composite_needed: bool, frame_publish_id: FramePublishId); + fn new_frame_ready(&self, _: DocumentId, publish_id: FramePublishId, params: &FrameReadyParams); /// A Gecko-specific notification mechanism to get some code executed on the /// `Renderer`'s thread, mostly replaced by `NotificationHandler`. You should /// probably use the latter instead. diff --git a/gfx/wr/wrench/reftests/image/reftest.list b/gfx/wr/wrench/reftests/image/reftest.list index bf41c1bd3a8..895fb045212 100644 --- a/gfx/wr/wrench/reftests/image/reftest.list +++ b/gfx/wr/wrench/reftests/image/reftest.list @@ -32,3 +32,4 @@ fuzzy(3,3000) == snapshot-shadow.yaml snapshot-shadow-ref.yaml # reference should be updated to reflect that something needs to be rendered # instead of leaving the snapshot empty. == snapshot-perspective-01.yaml empty.yaml +== snapshot-complex-clip.yaml snapshot-complex-clip-ref.yaml diff --git a/gfx/wr/wrench/reftests/image/snapshot-complex-clip-ref.yaml b/gfx/wr/wrench/reftests/image/snapshot-complex-clip-ref.yaml new file mode 100644 index 00000000000..86a00c0feaa --- /dev/null +++ b/gfx/wr/wrench/reftests/image/snapshot-complex-clip-ref.yaml @@ -0,0 +1,53 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 1000, 1000] + items: + - type: stacking-context + bounds: [0, 0, 200, 200] + items: + - type: clip + id: 101 + complex: + - rect: [10, 10, 180, 180] + radius: [32, 16] + clip-mode: clip-out + - type: clip-chain + id: 201 + clips: [101] + - type: stacking-context + bounds: [0, 0, 200, 200] + clip-chain: 201 + items: + - type: rect + bounds: [0, 0, 200, 200] + color: green + clip-chain: 201 + - type: rect + bounds: [100, 100, 100, 100] + color: [100, 100, 100] + + - type: stacking-context + bounds: [200, 0, 200, 200] + items: + - type: clip + id: 102 + complex: + - rect: [10, 10, 180, 180] + radius: [32, 16] + clip-mode: clip-out + - type: clip-chain + id: 202 + clips: [102] + - type: stacking-context + bounds: [0, 0, 200, 200] + clip-chain: 202 + items: + - type: rect + bounds: [0, 0, 200, 200] + color: green + clip-chain: 202 + - type: rect + bounds: [100, 100, 100, 100] + color: [100, 100, 100] diff --git a/gfx/wr/wrench/reftests/image/snapshot-complex-clip.yaml b/gfx/wr/wrench/reftests/image/snapshot-complex-clip.yaml new file mode 100644 index 00000000000..877ee4746b3 --- /dev/null +++ b/gfx/wr/wrench/reftests/image/snapshot-complex-clip.yaml @@ -0,0 +1,40 @@ +# This test case contains a snapshotted picture with a single +# child stacking context with a clip applied. In the general +# clips are hoisted out of picture if all of their children share +# it, but we rely on the clip being baked in the snapshot so +# This test should that snapshotted picture and the snapshot image +# both affected by the clip. +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 1000, 1000] + items: + - type: stacking-context + bounds: [0, 0, 200, 200] + snapshot: + name: "snap0" + items: + - type: clip + id: 101 + complex: + - rect: [10, 10, 180, 180] + radius: [32, 16] + clip-mode: clip-out + - type: clip-chain + id: 201 + clips: [101] + - type: stacking-context + bounds: [0, 0, 200, 200] + clip-chain: 201 + items: + - type: rect + bounds: [0, 0, 200, 200] + color: green + clip-chain: 201 + - type: rect + bounds: [100, 100, 100, 100] + color: [100, 100, 100] + + - image: snapshot(snap0) + bounds: [200, 0, 200, 200] diff --git a/gfx/wr/wrench/src/main.rs b/gfx/wr/wrench/src/main.rs index fb008b00ff9..6ec10199498 100644 --- a/gfx/wr/wrench/src/main.rs +++ b/gfx/wr/wrench/src/main.rs @@ -487,12 +487,11 @@ impl RenderNotifier for Notifier { fn new_frame_ready(&self, _: DocumentId, - _scrolled: bool, - composite_needed: bool, - _: FramePublishId) { + _: FramePublishId, + params: &FrameReadyParams) { // TODO(gw): Refactor wrench so that it can take advantage of cases // where no composite is required when appropriate. - self.wake_up(composite_needed); + self.wake_up(params.render); } } diff --git a/gfx/wr/wrench/src/wrench.rs b/gfx/wr/wrench/src/wrench.rs index 19b23ec2a2a..eba666e6541 100644 --- a/gfx/wr/wrench/src/wrench.rs +++ b/gfx/wr/wrench/src/wrench.rs @@ -112,11 +112,8 @@ impl RenderNotifier for Notifier { self.update(false); } - fn new_frame_ready(&self, _: DocumentId, - scrolled: bool, - _composite_needed: bool, - _: FramePublishId) { - self.update(!scrolled); + fn new_frame_ready(&self, _: DocumentId, _: FramePublishId, params: &FrameReadyParams) { + self.update(!params.scrolled); } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 93d1a77e342..17925ec362b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -60,7 +60,7 @@ material = "1.12.0" osslicenses-plugin = "0.10.6" play-review = "2.0.2" play-services-ads-id = "18.1.0" -play-services-base = "18.6.0" +play-services-base = "18.7.0" play-services-fido = "21.2.0" protobuf = "4.30.2" # Keep Protobuf in sync with the version used by AppServices. diff --git a/ipc/glue/GeckoChildProcessHost.cpp b/ipc/glue/GeckoChildProcessHost.cpp index 19ad76f8854..9bb8314bd98 100644 --- a/ipc/glue/GeckoChildProcessHost.cpp +++ b/ipc/glue/GeckoChildProcessHost.cpp @@ -611,10 +611,6 @@ void GeckoChildProcessHost::SetEnv(const char* aKey, const char* aValue) { bool GeckoChildProcessHost::PrepareLaunch( geckoargs::ChildProcessArgs& aExtraOpts) { - if (CrashReporter::GetEnabled()) { - CrashReporter::OOPInit(); - } - #if defined(XP_LINUX) && defined(MOZ_SANDBOX) if (!SandboxLaunch::Configure(mProcessType, mSandbox, aExtraOpts, mLaunchOptions.get())) { @@ -1125,13 +1121,20 @@ Result BaseProcessLauncher::DoSetup() { #if defined(MOZ_WIDGET_COCOA) || defined(XP_WIN) geckoargs::sCrashReporter.Put(CrashReporter::GetChildNotificationPipe(), mChildArgs); -#elif defined(XP_UNIX) +#elif defined(XP_UNIX) && !defined(XP_IOS) UniqueFileHandle childCrashFd = CrashReporter::GetChildNotificationPipe(); if (!childCrashFd) { return Err(LaunchError("DuplicateFileHandle failed")); } geckoargs::sCrashReporter.Put(std::move(childCrashFd), mChildArgs); -#endif + +# if !defined(MOZ_WIDGET_ANDROID) + CrashReporter::ProcessId pid = CrashReporter::GetCrashHelperPid(); + if (pid != base::kInvalidProcessId) { + geckoargs::sCrashHelperPid.Put(pid, mChildArgs); + } +# endif // !defined(MOZ_WIDGET_ANDROID) +#endif // XP_UNIX && !XP_IOS } return Ok(); diff --git a/js/src/aclocal.m4 b/js/src/aclocal.m4 deleted file mode 100644 index 4151cbd8878..00000000000 --- a/js/src/aclocal.m4 +++ /dev/null @@ -1,23 +0,0 @@ -dnl -dnl Local autoconf macros used with mozilla -dnl The contents of this file are under the Public Domain. -dnl - -builtin(include, ../../build/autoconf/hooks.m4)dnl -builtin(include, ../../build/autoconf/config.status.m4)dnl -builtin(include, ../../build/autoconf/altoptions.m4)dnl - -define([__MOZ_AC_INIT_PREPARE], defn([AC_INIT_PREPARE])) -define([AC_INIT_PREPARE], -[if test -z "$srcdir"; then - srcdir=`dirname "[$]0"` -fi -srcdir="$srcdir/../.." -__MOZ_AC_INIT_PREPARE($1) -]) - -dnl This won't actually read the mozconfig, but data that configure.py -dnl will have placed for us to read. Configure.py takes care of not reading -dnl the mozconfig where appropriate but can still give us some variables -dnl to read. -MOZ_READ_MOZCONFIG(.) diff --git a/js/src/configure b/js/src/configure index e2c672e40d1..db41b0e5b10 100755 --- a/js/src/configure +++ b/js/src/configure @@ -6,7 +6,6 @@ SRCDIR=$(dirname $0) TOPSRCDIR="$SRCDIR"/../.. PYTHON3="${PYTHON3:-python3}" -export OLD_CONFIGURE="$SRCDIR"/old-configure set -- "$@" --enable-project=js diff --git a/js/src/devtools/rootAnalysis/mozconfig.common b/js/src/devtools/rootAnalysis/mozconfig.common index c68fb6a26cc..3be4036ada8 100644 --- a/js/src/devtools/rootAnalysis/mozconfig.common +++ b/js/src/devtools/rootAnalysis/mozconfig.common @@ -32,6 +32,6 @@ ac_add_options --disable-replace-malloc # -Wignored-attributes is very verbose due to attributes being # ignored on template parameters. ANALYSIS_EXTRA_CFLAGS="-Wno-attributes -Wno-ignored-attributes" -CFLAGS="$CFLAGS $ANALYSIS_EXTRA_CFLAGS" -CPPFLAGS="$CPPFLAGS $ANALYSIS_EXTRA_CFLAGS" -CXXFLAGS="$CXXFLAGS $ANALYSIS_EXTRA_CFLAGS" +export CFLAGS="$CFLAGS $ANALYSIS_EXTRA_CFLAGS" +export CPPFLAGS="$CPPFLAGS $ANALYSIS_EXTRA_CFLAGS" +export CXXFLAGS="$CXXFLAGS $ANALYSIS_EXTRA_CFLAGS" diff --git a/js/src/make-source-package.py b/js/src/make-source-package.py index 87342529123..82db8dd50c5 100755 --- a/js/src/make-source-package.py +++ b/js/src/make-source-package.py @@ -112,7 +112,6 @@ print("") rsync_filter_list = """ # Top-level config and build files -+ /aclocal.m4 + /client.mk + /configure.py + /LICENSE @@ -383,33 +382,6 @@ def copy_cargo_toml(): f.write(content) -def generate_configure(): - """Generate configure files to avoid build dependency on autoconf-2.13""" - - src_old_configure_in_file = topsrc_dir / "js" / "src" / "old-configure.in" - dest_old_configure_file = target_dir / "js" / "src" / "old-configure" - - js_src_dir = topsrc_dir / "js" / "src" - - env = os.environ.copy() - env["M4"] = m4 - env["AWK"] = awk - env["AC_MACRODIR"] = topsrc_dir / "build" / "autoconf" - - with dest_old_configure_file.open("w") as f: - subprocess.run( - [ - "sh", - str(topsrc_dir / "build" / "autoconf" / "autoconf.sh"), - f"--localdir={js_src_dir}", - str(src_old_configure_in_file), - ], - stdout=f, - check=True, - env=env, - ) - - def copy_file(filename, content): """Copy an existing file from the staging area, or create a new file with the given contents if it does not exist.""" @@ -452,7 +424,6 @@ def stage(): create_target_dir() sync_files() copy_cargo_toml() - generate_configure() copy_file("INSTALL", INSTALL_CONTENT) copy_file("README", README_CONTENT) copy_file("mozconfig", MOZCONFIG_DEBUG_CONTENT) diff --git a/js/src/old-configure.in b/js/src/old-configure.in deleted file mode 100644 index f507cae34e1..00000000000 --- a/js/src/old-configure.in +++ /dev/null @@ -1,23 +0,0 @@ -dnl -*- Mode: Autoconf; tab-width: 4; indent-tabs-mode: nil; -*- -dnl vi: set tabstop=4 shiftwidth=4 expandtab syntax=m4: -dnl This Source Code Form is subject to the terms of the Mozilla Public -dnl License, v. 2.0. If a copy of the MPL was not distributed with this -dnl file, You can obtain one at http://mozilla.org/MPL/2.0/. - -dnl Process this file with autoconf to produce a configure script. -dnl ======================================================== -AC_PREREQ(2.13) -AC_INIT(js/src/jsapi.h) -AC_CONFIG_AUX_DIR(${srcdir}/build/autoconf) - -dnl ======================================================== -dnl = -dnl = Maintainer debug option (no --enable equivalent) -dnl = -dnl ======================================================== - -dnl Spit out some output -dnl ======================================================== -MOZ_CREATE_CONFIG_STATUS() - -rm -fr confdefs* $ac_clean_files diff --git a/js/sub.configure b/js/sub.configure deleted file mode 100644 index a331a9356bf..00000000000 --- a/js/sub.configure +++ /dev/null @@ -1,89 +0,0 @@ -# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - - -@depends(build_environment) -@imports("logging") -@imports(_from="__builtin__", _import="object") -@imports(_from="mozbuild.configure.util", _import="ConfigureOutputHandler") -def old_js_configure(build_env): - class PrefixOutput(object): - def __init__(self, prefix, fh): - self._fh = fh - self._begin_line = True - self._prefix = prefix - - def write(self, content): - if self._begin_line: - self._fh.write(self._prefix) - self._fh.write(("\n" + self._prefix).join(content.splitlines())) - self._begin_line = content.endswith("\n") - if self._begin_line: - self._fh.write("\n") - - def flush(self): - self._fh.flush() - - logger = logging.getLogger("moz.configure") - formatter = logging.Formatter("js/src> %(levelname)s: %(message)s") - for handler in logger.handlers: - handler.setFormatter(formatter) - if isinstance(handler, ConfigureOutputHandler): - handler._stdout = PrefixOutput("js/src> ", handler._stdout) - return os.path.join(build_env.topsrcdir, "js", "src", "old-configure") - - -@depends(old_configure.substs, mozconfig) -def old_js_configure_env(substs, mozconfig): - substs = dict(substs) - # Here, we mimic what we used to do from old-configure, which makes this - # all awkward. - - # Variables that were explicitly exported from old-configure, and those - # explicitly set in the environment when invoking old-configure, were - # automatically inherited from subconfigure. We assume the relevant ones - # have a corresponding AC_SUBST in old-configure, making them available - # in `substs`. - extra_env = {} - - for var in ( - "MOZ_DEV_EDITION", - "STLPORT_LIBS", - ): - if var in substs: - value = substs[var] - elif ( - mozconfig - and var in mozconfig - and not mozconfig[var][1].startswith("removed") - ): - value = mozconfig[var][0] - else: - continue - if isinstance(value, list): - value = " ".join(value) - extra_env[var] = value - - return extra_env - - -old_js_configure = old_configure_for(old_js_configure, extra_env=old_js_configure_env) -set_config("OLD_JS_CONFIGURE_SUBSTS", old_js_configure.substs) -set_config("OLD_JS_CONFIGURE_DEFINES", old_js_configure.defines) - - -@dependable -@imports("logging") -@imports(_from="mozbuild.configure.util", _import="ConfigureOutputHandler") -def post_old_js_configure(): - # Restore unprefixed logging. - formatter = logging.Formatter("%(levelname)s: %(message)s") - logger = logging.getLogger("moz.configure") - for handler in logger.handlers: - handler.setFormatter(formatter) - if isinstance(handler, ConfigureOutputHandler): - handler._stdout.flush() - handler._stdout = handler._stdout._fh diff --git a/js/xpconnect/src/XPCShellImpl.cpp b/js/xpconnect/src/XPCShellImpl.cpp index 24b89ce1aa9..46fcabc7953 100644 --- a/js/xpconnect/src/XPCShellImpl.cpp +++ b/js/xpconnect/src/XPCShellImpl.cpp @@ -1200,7 +1200,22 @@ int XRE_XPCShellMain(int argc, char** argv, char** envp, const char* val = getenv("MOZ_CRASHREPORTER"); if (val && *val && !CrashReporter::IsDummy()) { - rv = CrashReporter::SetExceptionHandler(greDir, true); + nsCOMPtr greBinDir; + bool persistent = false; + rv = dirprovider.GetFile(NS_GRE_BIN_DIR, &persistent, + getter_AddRefs(greBinDir)); + if (NS_FAILED(rv)) { + printf("Could not get the GreBinD directory\n"); + return 1; + } + +#if defined(MOZ_WIDGET_ANDROID) + CrashReporter::SetCrashHelperPipes( + aShellData->crashChildNotificationSocket, + aShellData->crashHelperSocket); +#endif // defined(MOZ_WIDGET_ANDROID) + + rv = CrashReporter::SetExceptionHandler(greBinDir, true); if (NS_FAILED(rv)) { printf("CrashReporter::SetExceptionHandler failed!\n"); return 1; diff --git a/layout/base/PresShell.cpp b/layout/base/PresShell.cpp index 83f455d6a00..c3116954a13 100644 --- a/layout/base/PresShell.cpp +++ b/layout/base/PresShell.cpp @@ -6814,9 +6814,8 @@ already_AddRefed PresShell::GetEventTargetContent( WidgetEvent* aEvent) { nsCOMPtr content = GetCurrentEventContent(); if (!content) { - nsIFrame* currentEventFrame = GetCurrentEventFrame(); - if (currentEventFrame) { - currentEventFrame->GetContentForEvent(aEvent, getter_AddRefs(content)); + if (nsIFrame* currentEventFrame = GetCurrentEventFrame()) { + content = currentEventFrame->GetContentForEvent(aEvent); NS_ASSERTION(!content || content->GetComposedDoc() == mDocument, "handing out content from a different doc"); } @@ -8297,11 +8296,8 @@ bool PresShell::EventHandler::MaybeDiscardOrDelayMouseEvent( return true; } - nsCOMPtr targetContent; - aFrameToHandleEvent->GetContentForEvent(aGUIEvent, - getter_AddRefs(targetContent)); - if (targetContent) { - aGUIEvent->mTarget = targetContent; + if (auto* target = aFrameToHandleEvent->GetContentForEvent(aGUIEvent)) { + aGUIEvent->mTarget = target; } nsCOMPtr eventTarget = aGUIEvent->mTarget; @@ -9267,15 +9263,15 @@ nsresult PresShell::EventHandler::DispatchEventToDOM( if (!eventTarget) { nsCOMPtr targetContent; if (mPresShell->mCurrentEventTarget.mFrame) { - rv = mPresShell->mCurrentEventTarget.mFrame->GetContentForEvent( - aEvent, getter_AddRefs(targetContent)); + targetContent = + mPresShell->mCurrentEventTarget.mFrame->GetContentForEvent(aEvent); if (targetContent && !targetContent->IsElement() && IsForbiddenDispatchingToNonElementContent(aEvent->mMessage)) { targetContent = targetContent->GetInclusiveFlattenedTreeAncestorElement(); } } - if (NS_SUCCEEDED(rv) && targetContent) { + if (targetContent) { eventTarget = targetContent; } else if (GetDocument()) { eventTarget = GetDocument(); @@ -12283,8 +12279,7 @@ void PresShell::EventHandler::EventTargetData:: void PresShell::EventHandler::EventTargetData::SetContentForEventFromFrame( WidgetGUIEvent* aGUIEvent) { MOZ_ASSERT(mFrame); - mContent = nullptr; - mFrame->GetContentForEvent(aGUIEvent, getter_AddRefs(mContent)); + mContent = mFrame->GetContentForEvent(aGUIEvent); AssertIfEventTargetContentAndFrameContentMismatch(aGUIEvent); } @@ -12302,9 +12297,7 @@ void PresShell::EventHandler::EventTargetData:: // If we know the event, we can compute the target correctly. if (aGUIEvent) { - nsCOMPtr content; - mFrame->GetContentForEvent(aGUIEvent, getter_AddRefs(content)); - MOZ_ASSERT(mContent == content); + MOZ_ASSERT(mContent == mFrame->GetContentForEvent(aGUIEvent)); return; } diff --git a/layout/base/TouchManager.cpp b/layout/base/TouchManager.cpp index c61b22569f9..97a02123338 100644 --- a/layout/base/TouchManager.cpp +++ b/layout/base/TouchManager.cpp @@ -137,8 +137,7 @@ nsIFrame* TouchManager::SetupTarget(WidgetTouchEvent* aEvent, aEvent, touch->mRefPoint, relativeTo); target = FindFrameTargetedByInputEvent(aEvent, relativeTo, eventPoint); if (target) { - nsCOMPtr targetContent; - target->GetContentForEvent(aEvent, getter_AddRefs(targetContent)); + nsIContent* targetContent = target->GetContentForEvent(aEvent); touch->SetTouchTarget(targetContent ? targetContent->GetAsElementOrParentElement() : nullptr); @@ -237,9 +236,7 @@ nsIFrame* TouchManager::SuppressInvalidPointsAndGetTargetedFrame( touch->mIsTouchEventSuppressed = true; } else { targetFrame = newTargetFrame; - nsCOMPtr newTargetContent; - targetFrame->GetContentForEvent(aEvent, - getter_AddRefs(newTargetContent)); + nsIContent* newTargetContent = targetFrame->GetContentForEvent(aEvent); touch->SetTouchTarget( newTargetContent ? newTargetContent->GetAsElementOrParentElement() : nullptr); diff --git a/layout/generic/nsCanvasFrame.cpp b/layout/generic/nsCanvasFrame.cpp index 6eabab9365f..47f01db1905 100644 --- a/layout/generic/nsCanvasFrame.cpp +++ b/layout/generic/nsCanvasFrame.cpp @@ -763,18 +763,14 @@ void nsCanvasFrame::Reflow(nsPresContext* aPresContext, NS_FRAME_TRACE_REFLOW_OUT("nsCanvasFrame::Reflow", aStatus); } -nsresult nsCanvasFrame::GetContentForEvent(const WidgetEvent* aEvent, - nsIContent** aContent) { - NS_ENSURE_ARG_POINTER(aContent); - nsresult rv = nsIFrame::GetContentForEvent(aEvent, aContent); - if (NS_FAILED(rv) || !*aContent) { - nsIFrame* kid = mFrames.FirstChild(); - if (kid) { - rv = kid->GetContentForEvent(aEvent, aContent); - } +nsIContent* nsCanvasFrame::GetContentForEvent(const WidgetEvent* aEvent) const { + if (nsIContent* content = nsIFrame::GetContentForEvent(aEvent)) { + return content; } - - return rv; + if (const nsIFrame* kid = mFrames.FirstChild()) { + return kid->GetContentForEvent(aEvent); + } + return nullptr; } #ifdef DEBUG_FRAME_DUMP diff --git a/layout/generic/nsCanvasFrame.h b/layout/generic/nsCanvasFrame.h index 33ab4e791bc..1f83fb447e6 100644 --- a/layout/generic/nsCanvasFrame.h +++ b/layout/generic/nsCanvasFrame.h @@ -101,9 +101,7 @@ class nsCanvasFrame final : public nsContainerFrame, #ifdef DEBUG_FRAME_DUMP nsresult GetFrameName(nsAString& aResult) const override; #endif - nsresult GetContentForEvent(const mozilla::WidgetEvent* aEvent, - nsIContent** aContent) override; - + nsIContent* GetContentForEvent(const mozilla::WidgetEvent*) const override; nsRect CanvasArea() const; protected: diff --git a/layout/generic/nsGridContainerFrame.cpp b/layout/generic/nsGridContainerFrame.cpp index b013d4fde00..008459ca946 100644 --- a/layout/generic/nsGridContainerFrame.cpp +++ b/layout/generic/nsGridContainerFrame.cpp @@ -264,6 +264,101 @@ static Maybe ComputeTransferredSize( aAxis, aWM, rdSize, aBoxSizingAdjustment.EnsureAndGet())); } +// A cached result for a grid item's block-axis measuring reflow. This +// cache prevents us from doing exponential reflows in cases of deeply +// nested grid frames. +// +// We store the cached value in the grid item's frame property table. +// +// We cache the following as a "key" +// - The size of the grid area in the item's inline axis +// - The item's block axis baseline padding +// ...and we cache the following as the "value", +// - The item's border-box BSize +class nsGridContainerFrame::CachedBAxisMeasurement final { + public: + NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(Prop, CachedBAxisMeasurement) + CachedBAxisMeasurement(const nsIFrame* aFrame, const LogicalSize& aCBSize, + const nscoord aBSize) + : mKey(aFrame, aCBSize), mBSize(aBSize) {} + + CachedBAxisMeasurement() = default; + + bool IsValidFor(const nsIFrame* aFrame, const LogicalSize& aCBSize) const { + if (aFrame->IsSubtreeDirty()) { + return false; + } + const Maybe maybeKey = Key::TryHash(aFrame, aCBSize); + return maybeKey.isSome() && mKey == *maybeKey; + } + + static bool CanCacheMeasurement(const nsIFrame* aFrame, + const LogicalSize& aCBSize) { + return Key::CanHash(aFrame, aCBSize); + } + + nscoord BSize() const { return mBSize; } + + void Update(const nsIFrame* aFrame, const LogicalSize& aCBSize, + const nscoord aBSize) { + mKey.UpdateHash(aFrame, aCBSize); + mBSize = aBSize; + } + + private: + class Key { + // mHashKey is generated by combining these 2 variables together + // 1. The containing block size in the item's inline axis used + // for measuring reflow + // 2. The item's baseline padding property + uint32_t mHashKey; + + explicit Key(uint32_t aHashKey) : mHashKey(aHashKey) {} + + public: + Key() = default; + + Key(const nsIFrame* aFrame, const LogicalSize& aCBSize) { + UpdateHash(aFrame, aCBSize); + } + + void UpdateHash(const nsIFrame* aFrame, const LogicalSize& aCBSize) { + const Maybe maybeKey = TryHash(aFrame, aCBSize); + MOZ_ASSERT(maybeKey.isSome()); + mHashKey = maybeKey->mHashKey; + } + + static Maybe TryHash(const nsIFrame* aFrame, + const LogicalSize& aCBSize) { + const nscoord gridAreaISize = aCBSize.ISize(aFrame->GetWritingMode()); + const nscoord bBaselinePaddingProperty = + abs(aFrame->GetProperty(nsIFrame::BBaselinePadProperty())); + + const uint_fast8_t bitsNeededForISize = FloorLog2(gridAreaISize) + 1; + + const uint_fast8_t bitsNeededForBBaselinePadding = + FloorLog2(bBaselinePaddingProperty) + 1; + if (bitsNeededForISize + bitsNeededForBBaselinePadding > 32) { + return Nothing(); + } + const uint32_t hashKey = (gridAreaISize << (32 - bitsNeededForISize)) | + bBaselinePaddingProperty; + return Some(Key(hashKey)); + } + + static bool CanHash(const nsIFrame* aFrame, const LogicalSize& aCBSize) { + return TryHash(aFrame, aCBSize).isSome(); + } + + bool operator==(const Key& aOther) const { + return mHashKey == aOther.mHashKey; + } + }; + + Key mKey; + nscoord mBSize; +}; + // The input sizes for calculating the number of repeat(auto-fill/fit) tracks. // https://drafts.csswg.org/css-grid-2/#auto-repeat struct RepeatTrackSizingInput { @@ -10450,6 +10545,12 @@ nsGridContainerFrame* nsGridContainerFrame::GetGridFrameWithComputedInfo( return gridFrame; } +void nsGridContainerFrame::MarkCachedGridMeasurementsDirty( + nsIFrame* aItemFrame) { + MOZ_ASSERT(aItemFrame->IsGridItem()); + aItemFrame->RemoveProperty(CachedBAxisMeasurement::Prop()); +} + // TODO: This is a rather dumb implementation of nsILineIterator, but it's // better than our pre-existing behavior. Ideally, we should probably use the // grid information to return a meaningful number of lines etc. diff --git a/layout/generic/nsGridContainerFrame.h b/layout/generic/nsGridContainerFrame.h index 959bb43fc44..15737b39e38 100644 --- a/layout/generic/nsGridContainerFrame.h +++ b/layout/generic/nsGridContainerFrame.h @@ -157,6 +157,19 @@ class nsGridContainerFrame final : public nsContainerFrame, nsFrameList&& aChildList) override; #endif + bool CanProvideLineIterator() const final { return true; } + nsILineIterator* GetLineIterator() final { return this; } + int32_t GetNumLines() const final; + bool IsLineIteratorFlowRTL() final; + mozilla::Result GetLine(int32_t aLineNumber) final; + int32_t FindLineContaining(nsIFrame* aFrame, int32_t aStartLine = 0) final; + NS_IMETHOD FindFrameAt(int32_t aLineNumber, nsPoint aPos, + nsIFrame** aFrameFound, bool* aPosIsBeforeFirstFrame, + bool* aPosIsAfterLastFrame) final; + NS_IMETHOD CheckLineOrder(int32_t aLine, bool* aIsReordered, + nsIFrame** aFirstVisual, + nsIFrame** aLastVisual) final; + /** * Return the containing block for aChild which MUST be an abs.pos. child * of a grid container and that container must have been reflowed. @@ -295,6 +308,12 @@ class nsGridContainerFrame final : public nsContainerFrame, MOZ_CAN_RUN_SCRIPT_BOUNDARY static nsGridContainerFrame* GetGridFrameWithComputedInfo(nsIFrame* aFrame); + /** + * Callback for nsIFrame::MarkIntrinsicISizesDirty() on a grid item. + */ + static void MarkCachedGridMeasurementsDirty(nsIFrame* aItemFrame); + + class CachedBAxisMeasurement; struct Subgrid; struct UsedTrackSizes; struct TrackSize; @@ -318,9 +337,7 @@ class nsGridContainerFrame final : public nsContainerFrame, }; protected: - typedef mozilla::LogicalPoint LogicalPoint; typedef mozilla::LogicalRect LogicalRect; - typedef mozilla::LogicalSize LogicalSize; typedef mozilla::WritingMode WritingMode; struct Grid; struct GridArea; @@ -557,116 +574,6 @@ class nsGridContainerFrame final : public nsContainerFrame, // Our baselines, one per BaselineSharingGroup per axis. PerLogicalAxis> mBaseline; - - public: - // A cached result for a grid item's block-axis measuring reflow. This - // cache prevents us from doing exponential reflows in cases of deeply - // nested grid frames. - // - // We store the cached value in the grid item's frame property table. - // - // We cache the following as a "key" - // - The size of the grid area in the item's inline axis - // - The item's block axis baseline padding - // ...and we cache the following as the "value", - // - The item's border-box BSize - class CachedBAxisMeasurement { - public: - NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(Prop, CachedBAxisMeasurement) - CachedBAxisMeasurement(const nsIFrame* aFrame, const LogicalSize& aCBSize, - const nscoord aBSize) - : mKey(aFrame, aCBSize), mBSize(aBSize) {} - - CachedBAxisMeasurement() = default; - - bool IsValidFor(const nsIFrame* aFrame, const LogicalSize& aCBSize) const { - if (aFrame->IsSubtreeDirty()) { - return false; - } - const mozilla::Maybe maybeKey = Key::TryHash(aFrame, aCBSize); - return maybeKey.isSome() && mKey == *maybeKey; - } - - static bool CanCacheMeasurement(const nsIFrame* aFrame, - const LogicalSize& aCBSize) { - return Key::CanHash(aFrame, aCBSize); - } - - nscoord BSize() const { return mBSize; } - - void Update(const nsIFrame* aFrame, const LogicalSize& aCBSize, - const nscoord aBSize) { - mKey.UpdateHash(aFrame, aCBSize); - mBSize = aBSize; - } - - private: - class Key { - // mHashKey is generated by combining these 2 variables together - // 1. The containing block size in the item's inline axis used - // for measuring reflow - // 2. The item's baseline padding property - uint32_t mHashKey; - - explicit Key(uint32_t aHashKey) : mHashKey(aHashKey) {} - - public: - Key() = default; - - Key(const nsIFrame* aFrame, const LogicalSize& aCBSize) { - UpdateHash(aFrame, aCBSize); - } - - void UpdateHash(const nsIFrame* aFrame, const LogicalSize& aCBSize) { - const mozilla::Maybe maybeKey = TryHash(aFrame, aCBSize); - MOZ_ASSERT(maybeKey.isSome()); - mHashKey = maybeKey->mHashKey; - } - - static mozilla::Maybe TryHash(const nsIFrame* aFrame, - const LogicalSize& aCBSize) { - const nscoord gridAreaISize = aCBSize.ISize(aFrame->GetWritingMode()); - const nscoord bBaselinePaddingProperty = - abs(aFrame->GetProperty(nsIFrame::BBaselinePadProperty())); - - const uint_fast8_t bitsNeededForISize = - mozilla::FloorLog2(gridAreaISize) + 1; - - const uint_fast8_t bitsNeededForBBaselinePadding = - mozilla::FloorLog2(bBaselinePaddingProperty) + 1; - if (bitsNeededForISize + bitsNeededForBBaselinePadding > 32) { - return mozilla::Nothing(); - } - const uint32_t hashKey = (gridAreaISize << (32 - bitsNeededForISize)) | - bBaselinePaddingProperty; - return mozilla::Some(Key(hashKey)); - } - - static bool CanHash(const nsIFrame* aFrame, const LogicalSize& aCBSize) { - return TryHash(aFrame, aCBSize).isSome(); - } - - bool operator==(const Key& aOther) const { - return mHashKey == aOther.mHashKey; - } - }; - - Key mKey; - nscoord mBSize; - }; - - bool CanProvideLineIterator() const final { return true; } - nsILineIterator* GetLineIterator() final { return this; } - int32_t GetNumLines() const final; - bool IsLineIteratorFlowRTL() final; - mozilla::Result GetLine(int32_t aLineNumber) final; - int32_t FindLineContaining(nsIFrame* aFrame, int32_t aStartLine = 0) final; - NS_IMETHOD FindFrameAt(int32_t aLineNumber, nsPoint aPos, - nsIFrame** aFrameFound, bool* aPosIsBeforeFirstFrame, - bool* aPosIsAfterLastFrame) final; - NS_IMETHOD CheckLineOrder(int32_t aLine, bool* aIsReordered, - nsIFrame** aFirstVisual, - nsIFrame** aLastVisual) final; }; #endif /* nsGridContainerFrame_h___ */ diff --git a/layout/generic/nsIFrame.cpp b/layout/generic/nsIFrame.cpp index e068d445ee5..31d7c381a25 100644 --- a/layout/generic/nsIFrame.cpp +++ b/layout/generic/nsIFrame.cpp @@ -4428,12 +4428,20 @@ void nsIFrame::MarkAbsoluteFramesForDisplayList( } } -nsresult nsIFrame::GetContentForEvent(const WidgetEvent* aEvent, - nsIContent** aContent) { - nsIFrame* f = nsLayoutUtils::GetNonGeneratedAncestor(this); - *aContent = f->GetContent(); - NS_IF_ADDREF(*aContent); - return NS_OK; +nsIContent* nsIFrame::GetContentForEvent(const WidgetEvent* aEvent) const { + if (!IsGeneratedContentFrame()) { + return GetContent(); + } + const nsIFrame* generatedRoot = this; + while (true) { + auto* parent = nsLayoutUtils::GetParentOrPlaceholderFor(generatedRoot); + if (!parent || !parent->IsGeneratedContentFrame()) { + break; + } + generatedRoot = parent; + } + // Return the non-generated ancestor. + return generatedRoot->GetContent()->GetParent(); } void nsIFrame::FireDOMEvent(const nsAString& aDOMEventName, @@ -6099,11 +6107,13 @@ void nsIFrame::MarkIntrinsicISizesDirty() { nsFlexContainerFrame::MarkCachedFlexMeasurementsDirty(this); } + if (IsGridItem()) { + nsGridContainerFrame::MarkCachedGridMeasurementsDirty(this); + } + if (HasAnyStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT)) { nsFontInflationData::MarkFontInflationDataTextDirty(this); } - - RemoveProperty(nsGridContainerFrame::CachedBAxisMeasurement::Prop()); } void nsIFrame::MarkSubtreeDirty() { @@ -9243,9 +9253,10 @@ static nsresult GetNextPrevLineFromBlockFrame(PeekOffsetStruct* aPos, if (!aPos->FrameContentIsInAncestorLimiter(resultFrame)) { return NS_ERROR_FAILURE; } - // check to see if this is ANOTHER blockframe inside the other one if so - // then call into its lines - if (resultFrame->CanProvideLineIterator()) { + // Check to see if this is ANOTHER blockframe inside the other one that + // we should look inside. + if (resultFrame->CanProvideLineIterator() && + IsRelevantBlockFrame(resultFrame)) { aPos->mResultFrame = resultFrame; return NS_OK; } diff --git a/layout/generic/nsIFrame.h b/layout/generic/nsIFrame.h index e7c163dc7c5..de245b74e70 100644 --- a/layout/generic/nsIFrame.h +++ b/layout/generic/nsIFrame.h @@ -2357,8 +2357,7 @@ class nsIFrame : public nsQueryFrame { int16_t DetermineDisplaySelection(); public: - virtual nsresult GetContentForEvent(const mozilla::WidgetEvent* aEvent, - nsIContent** aContent); + virtual nsIContent* GetContentForEvent(const mozilla::WidgetEvent*) const; // This structure keeps track of the content node and offsets associated with // a point; there is a primary and a secondary offset associated with any diff --git a/layout/generic/nsImageFrame.cpp b/layout/generic/nsImageFrame.cpp index c9afd8d54ec..25afef63911 100644 --- a/layout/generic/nsImageFrame.cpp +++ b/layout/generic/nsImageFrame.cpp @@ -2690,7 +2690,7 @@ bool nsImageFrame::IsServerImageMap() { return mContent->AsElement()->HasAttr(nsGkAtoms::ismap); } -CSSIntPoint nsImageFrame::TranslateEventCoords(const nsPoint& aPoint) { +CSSIntPoint nsImageFrame::TranslateEventCoords(const nsPoint& aPoint) const { const nsRect contentRect = GetContentRectRelativeToSelf(); // Subtract out border and padding here so that the coordinates are // now relative to the content area of this frame. @@ -2736,39 +2736,23 @@ bool nsImageFrame::IsLeafDynamic() const { return !shadow; } -nsresult nsImageFrame::GetContentForEvent(const WidgetEvent* aEvent, - nsIContent** aContent) { - NS_ENSURE_ARG_POINTER(aContent); - - nsIFrame* f = nsLayoutUtils::GetNonGeneratedAncestor(this); - if (f != this) { - return f->GetContentForEvent(aEvent, aContent); - } - - // XXX We need to make this special check for area element's capturing the - // mouse due to bug 135040. Remove it once that's fixed. - nsIContent* capturingContent = aEvent->HasMouseEventMessage() - ? PresShell::GetCapturingContent() - : nullptr; - if (capturingContent && capturingContent->GetPrimaryFrame() == this) { - *aContent = capturingContent; - NS_IF_ADDREF(*aContent); - return NS_OK; - } - - if (nsImageMap* map = GetImageMap()) { +nsIContent* nsImageFrame::GetContentForEvent(const WidgetEvent* aEvent) const { + if (mImageMap) { + // XXX We need to make this special check for area element's capturing the + // mouse due to bug 135040. Remove it once that's fixed. + nsIContent* capturingContent = aEvent->HasMouseEventMessage() + ? PresShell::GetCapturingContent() + : nullptr; + if (capturingContent && capturingContent->GetPrimaryFrame() == this) { + return capturingContent; + } const CSSIntPoint p = TranslateEventCoords( nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, RelativeTo{this})); - nsCOMPtr area = map->GetArea(p); - if (area) { - area.forget(aContent); - return NS_OK; + if (auto* area = mImageMap->GetArea(p)) { + return area; } } - - *aContent = GetContent(); - NS_IF_ADDREF(*aContent); - return NS_OK; + return nsIFrame::GetContentForEvent(aEvent); } // XXX what should clicks on transparent pixels do? diff --git a/layout/generic/nsImageFrame.h b/layout/generic/nsImageFrame.h index 89dea65fdfb..fc7f323b4ae 100644 --- a/layout/generic/nsImageFrame.h +++ b/layout/generic/nsImageFrame.h @@ -88,8 +88,7 @@ class nsImageFrame : public nsAtomicContainerFrame, public nsIReflowCallback { nsReflowStatus&) override; bool IsLeafDynamic() const override; - nsresult GetContentForEvent(const mozilla::WidgetEvent*, - nsIContent** aContent) final; + nsIContent* GetContentForEvent(const mozilla::WidgetEvent*) const final; nsresult HandleEvent(nsPresContext*, mozilla::WidgetGUIEvent*, nsEventStatus*) override; Cursor GetCursor(const nsPoint&) override; @@ -266,7 +265,7 @@ class nsImageFrame : public nsAtomicContainerFrame, public nsIReflowCallback { // Translate a point that is relative to our frame into a localized CSS pixel // coordinate that is relative to the content area of this frame (inside the // border+padding). - mozilla::CSSIntPoint TranslateEventCoords(const nsPoint& aPoint); + mozilla::CSSIntPoint TranslateEventCoords(const nsPoint&) const; bool GetAnchorHREFTargetAndNode(nsIURI** aHref, nsString& aTarget, nsIContent** aNode); diff --git a/media/libopus/celt/arch.h b/media/libopus/celt/arch.h index ce3ba304757..a843d1bcc03 100644 --- a/media/libopus/celt/arch.h +++ b/media/libopus/celt/arch.h @@ -103,6 +103,8 @@ void celt_fatal(const char *str, const char *file, int line) #define MAX32(a,b) ((a) > (b) ? (a) : (b)) /**< Maximum 32-bit value. */ #define IMIN(a,b) ((a) < (b) ? (a) : (b)) /**< Minimum int value. */ #define IMAX(a,b) ((a) > (b) ? (a) : (b)) /**< Maximum int value. */ +#define FMIN(a,b) ((a) < (b) ? (a) : (b)) /**< Minimum float value. */ +#define FMAX(a,b) ((a) > (b) ? (a) : (b)) /**< Maximum float value. */ #define UADD32(a,b) ((a)+(b)) #define USUB32(a,b) ((a)-(b)) #define MAXG(a,b) MAX32(a, b) diff --git a/media/libopus/celt/arm/arm_celt_map.c b/media/libopus/celt/arm/arm_celt_map.c index d9980444ea6..931c011ddd1 100644 --- a/media/libopus/celt/arm/arm_celt_map.c +++ b/media/libopus/celt/arm/arm_celt_map.c @@ -46,6 +46,14 @@ void (*const CELT_FLOAT2INT16_IMPL[OPUS_ARCHMASK+1])(const float * OPUS_RESTRICT celt_float2int16_neon,/* NEON */ celt_float2int16_neon /* DOTPROD */ }; + +int (*const OPUS_LIMIT2_CHECKWITHIN1_IMPL[OPUS_ARCHMASK+1])(float * samples, int cnt) = { + opus_limit2_checkwithin1_c, /* ARMv4 */ + opus_limit2_checkwithin1_c, /* EDSP */ + opus_limit2_checkwithin1_c, /* Media */ + opus_limit2_checkwithin1_neon,/* NEON */ + opus_limit2_checkwithin1_neon /* DOTPROD */ +}; # endif # endif diff --git a/media/libopus/celt/arm/celt_neon_intr.c b/media/libopus/celt/arm/celt_neon_intr.c index 32b6e5ac0fc..b87bebc1103 100644 --- a/media/libopus/celt/arm/celt_neon_intr.c +++ b/media/libopus/celt/arm/celt_neon_intr.c @@ -86,8 +86,85 @@ void celt_float2int16_neon(const float * OPUS_RESTRICT in, short * OPUS_RESTRICT out[i] = FLOAT2INT16(in[i]); } } + +int opus_limit2_checkwithin1_neon(float *samples, int cnt) +{ + const float hardclipMin = -2.0f; + const float hardclipMax = 2.0f; + + int i = 0; + int exceeding1 = 0; + int nextIndex = 0; + +#if defined(__ARM_NEON) + const int BLOCK_SIZE = 16; + const int blockedSize = cnt / BLOCK_SIZE * BLOCK_SIZE; + + float32x4_t min_all_0 = vdupq_n_f32(0.0f); + float32x4_t min_all_1 = vdupq_n_f32(0.0f); + float32x4_t max_all_0 = vdupq_n_f32(0.0f); + float32x4_t max_all_1 = vdupq_n_f32(0.0f); + + float max, min; + + for (i = 0; i < blockedSize; i += BLOCK_SIZE) + { + const float32x4_t orig_a = vld1q_f32(&samples[i + 0]); + const float32x4_t orig_b = vld1q_f32(&samples[i + 4]); + const float32x4_t orig_c = vld1q_f32(&samples[i + 8]); + const float32x4_t orig_d = vld1q_f32(&samples[i + 12]); + max_all_0 = vmaxq_f32(max_all_0, vmaxq_f32(orig_a, orig_b)); + max_all_1 = vmaxq_f32(max_all_1, vmaxq_f32(orig_c, orig_d)); + min_all_0 = vminq_f32(min_all_0, vminq_f32(orig_a, orig_b)); + min_all_1 = vminq_f32(min_all_1, vminq_f32(orig_c, orig_d)); + } + + max = vmaxvf(vmaxq_f32(max_all_0, max_all_1)); + min = vminvf(vminq_f32(min_all_0, min_all_1)); + + if (min < hardclipMin || max > hardclipMax) + { + const float32x4_t hardclipMinReg = vdupq_n_f32(hardclipMin); + const float32x4_t hardclipMaxReg = vdupq_n_f32(hardclipMax); + for (i = 0; i < blockedSize; i += BLOCK_SIZE) + { + const float32x4_t orig_a = vld1q_f32(&samples[i + 0]); + const float32x4_t orig_b = vld1q_f32(&samples[i + 4]); + const float32x4_t orig_c = vld1q_f32(&samples[i + 8]); + const float32x4_t orig_d = vld1q_f32(&samples[i + 12]); + const float32x4_t clipped_a = vminq_f32(hardclipMaxReg, vmaxq_f32(orig_a, hardclipMinReg)); + const float32x4_t clipped_b = vminq_f32(hardclipMaxReg, vmaxq_f32(orig_b, hardclipMinReg)); + const float32x4_t clipped_c = vminq_f32(hardclipMaxReg, vmaxq_f32(orig_c, hardclipMinReg)); + const float32x4_t clipped_d = vminq_f32(hardclipMaxReg, vmaxq_f32(orig_d, hardclipMinReg)); + vst1q_f32(&samples[i + 0], clipped_a); + vst1q_f32(&samples[i + 4], clipped_b); + vst1q_f32(&samples[i + 8], clipped_c); + vst1q_f32(&samples[i + 12], clipped_d); + } + } + + nextIndex = blockedSize; + exceeding1 |= max > 1.0f || min < -1.0f; + #endif + for (i = nextIndex; i < cnt; i++) + { + const float origVal = samples[i]; + float clippedVal = origVal; + clippedVal = MAX16(hardclipMin, clippedVal); + clippedVal = MIN16(hardclipMax, clippedVal); + samples[i] = clippedVal; + + exceeding1 |= origVal > 1.0f || origVal < -1.0f; + } + + return !exceeding1; +} + +#endif + + #if defined(FIXED_POINT) #include diff --git a/media/libopus/celt/arm/mathops_arm.h b/media/libopus/celt/arm/mathops_arm.h index ced719d3223..b1f91699797 100644 --- a/media/libopus/celt/arm/mathops_arm.h +++ b/media/libopus/celt/arm/mathops_arm.h @@ -46,6 +46,30 @@ static inline int32x4_t vroundf(float32x4_t x) # endif } +static inline float vminvf(float32x4_t a) +{ +#if defined(__aarch64__) + return vminvq_f32(a); +#else + float32x2_t xy = vmin_f32(vget_low_f32(a), vget_high_f32(a)); + float x = vget_lane_f32(xy, 0); + float y = vget_lane_f32(xy, 1); + return x < y ? x : y; +#endif +} + +static inline float vmaxvf(float32x4_t a) +{ +#if defined(__aarch64__) + return vmaxvq_f32(a); +#else + float32x2_t xy = vmax_f32(vget_low_f32(a), vget_high_f32(a)); + float x = vget_lane_f32(xy, 0); + float y = vget_lane_f32(xy, 1); + return x > y ? x : y; +#endif +} + void celt_float2int16_neon(const float * OPUS_RESTRICT in, short * OPUS_RESTRICT out, int cnt); # if defined(OPUS_HAVE_RTCD) && \ (defined(OPUS_ARM_MAY_HAVE_NEON_INTR) && !defined(OPUS_ARM_PRESUME_NEON_INTR)) @@ -60,6 +84,20 @@ extern void # define OVERRIDE_FLOAT2INT16 (1) # define celt_float2int16(in, out, cnt, arch) ((void)(arch), celt_float2int16_neon(in, out, cnt)) # endif + +int opus_limit2_checkwithin1_neon(float * samples, int cnt); +# if defined(OPUS_HAVE_RTCD) && \ + (defined(OPUS_ARM_MAY_HAVE_NEON_INTR) && !defined(OPUS_ARM_PRESUME_NEON_INTR)) +extern int (*const OPUS_LIMIT2_CHECKWITHIN1_IMPL[OPUS_ARCHMASK+1])(float * samples, int cnt); + +# define OVERRIDE_LIMIT2_CHECKWITHIN1 (1) +# define opus_limit2_checkwithin1(samples, cnt, arch) \ + ((*OPUS_LIMIT2_CHECKWITHIN1_IMPL[(arch)&OPUS_ARCHMASK])(samples, cnt)) + +# elif defined(OPUS_ARM_PRESUME_NEON_INTR) +# define OVERRIDE_LIMIT2_CHECKWITHIN1 (1) +# define opus_limit2_checkwithin1(samples, cnt, arch) ((void)(arch), opus_limit2_checkwithin1_neon(samples, cnt)) +# endif # endif #endif /* MATHOPS_ARM_H */ diff --git a/media/libopus/celt/celt_decoder.c b/media/libopus/celt/celt_decoder.c index 7a304f98729..28c94a05812 100644 --- a/media/libopus/celt/celt_decoder.c +++ b/media/libopus/celt/celt_decoder.c @@ -1042,8 +1042,34 @@ int celt_decode_with_ec_dred(CELTDecoder * OPUS_RESTRICT st, const unsigned char st->end = end = IMAX(1, mode->effEBands-2*(data0>>5)); LM = (data0>>3)&0x3; C = 1 + ((data0>>2)&0x1); - data++; - len--; + if ((data[0] & 0x03) == 0x03) { + data++; + len--; + if (len<=0) + return OPUS_INVALID_PACKET; + if (data[0] & 0x40) { + int p; + int padding=0; + data++; + len--; + do { + int tmp; + if (len<=0) + return OPUS_INVALID_PACKET; + p = *data++; + len--; + tmp = p==255 ? 254: p; + len -= tmp; + padding += tmp; + } while (p==255); + padding--; + if (len <= 0 || padding<0) return OPUS_INVALID_PACKET; + } + } else + { + data++; + len--; + } if (LM>mode->maxLM) return OPUS_INVALID_PACKET; if (frame_size < mode->shortMdctSize<1 || x[i*C]<-1) - break; + i = N; + } else { + for (i=curr;i1 || x[i*C]<-1) + break; + } } if (i==N) { @@ -135,6 +159,12 @@ OPUS_EXPORT void opus_pcm_soft_clip(float *_x, int N, int C, float *declip_mem) declip_mem[c] = a; } } + +OPUS_EXPORT void opus_pcm_soft_clip(float *_x, int N, int C, float *declip_mem) +{ + opus_pcm_soft_clip_impl(_x, N, C, declip_mem, 0); +} + #endif int encode_size(int size, unsigned char *data) diff --git a/media/libopus/src/opus_decoder.c b/media/libopus/src/opus_decoder.c index d5dd71c67bc..e4b33766920 100644 --- a/media/libopus/src/opus_decoder.c +++ b/media/libopus/src/opus_decoder.c @@ -814,7 +814,7 @@ int opus_decode_native(OpusDecoder *st, const unsigned char *data, OPUS_PRINT_INT(nb_samples); #ifndef FIXED_POINT if (soft_clip) - opus_pcm_soft_clip(pcm, nb_samples, st->channels, st->softclip_mem); + opus_pcm_soft_clip_impl(pcm, nb_samples, st->channels, st->softclip_mem, st->arch); else st->softclip_mem[0]=st->softclip_mem[1]=0; #endif diff --git a/media/libopus/src/opus_encoder.c b/media/libopus/src/opus_encoder.c index 8e4aae37ff3..883401309b0 100644 --- a/media/libopus/src/opus_encoder.c +++ b/media/libopus/src/opus_encoder.c @@ -1151,7 +1151,8 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_res *pcm, int frame_si #endif ALLOC_STACK; - max_data_bytes = IMIN(1276, out_data_bytes); + /* Just avoid insane packet sizes here, but the real bounds are applied later on. */ + max_data_bytes = IMIN(1276*6, out_data_bytes); st->rangeFinal = 0; if (frame_size <= 0 || max_data_bytes <= 0) @@ -1265,7 +1266,7 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_res *pcm, int frame_si st->bitrate_bps -= dred_bitrate_bps; #endif if (max_data_bytes<3 || st->bitrate_bps < 3*frame_rate*8 - || (frame_rate<50 && (max_data_bytes*frame_rate<300 || st->bitrate_bps < 2400))) + || (frame_rate<50 && (max_data_bytes*(opus_int32)frame_rate<300 || st->bitrate_bps < 2400))) { /*If the space is too low to do something useful, emit 'PLC' frames.*/ int tocmode = st->mode; @@ -1761,7 +1762,7 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_res *pcm, int frame_si } static opus_int32 opus_encode_frame_native(OpusEncoder *st, const opus_res *pcm, int frame_size, - unsigned char *data, opus_int32 max_data_bytes, + unsigned char *data, opus_int32 orig_max_data_bytes, int float_api, int first_frame, #ifdef ENABLE_DRED opus_int32 dred_bitrate_bps, @@ -1777,6 +1778,7 @@ static opus_int32 opus_encode_frame_native(OpusEncoder *st, const opus_res *pcm, const CELTMode *celt_mode; int i; int ret=0; + int max_data_bytes; opus_int32 nBytes; ec_enc enc; int bytes_target; @@ -1797,6 +1799,7 @@ static opus_int32 opus_encode_frame_native(OpusEncoder *st, const opus_res *pcm, VARDECL(opus_res, tmp_prefill); SAVE_STACK; + max_data_bytes = IMIN(orig_max_data_bytes, 1276); st->rangeFinal = 0; silk_enc = (char*)st+st->silk_enc_offset; celt_enc = (CELTEncoder*)((char*)st+st->celt_enc_offset); @@ -2497,12 +2500,12 @@ static opus_int32 opus_encode_frame_native(OpusEncoder *st, const opus_res *pcm, #endif if (apply_padding) { - if (opus_packet_pad(data, ret, max_data_bytes) != OPUS_OK) + if (opus_packet_pad(data, ret, orig_max_data_bytes) != OPUS_OK) { RESTORE_STACK; return OPUS_INTERNAL_ERROR; } - ret = max_data_bytes; + ret = orig_max_data_bytes; } RESTORE_STACK; return ret; diff --git a/media/libopus/src/opus_private.h b/media/libopus/src/opus_private.h index 19deb6ee210..c673c3e986d 100644 --- a/media/libopus/src/opus_private.h +++ b/media/libopus/src/opus_private.h @@ -177,6 +177,8 @@ void downmix_int(const void *_x, opus_val32 *sub, int subframe, int offset, int void downmix_int24(const void *_x, opus_val32 *sub, int subframe, int offset, int c1, int c2, int C); int is_digital_silence(const opus_res* pcm, int frame_size, int channels, int lsb_depth); +void opus_pcm_soft_clip_impl(float *_x, int N, int C, float *declip_mem, int arch); + int encode_size(int size, unsigned char *data); opus_int32 frame_size_select(opus_int32 frame_size, int variable_duration, opus_int32 Fs); diff --git a/mobile/android/android-components/components/compose/base/src/main/res/values-eu/strings.xml b/mobile/android/android-components/components/compose/base/src/main/res/values-eu/strings.xml new file mode 100644 index 00000000000..08a4e66719c --- /dev/null +++ b/mobile/android/android-components/components/compose/base/src/main/res/values-eu/strings.xml @@ -0,0 +1,11 @@ + + + + Erakutsi ezkutatutako testua + + Garbitu testua + diff --git a/mobile/android/android-components/components/compose/base/src/main/res/values-hy-rAM/strings.xml b/mobile/android/android-components/components/compose/base/src/main/res/values-hy-rAM/strings.xml new file mode 100644 index 00000000000..2620242cae8 --- /dev/null +++ b/mobile/android/android-components/components/compose/base/src/main/res/values-hy-rAM/strings.xml @@ -0,0 +1,11 @@ + + + + Ցուցադրել թաքնված տեքստը + + Մաքրել տեքստը + diff --git a/mobile/android/android-components/components/compose/base/src/main/res/values-rm/strings.xml b/mobile/android/android-components/components/compose/base/src/main/res/values-rm/strings.xml new file mode 100644 index 00000000000..907d1b49bd7 --- /dev/null +++ b/mobile/android/android-components/components/compose/base/src/main/res/values-rm/strings.xml @@ -0,0 +1,11 @@ + + + + Mussar il text zuppà + + Stizzar il text + diff --git a/mobile/android/android-components/components/feature/app-links/src/main/res/values-fa/strings.xml b/mobile/android/android-components/components/feature/app-links/src/main/res/values-fa/strings.xml index bdefb5b51e3..43f2b286dfe 100644 --- a/mobile/android/android-components/components/feature/app-links/src/main/res/values-fa/strings.xml +++ b/mobile/android/android-components/components/feature/app-links/src/main/res/values-fa/strings.xml @@ -4,6 +4,11 @@ گشودن در… گشودن در کاره؟ فعالیت شما ممکن است دیگر خصوصی نباشد. + + در %s باز شود؟ فعالیت شما ممکن است دیگر خصوص نماند. باز کردن در برنامه دیگر ยกเลิก + + ตรวจสอบการเชื่อมต่อของคุณแล้วลองอีกครั้ง %1$s ไม่สามารถดาวน์โหลดไฟล์ชนิดนี้ diff --git a/mobile/android/android-components/components/feature/downloads/src/main/res/values-zh-rCN/strings.xml b/mobile/android/android-components/components/feature/downloads/src/main/res/values-zh-rCN/strings.xml index bfcbbc628ab..cdd5dc79e86 100644 --- a/mobile/android/android-components/components/feature/downloads/src/main/res/values-zh-rCN/strings.xml +++ b/mobile/android/android-components/components/feature/downloads/src/main/res/values-zh-rCN/strings.xml @@ -13,6 +13,16 @@ 下载完毕 下载失败 + + 要重新下载文件吗?(%1$s) + + 要下载文件吗?(%1$s) 下载(%1$s) @@ -21,6 +31,18 @@ 重新下载 取消 + + “%1$s”已存在。 + + 未下载“%1$s”。 + + 请检查网络连接,然后重试。 %1$s 无法下载此种文件类型 diff --git a/mobile/android/android-components/components/lib/push-firebase/build.gradle b/mobile/android/android-components/components/lib/push-firebase/build.gradle index fac109002ff..0a9fb07ec70 100644 --- a/mobile/android/android-components/components/lib/push-firebase/build.gradle +++ b/mobile/android/android-components/components/lib/push-firebase/build.gradle @@ -19,10 +19,16 @@ android { } } + compileOptions { + coreLibraryDesugaringEnabled = true + } + namespace = 'mozilla.components.lib.push.firebase' } dependencies { + coreLibraryDesugaring libs.desugar.jdk.libs + implementation libs.kotlin.coroutines implementation libs.play.services.base diff --git a/mobile/android/android-components/plugins/dependencies/src/main/java/ApplicationServices.kt b/mobile/android/android-components/plugins/dependencies/src/main/java/ApplicationServices.kt index 56bafa2a037..a64593a192e 100644 --- a/mobile/android/android-components/plugins/dependencies/src/main/java/ApplicationServices.kt +++ b/mobile/android/android-components/plugins/dependencies/src/main/java/ApplicationServices.kt @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ // These lines are generated by android-components/automation/application-services-nightly-bump.py -val VERSION = "139.20250417201925" +val VERSION = "139.20250418130932" val CHANNEL = ApplicationServicesChannel.NIGHTLY object ApplicationServicesConfig { diff --git a/mobile/android/android-components/plugins/dependencies/src/main/java/moz.yaml b/mobile/android/android-components/plugins/dependencies/src/main/java/moz.yaml index 98cb420caf0..96f2bb660df 100644 --- a/mobile/android/android-components/plugins/dependencies/src/main/java/moz.yaml +++ b/mobile/android/android-components/plugins/dependencies/src/main/java/moz.yaml @@ -31,11 +31,11 @@ origin: # Human-readable identifier for this version/release # Generally "version NNN", "tag SSS", "bookmark SSS" - release: 24064dcb7ba1094e85e0c49f627cc7dff31ee55a (2025-04-17T20:19:25). + release: e1b42aa3292a71e788bc0f95fb5ab5fbe533996f (2025-04-18T13:09:32). # Revision to pull in # Must be a long or short commit SHA (long preferred) - revision: 24064dcb7ba1094e85e0c49f627cc7dff31ee55a + revision: e1b42aa3292a71e788bc0f95fb5ab5fbe533996f # The package's license, where possible using the mnemonic from # https://spdx.org/licenses/ diff --git a/mobile/android/config/mozconfigs/android-arm-gradle-dependencies/base b/mobile/android/config/mozconfigs/android-arm-gradle-dependencies/base index 91640adad39..b996024b53e 100644 --- a/mobile/android/config/mozconfigs/android-arm-gradle-dependencies/base +++ b/mobile/android/config/mozconfigs/android-arm-gradle-dependencies/base @@ -1,5 +1,5 @@ # Many things aren't appropriate for a frontend-only build. -MOZ_AUTOMATION_BUILD_SYMBOLS=0 +export MOZ_AUTOMATION_BUILD_SYMBOLS=0 MOZ_AUTOMATION_PACKAGE=0 MOZ_AUTOMATION_UPLOAD=0 MOZ_AUTOMATION_PACKAGE_GENERATED_SOURCES=0 diff --git a/mobile/android/config/mozconfigs/android-arm/nightly-android-lints b/mobile/android/config/mozconfigs/android-arm/nightly-android-lints index 094ca33ebd8..27d46fc5fb4 100644 --- a/mobile/android/config/mozconfigs/android-arm/nightly-android-lints +++ b/mobile/android/config/mozconfigs/android-arm/nightly-android-lints @@ -1,5 +1,5 @@ # Many things aren't appropriate for a frontend-only build. -MOZ_AUTOMATION_BUILD_SYMBOLS=0 +export MOZ_AUTOMATION_BUILD_SYMBOLS=0 MOZ_AUTOMATION_PACKAGE=0 MOZ_AUTOMATION_UPLOAD=0 MOZ_AUTOMATION_PACKAGE_GENERATED_SOURCES=0 diff --git a/mobile/android/config/mozconfigs/android-arm/nightly-android-lints-lite b/mobile/android/config/mozconfigs/android-arm/nightly-android-lints-lite index bb0410e0774..edea6cd9405 100644 --- a/mobile/android/config/mozconfigs/android-arm/nightly-android-lints-lite +++ b/mobile/android/config/mozconfigs/android-arm/nightly-android-lints-lite @@ -1,5 +1,5 @@ # Many things aren't appropriate for a frontend-only build. -MOZ_AUTOMATION_BUILD_SYMBOLS=0 +export MOZ_AUTOMATION_BUILD_SYMBOLS=0 MOZ_AUTOMATION_PACKAGE=0 MOZ_AUTOMATION_UPLOAD=0 MOZ_AUTOMATION_PACKAGE_GENERATED_SOURCES=0 diff --git a/mobile/android/config/mozconfigs/common.override b/mobile/android/config/mozconfigs/common.override index 1213a82f706..f4a8281a320 100644 --- a/mobile/android/config/mozconfigs/common.override +++ b/mobile/android/config/mozconfigs/common.override @@ -5,6 +5,6 @@ # This file is included at the bottom of all native android mozconfigs # # Disable enforcing that add-ons are signed by the trusted root -MOZ_REQUIRE_SIGNING= +unset MOZ_REQUIRE_SIGNING . "$topsrcdir/build/mozconfig.common.override" diff --git a/mobile/android/fenix/app/build.gradle b/mobile/android/fenix/app/build.gradle index abc1b395aec..287afee181e 100644 --- a/mobile/android/fenix/app/build.gradle +++ b/mobile/android/fenix/app/build.gradle @@ -306,7 +306,7 @@ android { compose = true } - compileOptions{ + compileOptions { coreLibraryDesugaringEnabled = true } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt index 8ead32ae956..8a6fb3759f2 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt @@ -1293,6 +1293,11 @@ class HomeFragment : Fragment() { browsingModeManager = browsingModeManager, ), interactor = sessionControlInteractor, + onMiddleSearchBarVisibilityChanged = { isVisible -> + // Hide the main address bar in the toolbar when the middle search is + // visible (and vice versa) + toolbarView.updateAddressBarVisibility(!isVisible) + }, onTopSitesItemBound = { StartupTimeline.onTopSitesItemBound(activity = (requireActivity() as HomeActivity)) }, diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/store/HomepageState.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/store/HomepageState.kt index 184878a01a6..6aad97e4adb 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/store/HomepageState.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/store/HomepageState.kt @@ -29,6 +29,7 @@ import org.mozilla.fenix.home.recentvisits.RecentlyVisitedItem import org.mozilla.fenix.home.topsites.TopSiteColors import org.mozilla.fenix.nimbus.FxNimbus import org.mozilla.fenix.nimbus.HomeScreenSection +import org.mozilla.fenix.search.SearchDialogFragment import org.mozilla.fenix.theme.FirefoxTheme import org.mozilla.fenix.utils.Settings import org.mozilla.fenix.wallpapers.WallpaperState @@ -73,7 +74,8 @@ internal sealed class HomepageState { * @property showBookmarks Whether to show bookmarks. * @property showRecentlyVisited Whether to show recent history section. * @property showPocketStories Whether to show the pocket stories section. - * @property showSearchBar Whether to show the search bar. + * @property showSearchBar Whether to show the middle search bar. + * @property searchBarEnabled Whether the middle search bar is enabled or not. * @property setupChecklistState Optional state of the setup checklist feature. * @property topSiteColors The color set defined by [TopSiteColors] used to style a top site. * @property cardBackgroundColor Background color for card items. @@ -99,6 +101,7 @@ internal sealed class HomepageState { val showRecentlyVisited: Boolean, val showPocketStories: Boolean, val showSearchBar: Boolean, + val searchBarEnabled: Boolean, val setupChecklistState: SetupChecklistState?, val topSiteColors: TopSiteColors, val cardBackgroundColor: Color, @@ -169,7 +172,8 @@ internal sealed class HomepageState { showRecentlyVisited = settings.historyMetadataUIFeature && recentHistory.isNotEmpty(), showPocketStories = settings.showPocketRecommendationsFeature && recommendationState.pocketStories.isNotEmpty() && firstFrameDrawn, - showSearchBar = settings.enableHomepageSearchBar, + showSearchBar = shouldShowSearchBar(appState = appState), + searchBarEnabled = settings.enableHomepageSearchBar, setupChecklistState = setupChecklistState, topSiteColors = TopSiteColors.colors(wallpaperState = wallpaperState), cardBackgroundColor = wallpaperState.cardBackgroundColor, @@ -211,4 +215,15 @@ private fun getBottomSpace(): Dp { return toolbarHeight + extraSpace + HOME_APP_BAR_HEIGHT } +/** + * Returns whether the search bar should be shown. Only show if the search dialog + * [SearchDialogFragment] is not visible, and the user does not have their toolbar set to be on the + * bottom, and the screen is not in landscape mode. This is in addition to logic in the view layer + * which hides the middle search bar when the users scrolls down. This is separate from the middle + * search bar being enabled in settings since the toolbar address bar needs to react to the middle + * search bar's visibility. + */ +private fun shouldShowSearchBar(appState: AppState) = + !appState.isSearchDialogVisible + private val HOME_APP_BAR_HEIGHT = 48.dp diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/toolbar/FenixHomeToolbar.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/toolbar/FenixHomeToolbar.kt index 4f77e5156dd..15d0674a7dd 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/toolbar/FenixHomeToolbar.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/toolbar/FenixHomeToolbar.kt @@ -38,6 +38,13 @@ interface FenixHomeToolbar { */ fun updateButtonVisibility(browserState: BrowserState, shouldAddNavigationBar: Boolean) + /** + * Updates the visibility of the address bar. + * + * @param isVisible Whether the address bar should be visible or not. + */ + fun updateAddressBarVisibility(isVisible: Boolean) + /** * Updates the tab counter view based on the current browser state. * diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/toolbar/HomeToolbarComposable.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/toolbar/HomeToolbarComposable.kt index deb4c0637d0..5c5d87393cc 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/toolbar/HomeToolbarComposable.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/toolbar/HomeToolbarComposable.kt @@ -116,6 +116,10 @@ internal class HomeToolbarComposable( // To be added later } + override fun updateAddressBarVisibility(isVisible: Boolean) { + // To be added later + } + @Composable private fun BrowserToolbar(shouldShowDivider: Boolean, shouldUseBottomToolbar: Boolean) { // Ensure the divider is shown together with the toolbar diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/toolbar/HomeToolbarView.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/toolbar/HomeToolbarView.kt index 9e6f17576a9..13975290eb9 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/toolbar/HomeToolbarView.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/toolbar/HomeToolbarView.kt @@ -7,6 +7,7 @@ package org.mozilla.fenix.home.toolbar import android.view.Gravity import android.view.View import android.view.ViewGroup +import android.view.animation.AnimationUtils import androidx.annotation.DrawableRes import androidx.annotation.VisibleForTesting import androidx.appcompat.content.res.AppCompatResources @@ -256,6 +257,16 @@ internal class HomeToolbarView( } } + override fun updateAddressBarVisibility(isVisible: Boolean) { + if (isVisible) { + toolbarBinding.toolbarWrapper.startAnimation(AnimationUtils.loadAnimation(context, R.anim.fade_in)) + toolbarBinding.toolbarWrapper.visibility = View.VISIBLE + } else { + toolbarBinding.toolbarWrapper.startAnimation(AnimationUtils.loadAnimation(context, R.anim.fade_out)) + toolbarBinding.toolbarWrapper.visibility = View.INVISIBLE + } + } + companion object { const val TOOLBAR_WRAPPER_INCREASE_HEIGHT_DPS = 5 } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/ui/Homepage.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/ui/Homepage.kt index 7a7de7ac21e..ae20fc6b45f 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/ui/Homepage.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/ui/Homepage.kt @@ -4,6 +4,10 @@ package org.mozilla.fenix.home.ui +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.animateContentSize +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -15,6 +19,10 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.dimensionResource @@ -64,11 +72,14 @@ import org.mozilla.fenix.theme.FirefoxTheme import org.mozilla.fenix.theme.Theme import org.mozilla.fenix.wallpapers.WallpaperState +private const val MIDDLE_SEARCH_SCROLL_THRESHOLD_PX = 10 + /** * Top level composable for the homepage. * * @param state State representing the homepage. * @param interactor for interactions with the homepage UI. + * @param onMiddleSearchBarVisibilityChanged Invoked when the middle search is shown/hidden. * @param onTopSitesItemBound Invoked during the composition of a top site item. */ @Suppress("LongMethod") @@ -76,11 +87,15 @@ import org.mozilla.fenix.wallpapers.WallpaperState internal fun Homepage( state: HomepageState, interactor: HomepageInteractor, + onMiddleSearchBarVisibilityChanged: (isVisible: Boolean) -> Unit, onTopSitesItemBound: () -> Unit, ) { + val scrollState = rememberScrollState() + Column( modifier = Modifier - .verticalScroll(rememberScrollState()), + .verticalScroll(scrollState) + .animateContentSize(), ) { HomepageHeader( browsingMode = state.browsingMode, @@ -120,10 +135,24 @@ internal fun Homepage( ) } - if (showSearchBar) { - SearchBar( - onClick = interactor::onNavigateSearch, - ) + if (searchBarEnabled) { + val atTopOfList by remember { + derivedStateOf { + scrollState.value < MIDDLE_SEARCH_SCROLL_THRESHOLD_PX + } + } + + LaunchedEffect(atTopOfList) { + onMiddleSearchBarVisibilityChanged(atTopOfList) + } + + AnimatedVisibility( + visible = showSearchBar && atTopOfList, + enter = fadeIn(), + exit = fadeOut(), + ) { + SearchBar(onClick = interactor::onNavigateSearch) + } } if (setupChecklistState != null) { @@ -420,6 +449,7 @@ private fun HomepagePreview() { showBookmarks = true, showRecentlyVisited = true, showPocketStories = true, + searchBarEnabled = false, showSearchBar = true, setupChecklistState = null, topSiteColors = TopSiteColors.colors(), @@ -431,6 +461,7 @@ private fun HomepagePreview() { ), interactor = FakeHomepagePreview.homepageInteractor, onTopSitesItemBound = {}, + onMiddleSearchBarVisibilityChanged = {}, ) } } @@ -456,6 +487,7 @@ private fun HomepagePreviewCollections() { showBookmarks = false, showRecentlyVisited = true, showPocketStories = true, + searchBarEnabled = false, showSearchBar = true, setupChecklistState = null, topSiteColors = TopSiteColors.colors(), @@ -467,6 +499,7 @@ private fun HomepagePreviewCollections() { ), interactor = FakeHomepagePreview.homepageInteractor, onTopSitesItemBound = {}, + onMiddleSearchBarVisibilityChanged = {}, ) } } @@ -487,6 +520,7 @@ private fun PrivateHomepagePreview() { ), interactor = FakeHomepagePreview.homepageInteractor, onTopSitesItemBound = {}, + onMiddleSearchBarVisibilityChanged = {}, ) } } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/ui/SearchBar.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/ui/SearchBar.kt index 17b0a6fb5d1..58eee48b6da 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/ui/SearchBar.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/ui/SearchBar.kt @@ -8,6 +8,7 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewLightDark import mozilla.components.compose.browser.toolbar.BrowserDisplayToolbar @@ -26,8 +27,7 @@ internal fun SearchBar( ) { BrowserDisplayToolbar( url = stringResource(R.string.search_hint), - colors = BrowserToolbarDefaults.colors().displayToolbarColors, - textStyle = FirefoxTheme.typography.body1, + colors = BrowserToolbarDefaults.colors().displayToolbarColors.copy(background = Color.Transparent), onUrlClicked = onClick, onInteraction = {}, ) diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/library/bookmarks/ui/BookmarksMiddleware.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/library/bookmarks/ui/BookmarksMiddleware.kt index a5473ac1408..89801e6acbf 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/library/bookmarks/ui/BookmarksMiddleware.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/library/bookmarks/ui/BookmarksMiddleware.kt @@ -343,12 +343,22 @@ internal class BookmarksMiddleware( rootNode.children?.find { it.guid == BookmarkRoot.Mobile.id }?.let { val newChildren = listOf(desktopRoot) + it.children.orEmpty() it.copy(children = newChildren) - }?.let { collectFolders(it, shouldCollect = { node -> !state.isGuidBeingMoved(node.guid) }) } + }?.let { + collectFolders( + node = it, + comparator = state.sortOrder.comparator, + shouldCollect = { node -> !state.isGuidBeingMoved(node.guid) }, + ) + } } } else { bookmarksStorage.getTree(BookmarkRoot.Mobile.id, recursive = true) - ?.let { rootNode -> - collectFolders(rootNode, shouldCollect = { node -> !state.isGuidBeingMoved(node.guid) }) + ?.let { + collectFolders( + node = it, + comparator = state.sortOrder.comparator, + shouldCollect = { node -> !state.isGuidBeingMoved(node.guid) }, + ) } } @@ -429,6 +439,7 @@ internal class BookmarksMiddleware( private fun collectFolders( node: BookmarkNode, + comparator: Comparator, indentation: Int = 0, shouldCollect: (BookmarkNode) -> Boolean = { _ -> true }, folders: MutableList = mutableListOf(), @@ -444,8 +455,11 @@ internal class BookmarksMiddleware( ), ) - node.children?.forEach { child -> - folders.addAll(collectFolders(child, indentation + 1, shouldCollect)) + val sortedChildren = node.childItems().folders().sortedWith(comparator) + sortedChildren.forEach { child -> + val childNode = node.children!!.first { it.guid == child.guid } + val children = collectFolders(childNode, comparator, indentation + 1, shouldCollect) + folders.addAll(children) } } diff --git a/mobile/android/fenix/app/src/main/res/values-en-rGB/strings.xml b/mobile/android/fenix/app/src/main/res/values-en-rGB/strings.xml index de5f598d79b..f60598d397c 100644 --- a/mobile/android/fenix/app/src/main/res/values-en-rGB/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-en-rGB/strings.xml @@ -129,6 +129,8 @@ Swipe the toolbar to switch tabs Swipe left or right to switch. Swipe left on your last tab to open a new tab. + + Swipe the toolbar left or right to switch tabs. Camera access needed. Go to Android settings, tap permissions, and tap allow. @@ -1483,6 +1485,10 @@ Remove Delete + + Navigate back + + Navigate up Share URL @@ -3378,6 +3384,26 @@ The title of the glean debugging feature --> Glean Debug Tools + + Region Tools + + Temporarily overrides the home and current region values for testing. + + Home region + + Current region + + Override home region + + Override current region + + Override home and current region Clear text + + Finish setting up %1$s + + Congratulations! + + Complete these 3 steps to set up %1$s for the best browsing experience. + + Great start! You’ve completed 1 out of 3 steps. + + Almost there! Two steps finished and 1 to go. + + You’ve completed all 3 steps. Enjoy the speed, privacy, and security of %1$s. + + Complete all 6 steps to set up %1$s for the best browsing experience. + + Great start! You’ve completed 1 out of 6 steps. + + You’ve completed 2 out of 6 steps. Great progress! + + You’re halfway there! Three steps finished and 3 to go. + + You’re 4 steps in. Only 2 more to go! + + Almost there! You’re just 1 step away from the finish line. + + You’ve completed all 6 setup steps. Enjoy the speed, privacy, and security of %1$s. Helpful tools + + Remove checklist Aldatzeko, pasatu ezkerrera edo eskuinera. Fitxa berria irekitzeko, pasatu ezkerrera zure azken fitxan. + + Fitxak aldatzeko, pasatu tresna-barra ezkerrera edo eskuinera. Kamerarako sarbidea behar da. Joan Androideko ezarpenetara, sakatu baimenetan eta sakatu baimendu. @@ -1384,6 +1386,8 @@ Partekatu hautatutako fitxak Hautatutako fitxen menua + + Hasiera-orria Kendu fitxa bildumatik @@ -1481,6 +1485,10 @@ Kendu Ezabatu + + Nabigatu atzera + + Nabigatu gora Partekatu URLa @@ -1524,6 +1532,8 @@ Bilatu deskargak Ez da emaitzarik aurkitu + + Hautatu dena Glean arazketa-tresnak + + Eskualdearen tresnak + + Aldi baterako baliogabetzen ditu etxeko eta uneko eskualdearen balioak probak egiteko. + + Etxeko eskualdea + + Uneko eskualdea + + Baliogabetu etxeko eskualdea + + Baliogabetu uneko eskualdea + + Baliogabetu etxeko eta uneko eskualdea Garbitu testua + + Amaitu %1$s(r)en konfigurazioa + + Zorionak! + + Osatu ondorengo hiru urratsak %1$s nabigatze-esperientzia optimorako konfiguratzeko. + + Hasiera ona! Hirutik urrats bat osatu duzu. + + Ia-ia eginda! Bi urrats amaituta eta bakarraren faltan. + + Hiru urratsak burutu dituzu. Gozatu %1$s(r)en abiadura, pribatutasuna eta segurtasuna. + + Osatu ondorengo sei urratsak %1$s nabigatze-esperientzia optimorako konfiguratzeko. + + Hasiera ona! Seitik urrats bat osatu duzu. + + Seitik bi urrats osatu dituzu. Ederki ari zara! + + Erdi-bidean zaude! Hiru urrats amaituta eta beste hiruren faltan. + + Lau urrats osatu dituzu. Bi gehiago faltan! + + Ia-ia eginda! Urrats bakarraren faltan zaude. + + Konfigurazioko sei urratsak burutu dituzu. Gozatu %1$s(r)en abiadura, pribatutasuna eta segurtasuna. Tresna lagungarriak + + Kendu egiaztapen-zerrenda تایید و آغاز به مرور + + نکته پایانی + + نحوه آشنایی خود با Firefox و اینکه از آن استفاده می‌کنید را با همکاران بازاریابی Mozilla هم‌رسانی کنید. این داده‌ها هرگز فروخته نمی‌شوند. اعلام موافقت و آغاز مرور @@ -768,6 +772,10 @@ افزودن میان‌بر مرور خصوصی حالت فقط-Https + + استفاده از قفل صفحه برای پنهان کردن زبانه‌ها در مرور خصوصی + + زبانه‌ها را با اثر انگشت، بازگشایی با چهره یا پین مشاهده کنید. مسدودکننده برنمای کلوچک @@ -864,12 +872,18 @@ نمایش جست‌وجوی صوتی نمایش در جلسات خصوصی + + نمایش پیشنهادهای پرطرفدار + + نمایش جستجوهای اخیر نمایش پیشنهادات تخته‌گیره جست‌وجوی تاریخچهٔ مرور جست‌و‌جو نشانک‌ها + + نمایش میانبرها جست‌وجو در زبانه‌های همگام‌سازی شده @@ -890,6 +904,12 @@ پیشنهادات از %1$s دریافت پیشنهادات از وب مرتبط با جستجوی شما + + هم‌رسانی پیوند + + پیوند هم‌رسانی شد + + مدیریت تنظیمات پیوندها را در برنامه ها باز کنید @@ -1362,6 +1382,8 @@ هم‌رسانی زبانه‌های انتخاب شده منوی زبانه‌های انتخاب شده + + صفحه اصلی حذف زبانه از مجموعه @@ -1429,6 +1451,10 @@ %1$s حذف شد جزئیات + + باز کردن + + برگردان تا به حال پرونده‌ای بارگیری نشده است ۷ روز گذشته + + ۳۰ روز گذشته + + ‏‏قدیمی‌تر انتخاب همه افزودن وبگاه + + خاموش (پیش‌فرض) @@ -2994,6 +3026,12 @@ بارگیری و ترجمه انصراف + + ایمن مواظب این وبگاه باشید @@ -3018,6 +3056,16 @@ اگر چیزی در این وبگاه خراب به نظر می‌رسد، سعی کنید محافظت‌ها را خاموش کنید. محافظ‌ها خاموش هستند. پیشنهاد می‌کنیم آن‌ها را دوباره فعال کنید. + + %s: %d + + پاک کردن + + انصراف Փոխարկելու համար սահեցրեք ձախ կամ աջ: Ձեր վերջին ներդիրի վրա սահեցրեք ձախ՝ նոր ներդիր բացելու համար: + + Սահեցրեք գործիքագոտին դեպի ձախ կամ աջ՝ ներդիրները փոխարկելու համար: Անհրաժեշտ է հասանելիություն տեսախցիկին: Անցեք Android-ի կարգավորումներ, հպեք՝ Թույլտվություններ, այնուհետև՝ Թույլատրել: @@ -1242,7 +1244,7 @@ --> Այսքան ներդիրներ բացելը կարող է դանդաղեցնել %s-ը, մինչ էջերը բեռնվում են: Վստա՞հ եք, որ ցանկանում եք շարունակել: - Բաց ներդիրներ + Բացել ներդիրները Չեղարկել Ընտրված ներդիրների ցանկը + + Տնային էջ Հեռացնել ներդիրը հավաքածուից @@ -1480,6 +1484,10 @@ Հեռացնել Ջնջել + + Նավարկել ետ + + Նավարկել վերև Համօգտագործել URL-ը @@ -1523,6 +1531,8 @@ Որոնել ներբեռնում Արդյունքներ չկան + + Նշել բոլորը Glean վրիպազերծման գործիքներ + + Տարածաշրջանային գործիքներ + + Ժամանակավորապես անտեսում է տան և ընթացիկ տարածաշրջանի արժեքները փորձարկման համար: + + Տնային տարածաշրջան + + Ներկայիս տարածաշրջան + + Գերազդել տնային տարածաշրջանը + + Գերազդել ներկայիս տարածաշրջանը + + Գերազդել տնային և ներկայիս տարածաշրջանը Մաքրել տեքստը + + Ավարտեք %1$s-ի կարգավորումը + + Շնորհավորանքնե՜ր + + Կատարեք այս 3 քայլերը՝ %1$s-ը կարգավորելու և դիտարկումների լավագույն փորձառության համար: + + Հիանալի սկիզբ: Դուք կատարել եք 3 քայլերից 1-ը: + + Գրեթե պատրաստ է: Երկու քայլ ավարտվեց, և մնում է 1-ը: + + Դուք ավարտել եք բոլոր 3 քայլերը: Վայելեք %1$s-ի արագությունը, գաղտնիությունը և անվտանգությունը: + + Կատարեք բոլոր 6 քայլերը՝ %1$s-ը կարգավորելու և դիտարկումների լավագույն փորձառության համար: + + Հիանալի սկիզբ: Դուք կատարել եք 6 քայլերից 1-ը: + + Դուք կատարել եք 6 քայլերից 2-ը: Մեծ առաջընթաց: + + Գրեթե պատրաստ է: Երեք քայլ ավարտվեց, և մնում է 3-ը: + + Դուք 4 քայլ առաջ եք: Մնացել է ևս 2-ը: + + Գրեթե պատրաստ է: Դուք ընդամենը 1 քայլ եք հեռու ավարտելուց: + + Դուք ավարտել եք բոլոր 6 քայլերը: Վայելեք %1$s-ի արագությունը, գաղտնիությունը և անվտանգությունը: Օգտակար գործիքներ + + Հեռացնել ստուգաթերթը Eyða + + Fara til baka + + Fara upp Deila vefslóð diff --git a/mobile/android/fenix/app/src/main/res/values-ja/strings.xml b/mobile/android/fenix/app/src/main/res/values-ja/strings.xml index 686a686a54d..b78a13174e8 100644 --- a/mobile/android/fenix/app/src/main/res/values-ja/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-ja/strings.xml @@ -129,6 +129,8 @@ ツールバーをスワイプすると、タブを切り替えられます 左右にスワイプするとタブが切り替わります。最後のタブを左にスワイプすると、新しいタブを開きます。 + + ツールバーを左または右にスワイプして、タブを切り替えます。 カメラへのアクセスが必要です。Android の設定から、権限をタップし、許可をタップしてください。 @@ -1483,6 +1485,10 @@ 削除 削除 + + 前のページへ戻ります + + 上へ移動します URL を共有 @@ -3377,6 +3383,26 @@ The title of the glean debugging feature --> Glean デバッグツール + + リージョンツール + + テストのためホームと現在の地域値を一時的に上書きします。 + + ホームリージョン + + 現在のリージョン + + ホームリージョンを上書きします + + 現在のリージョンを上書きします + + ホームと現在のリージョンを上書き おめでとうございます! + + これらの 3 ステップを完了して、最適なブラウジング体験のために %1$s をセットアップしましょう。 + + 素晴らしいスタートです! 3 ステップ中 、1 ステップを完了しました。 + + もうすぐです! 2 つのステップが完了し、残り 1 つです。 + + 3 つのステップがすべて完了しました。%1$s のスピード、プライバシー、セキュリティをお楽しみください。 + + 6 ステップすべてを完了して、最適なブラウジング体験のために %1$s をセットアップしましょう。 + + 素晴らしいスタートです! 6 ステップ中 、1 ステップを完了しました。 + + 6 ステップ中、2 ステップを完了しました。順調です! + + 半分まで来ました! 3 つのステップが完了し、残りは 3 つです。 + + 4 ステップが完了しました。あと 2 ステップです。 + + ゴールはすぐそこ! あと 1 ステップです。 + + 6 つのステップがすべて完了しました。%1$s のスピード、プライバシー、セキュリティをお楽しみください。 Соңғы бір нәрсе + + Firefox-ты өзіңіз үшін қалай ашқаныңызды және оны қалай пайдаланғаныңызды Mozilla маркетинг серіктестерімен бөлісіңіз. Бұл деректер ешқашан сатылмайды. Келісу және шолуды бастау diff --git a/mobile/android/fenix/app/src/main/res/values-ml/strings.xml b/mobile/android/fenix/app/src/main/res/values-ml/strings.xml index 36bf841f81f..715a1150f5e 100644 --- a/mobile/android/fenix/app/src/main/res/values-ml/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-ml/strings.xml @@ -3232,6 +3232,8 @@ സ്വദേശം നിലവിലെ ഇടം + + നിലവിലെ പ്രദേശം അസാധുവാക്കുക അഭിനന്ദനങ്ങള്‍! + + ഏതാണ്ടു് തീരാറായി! രണ്ടു പടി കഴിഞ്ഞിരിക്കുന്നു, ഇനി ഒരു പടി കൂടെ മിച്ചമിരിപ്പുണ്ടു്. + + നല്ല തുടക്കം. താങ്ങൾ 6-ൽ 1 പടി തീൎത്തു. 6-ൽ 2 എണ്ണം കിഴിഞ്ഞു. നല്ലോണം പോയിക്കൊണ്ടിരിക്കുന്നു! പകുതി കഴിഞ്ഞു, ഇനിയും 3 പടികൾ മാത്രം മിച്ചമിരിപ്പുണ്ടു്. + + 4 പടികൾ കഴിഞ്ഞിരിക്കുന്നു. ഇനി രണ്ടു പടി മാത്രം മിച്ചം! Excluir + + Página anterior + + Ir para o topo Compartilhar URL diff --git a/mobile/android/fenix/app/src/main/res/values-rm/strings.xml b/mobile/android/fenix/app/src/main/res/values-rm/strings.xml index a0624d5b717..a1c21908123 100644 --- a/mobile/android/fenix/app/src/main/res/values-rm/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-rm/strings.xml @@ -127,6 +127,8 @@ Stritga sur la trav da simbols per midar tab Stritga a sanestra u a dretga per midar. Stritga a sanestra sin tes ultim tab per avrir in nov. + + Stuschar la trav d’utensils a sanestra u a dretga per midar tab. L\'access a la camera è necessari. Acceda als parameters dad Android, smatga sin permissiuns e lura sin permetter. @@ -671,6 +673,8 @@ Maletg da la trav d’utensils inferiura Maletg da la trav d’utensils superiura + + tscherner Tscherna in design @@ -766,6 +770,10 @@ Agiuntar ina scursanida al modus privat Modus mo HTTPS + + Utilisar la bloccada dal visur per zuppentar tabs en il modus privat + + Utilisar l’impronta dal det, la reconuschientscha da la fatscha u il PIN per mussar ils tabs. Bloccada da bandieras da cookies @@ -1376,6 +1384,8 @@ Cundivider ils tabs tschernids Menu da tabs tschernids + + Pagina da partenza Allontanar il tab da la collecziun @@ -1471,6 +1481,12 @@ %1$d tschernidas Allontanar + + Stizzar + + Navigar enavos + + Navigar ensi Parter l’URL @@ -1514,6 +1530,8 @@ Retschertgar las telechargiadas Na chattà nagins resultats + + Selecziunar tut Auters + + Auter Utensils da debugadi per Glean + + Utensils per la regiun + + Recuvra temporarmain las valurs per il dachasa e la regiun actuala per exequir tests. + + Regiun dal dachasa + + Regiun actuala + + Recuvrir la regiun dachasa + + Recuvrir la regiun actuala + + Recuvrir la regiun dachasa e la regiun actuala Stizzar il text + + Finir la configuraziun da %1$s + + Gratulaziun! + + Suonda quests 3 pass per configurar %1$s per l’experientscha da navigaziun optimala. + + In cumenzament optimal! Ti has fatg 1 da 3 pass. + + Quasi finì! Dus pass davos tai ed anc in da far. + + Ti has cumplettà tut ils 3 pass. Giauda la sveltezza, la protecziun da datas e la segirezza da %1$s. + + Fa tut ils 6 pass per configurar %1$s uschia che ti hajas la meglra experientscha da navigaziun. + + In cumenzament optimal! Ti has fatg 1 da 6 pass. + + Ti has cumplettà 2 pass da 6. Ti fas progress! + + Ti has gia la mesadad! Trais pass fatgs e 3 da far. + + Ti has cumplettà quatter pass. Mo anc 2 dapli! + + Quasi fatg! I manca mo anc in pass per il final. + + Ti has cumplettà tut ils 6 pass da configuraziun. Giauda la sveltezza, la protecziun da datas e la segirezza da %1$s. + + Definir sco navigatur da standard + + T’annunzia en tes conto + + Tscherner in design + + Tscherna la posiziun per la trav d’utensils + + Explorar las extensiuns + + Installar il widget da retschertga + + Configuraziun da basa da %1$s + + Persunalisescha tes %1$s + + Utensils gidaivels + + Allontanar la glista da controlla Icona dad incumbensa + + Bandunar ils tabs privats + + Debloccar + + Debloccar ils tabs privats diff --git a/mobile/android/fenix/app/src/main/res/values-sl/strings.xml b/mobile/android/fenix/app/src/main/res/values-sl/strings.xml index c799b2f64c9..a14e5c8d0db 100644 --- a/mobile/android/fenix/app/src/main/res/values-sl/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-sl/strings.xml @@ -129,6 +129,8 @@ Podrsajte orodno vrstico za preklop zavihkov Podrsajte levo ali desno za preklop. Podrsajte levo na zadnjem zavihku, da odprete nov zavihek. + + Za preklop med zavihki podrsajte orodno vrstico v levo ali desno. Potreben je dostop do kamere. Odprite nastavitve sistema Android in v dovoljenjih tapnite Dovoli. @@ -1475,6 +1477,10 @@ Odstrani Izbriši + + Pojdi nazaj + + Pojdi gor Deli URL @@ -1501,6 +1507,17 @@ Zadnjih 30 dni Starejše + + %1$s/%2$s • še %3$s + + ustavljeno + + čakajoče Prenos ni uspel @@ -3358,6 +3375,26 @@ The title of the glean debugging feature --> Orodja Glean za razhroščevanje + + Območna orodja + + Začasno preglasi vrednosti domače in trenutne regije za namene testiranja. + + Domača regija + + Trenutna regija + + Preglasi domačo regijo + + Preglasi trenutno regijo + + Preglasi domačo in trenutno regijo Počisti besedilo + + Dokončajte nastavitev %1$sa + + Čestitamo! เอา %1$s ออกแล้ว + + ลบ “%1$s” แล้ว กำลังดาวน์โหลด… @@ -1470,6 +1472,10 @@ เอาออก ลบ + + นำทางย้อนกลับ + + นำทางขึ้นไปด้านบน แบ่งปัน URL @@ -1502,6 +1508,8 @@ กำลังรอ การดาวน์โหลดล้มเหลว + + ค้นหาการดาวน์โหลด ไม่พบผลลัพธ์ @@ -1632,6 +1640,10 @@ ลบ บันทึก + + เรียงตามใหม่ล่าสุด + + เรียงตามเก่าที่สุด ล้างข้อความ + + ขอแสดงความยินดี! + + ตั้งเป็นเบราว์เซอร์เริ่มต้น + + ลงชื่อเข้าบัญชีของคุณ + + สำรวจส่วนขยาย + + ปรับแต่ง %1$s ของคุณ เครื่องมือที่มีประโยชน์ ไอคอนงาน + + ออกจากแท็บส่วนตัว ปลดล็อก diff --git a/mobile/android/fenix/app/src/main/res/values-tr/strings.xml b/mobile/android/fenix/app/src/main/res/values-tr/strings.xml index 4932d497221..52779b725a8 100644 --- a/mobile/android/fenix/app/src/main/res/values-tr/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-tr/strings.xml @@ -1485,6 +1485,10 @@ Kaldır Sil + + Geri git + + Yukarı git Adresi paylaş diff --git a/mobile/android/fenix/app/src/main/res/values-ug/strings.xml b/mobile/android/fenix/app/src/main/res/values-ug/strings.xml index 9f1bf238491..ba7a7259e70 100644 --- a/mobile/android/fenix/app/src/main/res/values-ug/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-ug/strings.xml @@ -1485,6 +1485,10 @@ چىقىرىۋەت ئۆچۈر + + كەينىگە قايت + + ئۈستىگە يول باشلا تور ئادرېسى ھەمبەھىر diff --git a/mobile/android/fenix/app/src/main/res/values-vi/strings.xml b/mobile/android/fenix/app/src/main/res/values-vi/strings.xml index a16a1c0697b..39e3753493d 100644 --- a/mobile/android/fenix/app/src/main/res/values-vi/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values-vi/strings.xml @@ -1485,6 +1485,10 @@ Xóa Xóa + + Điều hướng quay lại + + Điều hướng lên Chia sẻ URL @@ -3380,6 +3384,26 @@ The title of the glean debugging feature --> Công cụ gỡ lỗi Glean + + Công cụ khu vực + + Tạm thời ghi đè giá trị khu vực hiện tại và khu vục chính để thử nghiệm. + + Khu vực chính + + Khu vực hiện tại + + Ghi đè khu vực chính + + Ghi đè khu vực hiện tại + + Ghi đè khu vực chính và hiện tại 向左或向右滑动以切换,于最后一个标签页向左滑动可打开新标签页。 + + 向左或向右滑动工具栏即可切换标签页。 需要访问相机。请前往 Android 设置,点按“权限”,然后点按“允许”。 @@ -768,6 +770,10 @@ 添加隐私浏览快捷方式 HTTPS-Only 模式 + + 使用屏幕锁定方式隐藏隐私浏览中的标签页 + + 用指纹、人脸解锁或 PIN 码查看标签页。 Cookie 横幅拦截器 @@ -1450,8 +1456,12 @@ 已删除 %1$d 个项目 已移除 %1$s + + 已删除“%1$s” 正在下载… + + 详细信息 下载完成 @@ -1460,6 +1470,10 @@ 撤销 暂无下载的文件 + + 暂无下载 + + 下载的文件将显示在这里。 删除 + + 浏览上一页 + + 向上导航 分享 URL @@ -1502,8 +1520,14 @@ %3$s is the amount of time remaining to complete the download. --> %1$s / %2$s • 剩余 %3$s + + 已暂停 + + 测算中 下载失败 + + 搜索下载项 未找到结果 @@ -3358,8 +3382,20 @@ The title of the glean debugging feature --> Glean 调试工具 + + 地区工具 本地地区 + + 当前地区 + + 覆盖当前地区 实用工具 + + 移除核对清单 同意并继续 + + 包含链接 diff --git a/supply-chain/audits.toml b/supply-chain/audits.toml index 6d1179e482c..b49cc9680da 100644 --- a/supply-chain/audits.toml +++ b/supply-chain/audits.toml @@ -646,6 +646,12 @@ who = "Mike Hommey " criteria = "safe-to-deploy" delta = "0.7.0 -> 0.8.1" +[[audits.android-tzdata]] +who = "Mark Hammond " +criteria = "safe-to-deploy" +version = "0.1.1" +notes = "Small crate parsing a file. No unsafe code" + [[audits.android_logger]] who = "Jan-Erik Rediger " criteria = "safe-to-deploy" @@ -1159,6 +1165,12 @@ who = "Bobby Holley " criteria = "safe-to-deploy" delta = "0.1.2 -> 0.1.2@git:ed8a4c6f900a90d4dbc1d64b856e61490a1c3570" +[[audits.chrono]] +who = "Mark Hammond " +criteria = "safe-to-deploy" +delta = "0.4.19 -> 0.4.40" +notes = "Significant refactor of both implementation and dependencies." + [[audits.circular]] who = "Alex Franchuk " criteria = "safe-to-deploy" @@ -2728,6 +2740,11 @@ who = "Mike Hommey " criteria = "safe-to-run" delta = "0.14.23 -> 0.14.24" +[[audits.iana-time-zone]] +who = "Mark Hammond " +criteria = "safe-to-deploy" +delta = "0.1.61 -> 0.1.63" + [[audits.icu_calendar]] who = "André Bargull " criteria = "safe-to-deploy" @@ -6233,6 +6250,12 @@ criteria = "safe-to-deploy" delta = "0.1.2 -> 0.3.1" notes = "Maintained by me. I have written or reviewed all of the code." +[[audits.windows-link]] +who = "Mark Hammond " +criteria = "safe-to-deploy" +version = "0.1.1" +notes = "A microsoft crate allowing unsafe calls to windows apis." + [[audits.winreg]] who = "Ray Kraesig " criteria = "safe-to-run" diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock index 9d879445268..d8fe9a90068 100644 --- a/supply-chain/imports.lock +++ b/supply-chain/imports.lock @@ -1202,6 +1202,11 @@ criteria = "safe-to-deploy" delta = "0.4.1 -> 0.5.0" notes = "Minor changes for a `no_std` upgrade but otherwise everything looks as expected." +[[audits.bytecode-alliance.audits.iana-time-zone-haiku]] +who = "Dan Gohman " +criteria = "safe-to-deploy" +version = "0.1.2" + [[audits.bytecode-alliance.audits.id-arena]] who = "Nick Fitzgerald " criteria = "safe-to-deploy" @@ -1705,6 +1710,13 @@ criteria = "safe-to-run" version = "0.14.20" aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" +[[audits.google.audits.iana-time-zone]] +who = "Manish Goregaokar " +criteria = "safe-to-deploy" +version = "0.1.61" +notes = "Some unsafe: interfacing with system timezone APIs" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + [[audits.google.audits.indexmap]] who = "Lukasz Anforowicz " criteria = "safe-to-deploy" diff --git a/taskcluster/config.yml b/taskcluster/config.yml index 107d6c7a206..976370b15c8 100644 --- a/taskcluster/config.yml +++ b/taskcluster/config.yml @@ -143,6 +143,20 @@ treeherder: 'ms-stage': 'Autograph-stage MAR signing test' 'Rel': 'Release promotion' 'Snap': 'Snap image generation' + 'B-nightly': 'Snap builds Nightly' + 'B-try': 'Snap builds Try' + 'B-beta': 'Snap builds Beta' + 'B-beta24': 'Snap builds Beta Core24' + 'B-stable': 'Snap builds Stable' + 'B-stable24': 'Snap builds Stable Core24' + 'B-esr': 'Snap builds ESR' + 'Sel-nightly': 'Snap selenium Nightly' + 'Sel-try': 'Snap selenium Try' + 'Sel-beta': 'Snap selenium Beta' + 'Sel-beta24': 'Snap selenium Beta Core24' + 'Sel-stable': 'Snap selenium Stable' + 'Sel-stable24': 'Snap selenium Stable Core24' + 'Sel-esr': 'Snap selenium ESR' 'Flatpak': 'Flatpak image generation' 'langpack': 'Langpack sigatures and uploads' 'TPS': 'Sync tests' @@ -161,7 +175,6 @@ treeherder: 'rust': 'Rust checks' 'Static-Analysis': 'Full tree static-analysis' 'SS': 'Shadow scheduler' - 'Sel': 'Selenium Snap tests' 'Sentry': 'Sentry synchronization' 'test-info': 'Test manifest skip/fail information' 'condprof': 'Conditioned Profile Builder' @@ -1018,6 +1031,7 @@ mac-signing: force: true # These files are signed without entitlements globs: + - "/Contents/MacOS/crashhelper" - "/Contents/MacOS/crashreporter.app" - "/Contents/MacOS/updater.app/Contents/Frameworks/UpdateSettings.framework" - "/Contents/MacOS/updater.app" @@ -1062,6 +1076,7 @@ mac-signing: force: true entitlements: security/mac/hardenedruntime/v2/developer/utility.xml globs: + - "/Contents/MacOS/crashhelper" - "/Contents/MacOS/crashreporter.app" - "/Contents/MacOS/updater.app/Contents/Frameworks/UpdateSettings.framework" - "/Contents/MacOS/updater.app" diff --git a/taskcluster/docker/snap-coreXX-build/Dockerfile b/taskcluster/docker/snap-coreXX-build/Dockerfile index da6d5b10d83..0e1e5fe6a7a 100644 --- a/taskcluster/docker/snap-coreXX-build/Dockerfile +++ b/taskcluster/docker/snap-coreXX-build/Dockerfile @@ -17,6 +17,7 @@ RUN apt-get update && \ build-essential \ curl \ $EXTRA_PACKAGES \ + gnupg \ jq \ patch \ patchelf \ @@ -63,8 +64,7 @@ ENV SNAP_NAME="snapcraft" ENV SNAP_ARCH="amd64" ENV SNAP_INSTANCE_KEY= ENV SNAP_INSTANCE_NAME=snapcraft -ENV SNAPCRAFT_BUILD_FOR=amd64 -ENV SNAPCRAFT_PLATFORM=amd64 +ENV SNAPCRAFT_PLATFORM= RUN snapcraft --version @@ -72,6 +72,10 @@ RUN snapcraft --version RUN echo "worker ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/worker && \ chmod 0440 /etc/sudoers.d/worker +COPY system-setup.sh /builds/worker/system-setup.sh + +RUN /builds/worker/system-setup.sh "${SNAP_BASE}" + COPY run.sh /builds/worker/run.sh COPY parse.py /builds/worker/parse.py diff --git a/taskcluster/docker/snap-coreXX-build/parse.py b/taskcluster/docker/snap-coreXX-build/parse.py index d6a51f1cf06..63584f6721a 100644 --- a/taskcluster/docker/snap-coreXX-build/parse.py +++ b/taskcluster/docker/snap-coreXX-build/parse.py @@ -9,19 +9,21 @@ import sys import yaml -def has_pkg_section(p, section): +def has_pkg_section(p, section, arch): has_section = section in p.keys() if has_section: for pkg in p[section]: if type(pkg) is str: yield pkg else: - yield from has_pkg_section(pkg, next(iter(pkg.keys()))) + next_section = next(iter(pkg.keys())) + if "on " in next_section or f"to {arch}" in next_section: + yield from has_pkg_section(pkg, next(iter(pkg.keys())), arch) def iter_pkgs(part, all_pkgs, arch): for section in ["build-packages", "stage-packages"]: - for pkg in has_pkg_section(part, section): + for pkg in has_pkg_section(part, section, arch): if pkg not in all_pkgs: if ":" in pkg and pkg.split(":")[1] != arch: continue diff --git a/taskcluster/docker/snap-coreXX-build/run.sh b/taskcluster/docker/snap-coreXX-build/run.sh index ed09a2f86e4..054e39dbc69 100755 --- a/taskcluster/docker/snap-coreXX-build/run.sh +++ b/taskcluster/docker/snap-coreXX-build/run.sh @@ -7,7 +7,6 @@ mkdir -p /builds/worker/artifacts/ export UPLOAD_PATH=/builds/worker/artifacts/ mkdir -p /builds/worker/.local/state/snapcraft/ -ln -s /builds/worker/artifacts /builds/worker/.local/state/snapcraft/log BRANCH=$1 ARCH=$2 @@ -98,6 +97,10 @@ if [ "${USE_SNAP_FROM_STORE_OR_MC}" = "0" ]; then sed -ri 's/ac_add_options MOZ_PGO=1//g' snapcraft.yaml fi + # Until launchpad is able to handle platforms definition, the snapcraft yaml + # hides them and we want to unhide. + sed -ri 's/^##CROSS-COMPILATION##//g' snapcraft.yaml + MAX_MEMORY_GB=$(free -g | awk '/Mem:/ { print $2 - 1 }') # setting parallelism does not work with core24 ? @@ -111,19 +114,8 @@ if [ "${USE_SNAP_FROM_STORE_OR_MC}" = "0" ]; then sed -ri "s|\\\$CRAFT_PARALLEL_BUILD_COUNT|${MAX_CPUS}|g" snapcraft.yaml grep "make -j" snapcraft.yaml - # CRAFT_PARTS_PACKAGE_REFRESH required to avoid snapcraft running apt-get update - # especially for stage-packages - # - # Passing --build-for=amd64 when building on amd64 fails with the following - # when running on a non-multiarch-ready snapcraft.yaml: - # Could not make build plan: build-on architectures in snapcraft.yaml does not match host architecture (amd64). - if [ "${BRANCH}" != "esr" ] && [ "${BRANCH}" != "esr-128" ]; then - MAYBE_BUILD_FOR_ARCH="--build-for=${ARCH}" - fi; - SNAPCRAFT_BUILD_ENVIRONMENT_MEMORY="${MAX_MEMORY_GB}G" \ - CRAFT_PARTS_PACKAGE_REFRESH=0 \ - snapcraft --destructive-mode --verbose "${MAYBE_BUILD_FOR_ARCH}" + snapcraft --destructive-mode --verbosity verbose --build-for "${ARCH}" elif [ "${USE_SNAP_FROM_STORE_OR_MC}" = "store" ]; then mkdir from-snap-store && cd from-snap-store @@ -159,7 +151,7 @@ else TASKCLUSTER_API_ROOT="https://firefox-ci-tc.services.mozilla.com/api" - URL_TASK="${TASKCLUSTER_API_ROOT}/index/v1/task/gecko.v2.mozilla-central.${USE_SNAP_FROM_STORE_OR_MC}.firefox.amd64-${INDEX_NAME}" + URL_TASK="${TASKCLUSTER_API_ROOT}/index/v1/task/gecko.v2.mozilla-central.${USE_SNAP_FROM_STORE_OR_MC}.firefox.${ARCH}-${INDEX_NAME}" PKGS_TASK_ID=$(curl "${URL_TASK}" | jq -r '.taskId') if [ -z "${PKGS_TASK_ID}" ]; then diff --git a/taskcluster/docker/snap-coreXX-build/system-setup.sh b/taskcluster/docker/snap-coreXX-build/system-setup.sh new file mode 100755 index 00000000000..95b016b1f50 --- /dev/null +++ b/taskcluster/docker/snap-coreXX-build/system-setup.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +set -ex + +SNAP_BASE=$1 + +# Limit cross compilation support to core24 because it's not working before +if [ "${SNAP_BASE}" != "core24" ]; then + echo "Cross-compilation not supported before core24 base" + exit 0 +fi; + +# GPG: we need to do it here otherwhise snapcraft tries to do it as user and fails +CROSS_GPG_KEYID="F6ECB3762474EDA9D21B7022871920D1991BC93C" +KEYRING_FILENAME="/etc/apt/keyrings/craft-$(echo "${CROSS_GPG_KEYID}" | rev | cut -c 1-8 | rev | tr -d '\n').gpg" +gpg --batch --no-default-keyring --with-colons --keyring gnupg-ring:"${KEYRING_FILENAME}" --homedir /tmp --keyserver keyserver.ubuntu.com --recv-keys "${CROSS_GPG_KEYID}" +chmod 0444 "${KEYRING_FILENAME}" + +sed -ri 's/^deb http/deb [arch=amd64] http/g' /etc/apt/sources.list + +# snapcraft 8.8+ looks for ports.ubuntu.com +# > 2025-04-15 06:31:53.558 Reading sources in '/etc/apt/sources.list.d/ubuntu.sources' looking for 'ports.ubuntu.com/' +sed -ri "s|URIs: http://archive.ubuntu.com/ubuntu/|URIs: http://archive.ubuntu.com/ubuntu/\nArchitectures: amd64|g" /etc/apt/sources.list.d/ubuntu.sources +sed -ri "s|URIs: http://security.ubuntu.com/ubuntu/|URIs: http://security.ubuntu.com/ubuntu/\nArchitectures: amd64|g" /etc/apt/sources.list.d/ubuntu.sources + +# This needs to exactly match what snapcraft will produce at https://github.com/canonical/craft-archives/blob/015bbe59d53cad5e262ab7fddba3a6080cb90391/craft_archives/repo/apt_sources_manager.py#L161-L165 +# If it's not the case then the file will be re-generated, and snapcraft will try to do things with .sources files and dpkg architecture that will badly break +cat < /etc/apt/sources.list.d/craft-http_ports_ubuntu_com.sources +Types: deb +URIs: http://ports.ubuntu.com +Suites: noble noble-updates noble-security noble-backports +Components: main multiverse universe +Architectures: armhf arm64 +Signed-By: ${KEYRING_FILENAME} +EOF + +# snapcraft 8.8+ will want both and will rewrite ubuntu.sources +dpkg --add-architecture "arm64" +dpkg --add-architecture "armhf" +apt-get update +apt-get --fix-broken install -y +apt-get upgrade -y diff --git a/taskcluster/gecko_taskgraph/transforms/snap_test.py b/taskcluster/gecko_taskgraph/transforms/snap_test.py index 3e1d299e3f6..4d3645ca992 100644 --- a/taskcluster/gecko_taskgraph/transforms/snap_test.py +++ b/taskcluster/gecko_taskgraph/transforms/snap_test.py @@ -19,10 +19,10 @@ transforms = TransformSequence() @transforms.add def fill_template(config, tasks): for task in tasks: + assert "snap-upstream-test-" in task.get("label") + test_type = task.get("attributes")["snap_test_type"] test_release = task.get("attributes")["snap_test_release"] - - assert "-test-" in task.get("label") task["label"] = task.get("label").replace( "-test-", "-test-" + test_type + "-" + test_release + "-" ) @@ -31,30 +31,18 @@ def fill_template(config, tasks): assert dep inherit_treeherder_from_dep(task, dep) - task_platform = task["task"]["extra"]["treeherder"]["machine"]["platform"] - # Disambiguate the treeherder symbol. - full_platform_collection = ( - task_platform - + "-snap-" - + task.get("label").split("-")[-2] - + "-" - + test_release - + "-" - + task.get("label").split("-")[-1] + th_group = dep.task["extra"]["treeherder"]["groupSymbol"].replace("B", "Sel") + th_symbol = ( + f"{test_type}-{test_release}-{dep.task['extra']['treeherder']['symbol']}" ) - - (platform, collection) = full_platform_collection.split("/") - task["task"]["extra"]["treeherder"]["collection"] = {collection: True} - task["task"]["extra"]["treeherder"]["machine"]["platform"] = platform - task["task"]["extra"]["treeherder-platform"] = full_platform_collection - task["task"]["metadata"]["name"] = task["label"] + task["treeherder"]["symbol"] = f"{th_group}({th_symbol})" timeout = 10 - if collection != "opt": + if dep.attributes.get("build_type") != "opt": timeout = 60 - task["task"]["payload"]["env"]["BUILD_IS_DEBUG"] = "1" + task["worker"]["env"]["BUILD_IS_DEBUG"] = "1" - task["task"]["payload"]["env"]["TEST_TIMEOUT"] = f"{timeout}" + task["worker"]["env"]["TEST_TIMEOUT"] = f"{timeout}" yield task diff --git a/taskcluster/kinds/fetch/toolchains.yml b/taskcluster/kinds/fetch/toolchains.yml index 0848255844c..661f2143421 100644 --- a/taskcluster/kinds/fetch/toolchains.yml +++ b/taskcluster/kinds/fetch/toolchains.yml @@ -533,9 +533,9 @@ jdk-8-linux64: description: Java 8 JDK fetch: type: static-url - url: https://github.com/adoptium/temurin8-binaries/releases/download/jdk8u442-b06/OpenJDK8U-jdk_x64_linux_hotspot_8u442b06.tar.gz - sha256: 5b0a0145e7790552a9c8767b4680074c4628ec276e5bb278b61d85cf90facafa - size: 103096556 + url: https://github.com/adoptium/temurin8-binaries/releases/download/jdk8u452-b09/OpenJDK8U-jdk_x64_linux_hotspot_8u452b09.tar.gz + sha256: 9448308a21841960a591b47927cf2d44fdc4c0533a5f8111a4b243a6bafb5d27 + size: 103082903 artifact-name: jdk-8.tar.zst strip-components: 1 add-prefix: jdk-8/ diff --git a/taskcluster/kinds/snap-upstream-build/kind.yml b/taskcluster/kinds/snap-upstream-build/kind.yml index 3510f200401..7af39e46e90 100644 --- a/taskcluster/kinds/snap-upstream-build/kind.yml +++ b/taskcluster/kinds/snap-upstream-build/kind.yml @@ -18,7 +18,6 @@ task-defaults: treeherder: kind: build tier: 2 - symbol: B worker-type: b-linux-xlarge-gcp run: checkout: true @@ -27,6 +26,9 @@ task-defaults: - name: public/build type: directory path: /builds/worker/artifacts/ + - name: public/snapcraft-logs + type: directory + path: /builds/worker/.local/state/snapcraft/log max-run-time: 900 env: UPLOAD_DIR: artifacts @@ -39,7 +41,7 @@ tasks: amd64-nightly/opt: description: Build Firefox Nightly as a Snap using upstream definition attributes: - build_platform: linux64-snap-upstream-nightly + build_platform: linux64-snap build_type: opt index: product: firefox @@ -53,12 +55,13 @@ tasks: command: >- ./run.sh nightly amd64 treeherder: - platform: linux64-snap-amd64-nightly/opt + platform: linux64-snap/opt + symbol: B-nightly(amd64) amd64-nightly/debug: description: Build Firefox Nightly (debug) as a Snap using upstream definition attributes: - build_platform: linux64-snap-upstream-nightly + build_platform: linux64-snap build_type: debug index: product: firefox @@ -72,12 +75,93 @@ tasks: command: >- ./run.sh nightly amd64 --debug treeherder: - platform: linux64-snap-amd64-nightly/debug + platform: linux64-snap/debug + symbol: B-nightly(amd64) + + armhf-nightly/opt: + description: Build Firefox Nightly as a Snap using upstream definition + attributes: + build_platform: linux-armhf-snap + build_type: opt + index: + product: firefox + job-name: armhf-nightly + type: generic + worker: + docker-image: {in-tree: snap-build-core24} + max-run-time: 9000 + run: + using: run-task + command: >- + ./run.sh nightly armhf + treeherder: + platform: linux64-snap/opt + symbol: B-nightly(armhf) + + armhf-nightly/debug: + description: Build Firefox Nightly (debug) as a Snap using upstream definition + attributes: + build_platform: linux-armhf-snap + build_type: debug + index: + product: firefox + job-name: armhf-nightly-debug + type: generic + worker: + docker-image: {in-tree: snap-build-core24} + max-run-time: 7200 + run: + using: run-task + command: >- + ./run.sh nightly armhf --debug + treeherder: + platform: linux64-snap/debug + symbol: B-nightly(armhf) + + arm64-nightly/opt: + description: Build Firefox Nightly as a Snap using upstream definition + attributes: + build_platform: linux-arm64-snap + build_type: opt + index: + product: firefox + job-name: arm64-nightly + type: generic + worker: + docker-image: {in-tree: snap-build-core24} + max-run-time: 9000 + run: + using: run-task + command: >- + ./run.sh nightly arm64 + treeherder: + platform: linux64-snap/opt + symbol: B-nightly(arm64) + + arm64-nightly/debug: + description: Build Firefox Nightly (debug) as a Snap using upstream definition + attributes: + build_platform: linux-arm64-snap + build_type: debug + index: + product: firefox + job-name: arm64-nightly-debug + type: generic + worker: + docker-image: {in-tree: snap-build-core24} + max-run-time: 7200 + run: + using: run-task + command: >- + ./run.sh nightly arm64 --debug + treeherder: + platform: linux64-snap/debug + symbol: B-nightly(arm64) amd64-beta/opt: description: Build Firefox Beta as a Snap using upstream definition attributes: - build_platform: linux64-snap-upstream-beta + build_platform: linux64-snap build_type: opt index: product: firefox @@ -91,12 +175,13 @@ tasks: command: >- ./run.sh beta amd64 treeherder: - platform: linux64-snap-amd64-beta/opt + platform: linux64-snap/opt + symbol: B-beta(amd64) amd64-beta/debug: description: Build Firefox Beta (debug) as a Snap using upstream definition attributes: - build_platform: linux64-snap-upstream-beta + build_platform: linux64-snap build_type: debug index: product: firefox @@ -110,12 +195,13 @@ tasks: command: >- ./run.sh beta amd64 --debug treeherder: - platform: linux64-snap-amd64-beta/debug + platform: linux64-snap/debug + symbol: B-beta(amd64) amd64-betacore24/opt: description: Build Firefox Beta (Core24) as a Snap using upstream definition attributes: - build_platform: linux64-snap-upstream-betacore24 + build_platform: linux64-snap build_type: opt index: product: firefox @@ -129,12 +215,13 @@ tasks: command: >- ./run.sh beta-core24 amd64 treeherder: - platform: linux64-snap-amd64-betacore24/opt + platform: linux64-snap/opt + symbol: B-beta24(amd64) amd64-betacore24/debug: description: Build Firefox Beta (Core24 debug) as a Snap using upstream definition attributes: - build_platform: linux64-snap-upstream-betacore24 + build_platform: linux64-snap build_type: debug index: product: firefox @@ -148,12 +235,13 @@ tasks: command: >- ./run.sh beta-core24 amd64 --debug treeherder: - platform: linux64-snap-amd64-betacore24/debug + platform: linux64-snap/debug + symbol: B-beta24(amd64) amd64-stable/opt: description: Build Firefox Stable as a Snap using upstream definition attributes: - build_platform: linux64-snap-upstream-stable + build_platform: linux64-snap build_type: opt index: product: firefox @@ -167,12 +255,13 @@ tasks: command: >- ./run.sh stable amd64 treeherder: - platform: linux64-snap-amd64-stable/opt + platform: linux64-snap/opt + symbol: B-stable(amd64) amd64-stable/debug: description: Build Firefox Stable (debug) as a Snap using upstream definition attributes: - build_platform: linux64-snap-upstream-stable + build_platform: linux64-snap build_type: debug index: product: firefox @@ -186,12 +275,13 @@ tasks: command: >- ./run.sh stable amd64 --debug treeherder: - platform: linux64-snap-amd64-stable/debug + platform: linux64-snap/debug + symbol: B-stable(amd64) amd64-stablecore24/opt: description: Build Firefox Stable (Core24) as a Snap using upstream definition attributes: - build_platform: linux64-snap-upstream-stablecore24 + build_platform: linux64-snap build_type: opt index: product: firefox @@ -205,12 +295,13 @@ tasks: command: >- ./run.sh stable-core24 amd64 treeherder: - platform: linux64-snap-amd64-stablecore24/opt + platform: linux64-snap/opt + symbol: B-stable24(amd64) amd64-stablecore24/debug: description: Build Firefox Stable (Core24 debug) as a Snap using upstream definition attributes: - build_platform: linux64-snap-upstream-stablecore24 + build_platform: linux64-snap build_type: debug index: product: firefox @@ -224,12 +315,13 @@ tasks: command: >- ./run.sh stable-core24 amd64 --debug treeherder: - platform: linux64-snap-amd64-stablecore24/debug + platform: linux64-snap/debug + symbol: B-stable24(amd64) amd64-esr128/opt: description: Build Firefox ESR 128 as a Snap using upstream definition attributes: - build_platform: linux64-snap-upstream-esr128 + build_platform: linux64-snap build_type: opt index: product: firefox @@ -243,12 +335,13 @@ tasks: command: >- ./run.sh esr-128 amd64 treeherder: - platform: linux64-snap-amd64-esr128/opt + platform: linux64-snap/opt + symbol: B-esr(amd64) amd64-esr128/debug: description: Build Firefox ESR 128 (debug) as a Snap using upstream definition attributes: - build_platform: linux64-snap-upstream-esr128 + build_platform: linux64-snap build_type: debug index: product: firefox @@ -262,12 +355,13 @@ tasks: command: >- ./run.sh esr-128 amd64 --debug treeherder: - platform: linux64-snap-amd64-esr128/debug + platform: linux64-snap/debug + symbol: B-esr(amd64) amd64-try/opt: description: Build Firefox Nightly as a Snap using upstream definition and try repo attributes: - build_platform: linux64-snap-upstream-try + build_platform: linux64-snap build_type: opt index: product: firefox @@ -281,12 +375,13 @@ tasks: command: >- ./run.sh try amd64 treeherder: - platform: linux64-snap-amd64-try/opt + platform: linux64-snap/opt + symbol: B-try(amd64) amd64-try/debug: description: Build Firefox Nightly (debug) as a Snap using upstream definition and try repo attributes: - build_platform: linux64-snap-upstream-try + build_platform: linux64-snap build_type: debug index: product: firefox @@ -300,4 +395,85 @@ tasks: command: >- ./run.sh try amd64 --debug treeherder: - platform: linux64-snap-amd64-try/debug + platform: linux64-snap/debug + symbol: B-try(amd64) + + armhf-try/opt: + description: Build Firefox Nightly as a Snap using upstream definition and try repo + attributes: + build_platform: linux-armhf-snap + build_type: opt + index: + product: firefox + job-name: armhf-try + type: generic + worker: + docker-image: {in-tree: snap-build-core24} + max-run-time: 9000 + run: + using: run-task + command: >- + ./run.sh try armhf + treeherder: + platform: linux64-snap/opt + symbol: B-try(armhf) + + armhf-try/debug: + description: Build Firefox Nightly (debug) as a Snap using upstream definition and try repo + attributes: + build_platform: linux-armhf-snap + build_type: debug + index: + product: firefox + job-name: armhf-try-debug + type: generic + worker: + docker-image: {in-tree: snap-build-core24} + max-run-time: 7200 + run: + using: run-task + command: >- + ./run.sh try armhf --debug + treeherder: + platform: linux64-snap/debug + symbol: B-try(armhf) + + arm64-try/opt: + description: Build Firefox Nightly as a Snap using upstream definition and try repo + attributes: + build_platform: linux-arm64-snap + build_type: opt + index: + product: firefox + job-name: arm64-try + type: generic + worker: + docker-image: {in-tree: snap-build-core24} + max-run-time: 9000 + run: + using: run-task + command: >- + ./run.sh try arm64 + treeherder: + platform: linux64-snap/opt + symbol: B-try(arm64) + + arm64-try/debug: + description: Build Firefox Nightly (debug) as a Snap using upstream definition and try repo + attributes: + build_platform: linux-arm64-snap + build_type: debug + index: + product: firefox + job-name: arm64-try-debug + type: generic + worker: + docker-image: {in-tree: snap-build-core24} + max-run-time: 7200 + run: + using: run-task + command: >- + ./run.sh try arm64 --debug + treeherder: + platform: linux64-snap/debug + symbol: B-try(arm64) diff --git a/taskcluster/kinds/snap-upstream-test/kind.yml b/taskcluster/kinds/snap-upstream-test/kind.yml index 99257fdcf01..5e0f19825b9 100644 --- a/taskcluster/kinds/snap-upstream-test/kind.yml +++ b/taskcluster/kinds/snap-upstream-test/kind.yml @@ -4,14 +4,15 @@ --- loader: taskgraph.loader.transform:loader -only-for-attributes: - - build_platform +only-for-build-platforms: + - linux64-snap/opt + - linux64-snap/debug transforms: - taskgraph.transforms.from_deps - gecko_taskgraph.transforms.job - - gecko_taskgraph.transforms.task - gecko_taskgraph.transforms.snap_test + - gecko_taskgraph.transforms.task kind-dependencies: - snap-upstream-build @@ -53,9 +54,6 @@ tasks: run: command: >- export TASKCLUSTER_ROOT_DIR=$PWD && cd $MOZ_FETCHES_DIR/ && ./tests.sh --basic - treeherder: - platform: linux64/opt - symbol: Sel(basic) attributes: snap_test_type: basic snap_test_release: "2204" @@ -68,9 +66,6 @@ tasks: run: command: >- export TASKCLUSTER_ROOT_DIR=$PWD && cd $MOZ_FETCHES_DIR/ && ./tests.sh --qa - treeherder: - platform: linux64/opt - symbol: Sel(QA) attributes: snap_test_type: qa snap_test_release: "2204" @@ -83,9 +78,6 @@ tasks: run: command: >- export TASKCLUSTER_ROOT_DIR=$PWD && cd $MOZ_FETCHES_DIR/ && ./tests.sh --basic - treeherder: - platform: linux64/opt - symbol: Sel(basic) attributes: snap_test_type: basic snap_test_release: "2404" @@ -98,9 +90,6 @@ tasks: run: command: >- export TASKCLUSTER_ROOT_DIR=$PWD && cd $MOZ_FETCHES_DIR/ && ./tests.sh --qa - treeherder: - platform: linux64/opt - symbol: Sel(QA) attributes: snap_test_type: qa snap_test_release: "2404" diff --git a/taskcluster/kinds/test/browsertime-desktop.yml b/taskcluster/kinds/test/browsertime-desktop.yml index 1ef7c4ac4e8..eb9bf8f162d 100644 --- a/taskcluster/kinds/test/browsertime-desktop.yml +++ b/taskcluster/kinds/test/browsertime-desktop.yml @@ -1231,6 +1231,10 @@ browsertime-indexeddb: apps: [firefox, chrome] run-visual-metrics: false subtests: + - [idb-open-many-par, idb-opn-mp] + - [idb-open-many-seq, idb-opn-ms] + - [idb-open-few-par, idb-opn-fp] + - [idb-open-few-seq, idb-opn-fs] - addMab1 - addMabN - addMar1 diff --git a/taskcluster/kinds/toolchain/android.yml b/taskcluster/kinds/toolchain/android.yml index 531ba5113c5..04334e94f67 100644 --- a/taskcluster/kinds/toolchain/android.yml +++ b/taskcluster/kinds/toolchain/android.yml @@ -156,7 +156,7 @@ linux64-jdk-repack: toolchain-artifact: project/gecko/jdk/jdk-linux.tar.zst toolchain-alias: linux64-jdk toolchain-env: - JAVA_HOME: "/builds/worker/fetches/jdk/jdk-17.0.14+7" + JAVA_HOME: "/builds/worker/fetches/jdk/jdk-17.0.15+6" linux64-android-sdk-linux-repack: description: "Android SDK (Linux) repack toolchain build" diff --git a/testing/mozbase/mozcrash/mozcrash/mozcrash.py b/testing/mozbase/mozcrash/mozcrash/mozcrash.py index 60055cafc70..95f589761f5 100644 --- a/testing/mozbase/mozcrash/mozcrash/mozcrash.py +++ b/testing/mozbase/mozcrash/mozcrash/mozcrash.py @@ -818,7 +818,7 @@ def cleanup_pending_crash_reports(): ) elif mozinfo.isMac: location = os.path.expanduser( - "~/Library/Application Support/firefox/Crash Reports" + "~/Library/Application Support/Firefox/Crash Reports" ) else: location = os.path.expanduser("~/.mozilla/firefox/Crash Reports") diff --git a/testing/perfdocs/generated/raptor-metrics.rst b/testing/perfdocs/generated/raptor-metrics.rst index 1ecc3b3fa5f..82eaa7db925 100644 --- a/testing/perfdocs/generated/raptor-metrics.rst +++ b/testing/perfdocs/generated/raptor-metrics.rst @@ -232,7 +232,7 @@ Specifies cumulative CPU usage in milliseconds across all threads of the process * **Aliases**: cpuTime * **Tests using it**: * **Benchmarks**: `speedometer `__, `speedometer3 `__, `youtube-playback-h264-1080p30 `__, `youtube-playback-h264-1080p60 `__, `youtube-playback-h264-full-1080p30 `__, `youtube-playback-h264-full-1080p60 `__, `youtube-playback-v9-1080p30 `__, `youtube-playback-v9-1080p60 `__, `youtube-playback-v9-full-1080p30 `__, `youtube-playback-v9-full-1080p60 `__ - * **Custom**: `addMab1 `__, `addMabN `__, `addMar1 `__, `addMarN `__, `addMbl1 `__, `addMblN `__, `addkAB1 `__, `addkABN `__, `addkAR1 `__, `addkARN `__, `addkBL1 `__, `addkBLN `__, `getkeyrng `__ + * **Custom**: `addMab1 `__, `addMabN `__, `addMar1 `__, `addMarN `__, `addMbl1 `__, `addMblN `__, `addkAB1 `__, `addkABN `__, `addkAR1 `__, `addkARN `__, `addkBL1 `__, `addkBLN `__, `getkeyrng `__, `idb-open-few-par `__, `idb-open-few-seq `__, `idb-open-many-par `__, `idb-open-many-seq `__ * **Desktop**: `amazon `__, `bing-search `__, `buzzfeed `__, `cnn `__, `docomo `__, `ebay `__, `espn `__, `expedia `__, `facebook `__, `fandom `__, `google-docs `__, `google-mail `__, `google-search `__, `google-slides `__, `imdb `__, `imgur `__, `instagram `__, `linkedin `__, `microsoft `__, `netflix `__, `nytimes `__, `office `__, `openai `__, `outlook `__, `paypal `__, `pinterest `__, `reddit `__, `samsung `__, `tiktok `__, `tumblr `__, `twitch `__, `twitter `__, `weather `__, `wikia `__, `wikipedia `__, `yahoo-mail `__, `youtube `__ * **Interactive**: `cnn-nav `__, `facebook-nav `__, `reddit-billgates-ama `__, `reddit-billgates-post-1 `__, `reddit-billgates-post-2 `__ * **Mobile**: `allrecipes `__, `amazon `__, `amazon-search `__, `bild-de `__, `bing `__, `bing-search-restaurants `__, `booking `__, `cnn `__, `cnn-ampstories `__, `dailymail `__, `ebay-kleinanzeigen `__, `ebay-kleinanzeigen-search `__, `espn `__, `facebook `__, `facebook-cristiano `__, `google `__, `google-maps `__, `google-search-restaurants `__, `imdb `__, `instagram `__, `microsoft-support `__, `reddit `__, `sina `__, `stackoverflow `__, `wikipedia `__, `youtube `__, `youtube-watch `__ diff --git a/testing/perfdocs/generated/raptor.rst b/testing/perfdocs/generated/raptor.rst index 8c828559190..6deb9ae0007 100644 --- a/testing/perfdocs/generated/raptor.rst +++ b/testing/perfdocs/generated/raptor.rst @@ -10875,7 +10875,7 @@ Browsertime tests that use a custom pageload test script. These use the pageload * **gecko profile entries**: 131072000 * **gecko profile features**: js,stackwalk,cpu,memory * **gecko profile threads**: GeckoMain,DOM Worker,IndexedDB - * **link searchfox**: ``__ + * **link searchfox**: ``__ * **lower is better**: true * **measure**: cpuTime * **output timeout**: 2000000 @@ -11074,7 +11074,7 @@ Browsertime tests that use a custom pageload test script. These use the pageload * **gecko profile entries**: 131072000 * **gecko profile features**: js,stackwalk,cpu,memory * **gecko profile threads**: GeckoMain,DOM Worker,IndexedDB - * **link searchfox**: ``__ + * **link searchfox**: ``__ * **lower is better**: true * **measure**: cpuTime * **output timeout**: 2000000 @@ -11273,7 +11273,7 @@ Browsertime tests that use a custom pageload test script. These use the pageload * **gecko profile entries**: 131072000 * **gecko profile features**: js,stackwalk,cpu,memory * **gecko profile threads**: GeckoMain,DOM Worker,IndexedDB - * **link searchfox**: ``__ + * **link searchfox**: ``__ * **lower is better**: true * **measure**: cpuTime * **output timeout**: 2000000 @@ -11472,7 +11472,7 @@ Browsertime tests that use a custom pageload test script. These use the pageload * **gecko profile entries**: 131072000 * **gecko profile features**: js,stackwalk,cpu,memory * **gecko profile threads**: GeckoMain,DOM Worker,IndexedDB - * **link searchfox**: ``__ + * **link searchfox**: ``__ * **lower is better**: true * **measure**: cpuTime * **output timeout**: 2000000 @@ -11671,7 +11671,7 @@ Browsertime tests that use a custom pageload test script. These use the pageload * **gecko profile entries**: 131072000 * **gecko profile features**: js,stackwalk,cpu,memory * **gecko profile threads**: GeckoMain,DOM Worker,IndexedDB - * **link searchfox**: ``__ + * **link searchfox**: ``__ * **lower is better**: true * **measure**: cpuTime * **output timeout**: 2000000 @@ -11870,7 +11870,7 @@ Browsertime tests that use a custom pageload test script. These use the pageload * **gecko profile entries**: 131072000 * **gecko profile features**: js,stackwalk,cpu,memory * **gecko profile threads**: GeckoMain,DOM Worker,IndexedDB - * **link searchfox**: ``__ + * **link searchfox**: ``__ * **lower is better**: true * **measure**: cpuTime * **output timeout**: 2000000 @@ -12069,7 +12069,7 @@ Browsertime tests that use a custom pageload test script. These use the pageload * **gecko profile entries**: 131072000 * **gecko profile features**: js,stackwalk,cpu,memory * **gecko profile threads**: GeckoMain,DOM Worker,IndexedDB - * **link searchfox**: ``__ + * **link searchfox**: ``__ * **lower is better**: true * **measure**: cpuTime * **output timeout**: 2000000 @@ -12268,7 +12268,7 @@ Browsertime tests that use a custom pageload test script. These use the pageload * **gecko profile entries**: 131072000 * **gecko profile features**: js,stackwalk,cpu,memory * **gecko profile threads**: GeckoMain,DOM Worker,IndexedDB - * **link searchfox**: ``__ + * **link searchfox**: ``__ * **lower is better**: true * **measure**: cpuTime * **output timeout**: 2000000 @@ -12467,7 +12467,7 @@ Browsertime tests that use a custom pageload test script. These use the pageload * **gecko profile entries**: 131072000 * **gecko profile features**: js,stackwalk,cpu,memory * **gecko profile threads**: GeckoMain,DOM Worker,IndexedDB - * **link searchfox**: ``__ + * **link searchfox**: ``__ * **lower is better**: true * **measure**: cpuTime * **output timeout**: 2000000 @@ -12666,7 +12666,7 @@ Browsertime tests that use a custom pageload test script. These use the pageload * **gecko profile entries**: 131072000 * **gecko profile features**: js,stackwalk,cpu,memory * **gecko profile threads**: GeckoMain,DOM Worker,IndexedDB - * **link searchfox**: ``__ + * **link searchfox**: ``__ * **lower is better**: true * **measure**: cpuTime * **output timeout**: 2000000 @@ -12865,7 +12865,7 @@ Browsertime tests that use a custom pageload test script. These use the pageload * **gecko profile entries**: 131072000 * **gecko profile features**: js,stackwalk,cpu,memory * **gecko profile threads**: GeckoMain,DOM Worker,IndexedDB - * **link searchfox**: ``__ + * **link searchfox**: ``__ * **lower is better**: true * **measure**: cpuTime * **output timeout**: 2000000 @@ -13064,7 +13064,7 @@ Browsertime tests that use a custom pageload test script. These use the pageload * **gecko profile entries**: 131072000 * **gecko profile features**: js,stackwalk,cpu,memory * **gecko profile threads**: GeckoMain,DOM Worker,IndexedDB - * **link searchfox**: ``__ + * **link searchfox**: ``__ * **lower is better**: true * **measure**: cpuTime * **output timeout**: 2000000 @@ -13544,7 +13544,7 @@ Browsertime tests that use a custom pageload test script. These use the pageload * **gecko profile entries**: 131072000 * **gecko profile features**: js,stackwalk,cpu,memory * **gecko profile threads**: GeckoMain,DOM Worker,IndexedDB - * **link searchfox**: ``__ + * **link searchfox**: ``__ * **lower is better**: true * **measure**: cpuTime * **output timeout**: 2000000 @@ -14892,6 +14892,802 @@ Browsertime tests that use a custom pageload test script. These use the pageload +.. dropdown:: idb-open-few-par + :class-container: anchor-id-idb-open-few-par-c + + * Command to Run Locally + + .. code-block:: + + ./mach raptor -t idb-open-few-par + + **Owner**: DOM Lifecycle, Workers and Storage Team + + **Description**: Measures opening a few IndexedDB databases in parallel + + * **alert threshold**: 2.0 + * **apps**: firefox, chrome, safari + * **browser cycles**: 1 + * **browsertime args**: --browsertime.iterations=1000 --browsertime.parallel=1 + * **custom data**: true + * **expected**: pass + * **gecko profile entries**: 131072000 + * **gecko profile features**: js,stackwalk,cpu,memory + * **gecko profile threads**: GeckoMain,DOM Worker,IndexedDB + * **link searchfox**: ``__ + * **lower is better**: true + * **measure**: cpuTime + * **output timeout**: 2000000 + * **page cycles**: 1 + * **page timeout**: 1800000 + * **playback**: mitmproxy + * **playback pageset manifest**: mitm8-linux-firefox-example.manifest + * **subtest unit**: ms + * **test script**: indexeddb_open.js + * **test summary**: flatten + * **test url**: ``__ + * **type**: pageload + * **unit**: ms + * **use live sites**: false + * **Test Task**: + + .. list-table:: **test-linux1804-64-nightlyasrelease-qr/opt** + :widths: 30 15 15 15 15 + :header-rows: 1 + + * - **Test Name** + - mozilla-central + - autoland + - mozilla-release + - mozilla-beta + * - **browsertime-indexeddb-firefox-idb-open-few-par** + - ❌ + - ❌ + - ❌ + - ❌ + + + .. list-table:: **test-linux1804-64-qr/opt** + :widths: 30 15 15 15 15 + :header-rows: 1 + + * - **Test Name** + - mozilla-central + - autoland + - mozilla-release + - mozilla-beta + * - **browsertime-indexeddb-firefox-idb-open-few-par** + - ❌ + - ❌ + - ❌ + - ❌ + + + .. list-table:: **test-linux1804-64-shippable-qr/opt** + :widths: 30 15 15 15 15 + :header-rows: 1 + + * - **Test Name** + - mozilla-central + - autoland + - mozilla-release + - mozilla-beta + * - **browsertime-indexeddb-chrome-idb-open-few-par** + - ❌ + - ❌ + - ❌ + - ❌ + * - **browsertime-indexeddb-firefox-idb-open-few-par** + - ✅ + - ✅ + - ❌ + - ❌ + + + .. list-table:: **test-macosx1470-64-nightlyasrelease/opt** + :widths: 30 15 15 15 15 + :header-rows: 1 + + * - **Test Name** + - mozilla-central + - autoland + - mozilla-release + - mozilla-beta + * - **browsertime-indexeddb-firefox-idb-open-few-par** + - ❌ + - ❌ + - ❌ + - ❌ + + + .. list-table:: **test-macosx1470-64-shippable/opt** + :widths: 30 15 15 15 15 + :header-rows: 1 + + * - **Test Name** + - mozilla-central + - autoland + - mozilla-release + - mozilla-beta + * - **browsertime-indexeddb-chrome-idb-open-few-par** + - ❌ + - ❌ + - ❌ + - ❌ + * - **browsertime-indexeddb-firefox-idb-open-few-par** + - ✅ + - ✅ + - ❌ + - ❌ + + + .. list-table:: **test-macosx1470-64/opt** + :widths: 30 15 15 15 15 + :header-rows: 1 + + * - **Test Name** + - mozilla-central + - autoland + - mozilla-release + - mozilla-beta + * - **browsertime-indexeddb-firefox-idb-open-few-par** + - ❌ + - ❌ + - ❌ + - ❌ + + + .. list-table:: **test-windows11-64-24h2-nightlyasrelease/opt** + :widths: 30 15 15 15 15 + :header-rows: 1 + + * - **Test Name** + - mozilla-central + - autoland + - mozilla-release + - mozilla-beta + * - **browsertime-indexeddb-firefox-idb-open-few-par** + - ❌ + - ❌ + - ❌ + - ❌ + + + .. list-table:: **test-windows11-64-24h2-shippable/opt** + :widths: 30 15 15 15 15 + :header-rows: 1 + + * - **Test Name** + - mozilla-central + - autoland + - mozilla-release + - mozilla-beta + * - **browsertime-indexeddb-chrome-idb-open-few-par** + - ❌ + - ❌ + - ❌ + - ❌ + * - **browsertime-indexeddb-firefox-idb-open-few-par** + - ✅ + - ✅ + - ❌ + - ❌ + + + .. list-table:: **test-windows11-64-24h2/opt** + :widths: 30 15 15 15 15 + :header-rows: 1 + + * - **Test Name** + - mozilla-central + - autoland + - mozilla-release + - mozilla-beta + * - **browsertime-indexeddb-firefox-idb-open-few-par** + - ❌ + - ❌ + - ❌ + - ❌ + + + +.. dropdown:: idb-open-few-seq + :class-container: anchor-id-idb-open-few-seq-c + + * Command to Run Locally + + .. code-block:: + + ./mach raptor -t idb-open-few-seq + + **Owner**: DOM Lifecycle, Workers and Storage Team + + **Description**: Measures opening a few IndexedDB databases in sequence + + * **alert threshold**: 2.0 + * **apps**: firefox, chrome, safari + * **browser cycles**: 1 + * **browsertime args**: --browsertime.iterations=1000 --browsertime.parallel=0 + * **custom data**: true + * **expected**: pass + * **gecko profile entries**: 131072000 + * **gecko profile features**: js,stackwalk,cpu,memory + * **gecko profile threads**: GeckoMain,DOM Worker,IndexedDB + * **link searchfox**: ``__ + * **lower is better**: true + * **measure**: cpuTime + * **output timeout**: 2000000 + * **page cycles**: 1 + * **page timeout**: 1800000 + * **playback**: mitmproxy + * **playback pageset manifest**: mitm8-linux-firefox-example.manifest + * **subtest unit**: ms + * **test script**: indexeddb_open.js + * **test summary**: flatten + * **test url**: ``__ + * **type**: pageload + * **unit**: ms + * **use live sites**: false + * **Test Task**: + + .. list-table:: **test-linux1804-64-nightlyasrelease-qr/opt** + :widths: 30 15 15 15 15 + :header-rows: 1 + + * - **Test Name** + - mozilla-central + - autoland + - mozilla-release + - mozilla-beta + * - **browsertime-indexeddb-firefox-idb-open-few-seq** + - ❌ + - ❌ + - ❌ + - ❌ + + + .. list-table:: **test-linux1804-64-qr/opt** + :widths: 30 15 15 15 15 + :header-rows: 1 + + * - **Test Name** + - mozilla-central + - autoland + - mozilla-release + - mozilla-beta + * - **browsertime-indexeddb-firefox-idb-open-few-seq** + - ❌ + - ❌ + - ❌ + - ❌ + + + .. list-table:: **test-linux1804-64-shippable-qr/opt** + :widths: 30 15 15 15 15 + :header-rows: 1 + + * - **Test Name** + - mozilla-central + - autoland + - mozilla-release + - mozilla-beta + * - **browsertime-indexeddb-chrome-idb-open-few-seq** + - ❌ + - ❌ + - ❌ + - ❌ + * - **browsertime-indexeddb-firefox-idb-open-few-seq** + - ✅ + - ✅ + - ❌ + - ❌ + + + .. list-table:: **test-macosx1470-64-nightlyasrelease/opt** + :widths: 30 15 15 15 15 + :header-rows: 1 + + * - **Test Name** + - mozilla-central + - autoland + - mozilla-release + - mozilla-beta + * - **browsertime-indexeddb-firefox-idb-open-few-seq** + - ❌ + - ❌ + - ❌ + - ❌ + + + .. list-table:: **test-macosx1470-64-shippable/opt** + :widths: 30 15 15 15 15 + :header-rows: 1 + + * - **Test Name** + - mozilla-central + - autoland + - mozilla-release + - mozilla-beta + * - **browsertime-indexeddb-chrome-idb-open-few-seq** + - ❌ + - ❌ + - ❌ + - ❌ + * - **browsertime-indexeddb-firefox-idb-open-few-seq** + - ✅ + - ✅ + - ❌ + - ❌ + + + .. list-table:: **test-macosx1470-64/opt** + :widths: 30 15 15 15 15 + :header-rows: 1 + + * - **Test Name** + - mozilla-central + - autoland + - mozilla-release + - mozilla-beta + * - **browsertime-indexeddb-firefox-idb-open-few-seq** + - ❌ + - ❌ + - ❌ + - ❌ + + + .. list-table:: **test-windows11-64-24h2-nightlyasrelease/opt** + :widths: 30 15 15 15 15 + :header-rows: 1 + + * - **Test Name** + - mozilla-central + - autoland + - mozilla-release + - mozilla-beta + * - **browsertime-indexeddb-firefox-idb-open-few-seq** + - ❌ + - ❌ + - ❌ + - ❌ + + + .. list-table:: **test-windows11-64-24h2-shippable/opt** + :widths: 30 15 15 15 15 + :header-rows: 1 + + * - **Test Name** + - mozilla-central + - autoland + - mozilla-release + - mozilla-beta + * - **browsertime-indexeddb-chrome-idb-open-few-seq** + - ❌ + - ❌ + - ❌ + - ❌ + * - **browsertime-indexeddb-firefox-idb-open-few-seq** + - ✅ + - ✅ + - ❌ + - ❌ + + + .. list-table:: **test-windows11-64-24h2/opt** + :widths: 30 15 15 15 15 + :header-rows: 1 + + * - **Test Name** + - mozilla-central + - autoland + - mozilla-release + - mozilla-beta + * - **browsertime-indexeddb-firefox-idb-open-few-seq** + - ❌ + - ❌ + - ❌ + - ❌ + + + +.. dropdown:: idb-open-many-par + :class-container: anchor-id-idb-open-many-par-c + + * Command to Run Locally + + .. code-block:: + + ./mach raptor -t idb-open-many-par + + **Owner**: DOM Lifecycle, Workers and Storage Team + + **Description**: Measures opening many IndexedDB databases in parallel + + * **alert threshold**: 2.0 + * **apps**: firefox, chrome, safari + * **browser cycles**: 1 + * **browsertime args**: --browsertime.iterations=10000 --browsertime.parallel=1 + * **custom data**: true + * **expected**: pass + * **gecko profile entries**: 131072000 + * **gecko profile features**: js,stackwalk,cpu,memory + * **gecko profile threads**: GeckoMain,DOM Worker,IndexedDB + * **link searchfox**: ``__ + * **lower is better**: true + * **measure**: cpuTime + * **output timeout**: 2000000 + * **page cycles**: 1 + * **page timeout**: 1800000 + * **playback**: mitmproxy + * **playback pageset manifest**: mitm8-linux-firefox-example.manifest + * **subtest unit**: ms + * **test script**: indexeddb_open.js + * **test summary**: flatten + * **test url**: ``__ + * **type**: pageload + * **unit**: ms + * **use live sites**: false + * **Test Task**: + + .. list-table:: **test-linux1804-64-nightlyasrelease-qr/opt** + :widths: 30 15 15 15 15 + :header-rows: 1 + + * - **Test Name** + - mozilla-central + - autoland + - mozilla-release + - mozilla-beta + * - **browsertime-indexeddb-firefox-idb-open-many-par** + - ❌ + - ❌ + - ❌ + - ❌ + + + .. list-table:: **test-linux1804-64-qr/opt** + :widths: 30 15 15 15 15 + :header-rows: 1 + + * - **Test Name** + - mozilla-central + - autoland + - mozilla-release + - mozilla-beta + * - **browsertime-indexeddb-firefox-idb-open-many-par** + - ❌ + - ❌ + - ❌ + - ❌ + + + .. list-table:: **test-linux1804-64-shippable-qr/opt** + :widths: 30 15 15 15 15 + :header-rows: 1 + + * - **Test Name** + - mozilla-central + - autoland + - mozilla-release + - mozilla-beta + * - **browsertime-indexeddb-chrome-idb-open-many-par** + - ❌ + - ❌ + - ❌ + - ❌ + * - **browsertime-indexeddb-firefox-idb-open-many-par** + - ✅ + - ✅ + - ❌ + - ❌ + + + .. list-table:: **test-macosx1470-64-nightlyasrelease/opt** + :widths: 30 15 15 15 15 + :header-rows: 1 + + * - **Test Name** + - mozilla-central + - autoland + - mozilla-release + - mozilla-beta + * - **browsertime-indexeddb-firefox-idb-open-many-par** + - ❌ + - ❌ + - ❌ + - ❌ + + + .. list-table:: **test-macosx1470-64-shippable/opt** + :widths: 30 15 15 15 15 + :header-rows: 1 + + * - **Test Name** + - mozilla-central + - autoland + - mozilla-release + - mozilla-beta + * - **browsertime-indexeddb-chrome-idb-open-many-par** + - ❌ + - ❌ + - ❌ + - ❌ + * - **browsertime-indexeddb-firefox-idb-open-many-par** + - ✅ + - ✅ + - ❌ + - ❌ + + + .. list-table:: **test-macosx1470-64/opt** + :widths: 30 15 15 15 15 + :header-rows: 1 + + * - **Test Name** + - mozilla-central + - autoland + - mozilla-release + - mozilla-beta + * - **browsertime-indexeddb-firefox-idb-open-many-par** + - ❌ + - ❌ + - ❌ + - ❌ + + + .. list-table:: **test-windows11-64-24h2-nightlyasrelease/opt** + :widths: 30 15 15 15 15 + :header-rows: 1 + + * - **Test Name** + - mozilla-central + - autoland + - mozilla-release + - mozilla-beta + * - **browsertime-indexeddb-firefox-idb-open-many-par** + - ❌ + - ❌ + - ❌ + - ❌ + + + .. list-table:: **test-windows11-64-24h2-shippable/opt** + :widths: 30 15 15 15 15 + :header-rows: 1 + + * - **Test Name** + - mozilla-central + - autoland + - mozilla-release + - mozilla-beta + * - **browsertime-indexeddb-chrome-idb-open-many-par** + - ❌ + - ❌ + - ❌ + - ❌ + * - **browsertime-indexeddb-firefox-idb-open-many-par** + - ✅ + - ✅ + - ❌ + - ❌ + + + .. list-table:: **test-windows11-64-24h2/opt** + :widths: 30 15 15 15 15 + :header-rows: 1 + + * - **Test Name** + - mozilla-central + - autoland + - mozilla-release + - mozilla-beta + * - **browsertime-indexeddb-firefox-idb-open-many-par** + - ❌ + - ❌ + - ❌ + - ❌ + + + +.. dropdown:: idb-open-many-seq + :class-container: anchor-id-idb-open-many-seq-c + + * Command to Run Locally + + .. code-block:: + + ./mach raptor -t idb-open-many-seq + + **Owner**: DOM Lifecycle, Workers and Storage Team + + **Description**: Measures opening many IndexedDB databases in sequence + + * **alert threshold**: 2.0 + * **apps**: firefox, chrome, safari + * **browser cycles**: 1 + * **browsertime args**: --browsertime.iterations=10000 --browsertime.parallel=0 + * **custom data**: true + * **expected**: pass + * **gecko profile entries**: 131072000 + * **gecko profile features**: js,stackwalk,cpu,memory + * **gecko profile threads**: GeckoMain,DOM Worker,IndexedDB + * **link searchfox**: ``__ + * **lower is better**: true + * **measure**: cpuTime + * **output timeout**: 2000000 + * **page cycles**: 1 + * **page timeout**: 1800000 + * **playback**: mitmproxy + * **playback pageset manifest**: mitm8-linux-firefox-example.manifest + * **subtest unit**: ms + * **test script**: indexeddb_open.js + * **test summary**: flatten + * **test url**: ``__ + * **type**: pageload + * **unit**: ms + * **use live sites**: false + * **Test Task**: + + .. list-table:: **test-linux1804-64-nightlyasrelease-qr/opt** + :widths: 30 15 15 15 15 + :header-rows: 1 + + * - **Test Name** + - mozilla-central + - autoland + - mozilla-release + - mozilla-beta + * - **browsertime-indexeddb-firefox-idb-open-many-seq** + - ❌ + - ❌ + - ❌ + - ❌ + + + .. list-table:: **test-linux1804-64-qr/opt** + :widths: 30 15 15 15 15 + :header-rows: 1 + + * - **Test Name** + - mozilla-central + - autoland + - mozilla-release + - mozilla-beta + * - **browsertime-indexeddb-firefox-idb-open-many-seq** + - ❌ + - ❌ + - ❌ + - ❌ + + + .. list-table:: **test-linux1804-64-shippable-qr/opt** + :widths: 30 15 15 15 15 + :header-rows: 1 + + * - **Test Name** + - mozilla-central + - autoland + - mozilla-release + - mozilla-beta + * - **browsertime-indexeddb-chrome-idb-open-many-seq** + - ❌ + - ❌ + - ❌ + - ❌ + * - **browsertime-indexeddb-firefox-idb-open-many-seq** + - ✅ + - ✅ + - ❌ + - ❌ + + + .. list-table:: **test-macosx1470-64-nightlyasrelease/opt** + :widths: 30 15 15 15 15 + :header-rows: 1 + + * - **Test Name** + - mozilla-central + - autoland + - mozilla-release + - mozilla-beta + * - **browsertime-indexeddb-firefox-idb-open-many-seq** + - ❌ + - ❌ + - ❌ + - ❌ + + + .. list-table:: **test-macosx1470-64-shippable/opt** + :widths: 30 15 15 15 15 + :header-rows: 1 + + * - **Test Name** + - mozilla-central + - autoland + - mozilla-release + - mozilla-beta + * - **browsertime-indexeddb-chrome-idb-open-many-seq** + - ❌ + - ❌ + - ❌ + - ❌ + * - **browsertime-indexeddb-firefox-idb-open-many-seq** + - ✅ + - ✅ + - ❌ + - ❌ + + + .. list-table:: **test-macosx1470-64/opt** + :widths: 30 15 15 15 15 + :header-rows: 1 + + * - **Test Name** + - mozilla-central + - autoland + - mozilla-release + - mozilla-beta + * - **browsertime-indexeddb-firefox-idb-open-many-seq** + - ❌ + - ❌ + - ❌ + - ❌ + + + .. list-table:: **test-windows11-64-24h2-nightlyasrelease/opt** + :widths: 30 15 15 15 15 + :header-rows: 1 + + * - **Test Name** + - mozilla-central + - autoland + - mozilla-release + - mozilla-beta + * - **browsertime-indexeddb-firefox-idb-open-many-seq** + - ❌ + - ❌ + - ❌ + - ❌ + + + .. list-table:: **test-windows11-64-24h2-shippable/opt** + :widths: 30 15 15 15 15 + :header-rows: 1 + + * - **Test Name** + - mozilla-central + - autoland + - mozilla-release + - mozilla-beta + * - **browsertime-indexeddb-chrome-idb-open-many-seq** + - ❌ + - ❌ + - ❌ + - ❌ + * - **browsertime-indexeddb-firefox-idb-open-many-seq** + - ✅ + - ✅ + - ❌ + - ❌ + + + .. list-table:: **test-windows11-64-24h2/opt** + :widths: 30 15 15 15 15 + :header-rows: 1 + + * - **Test Name** + - mozilla-central + - autoland + - mozilla-release + - mozilla-beta + * - **browsertime-indexeddb-firefox-idb-open-many-seq** + - ❌ + - ❌ + - ❌ + - ❌ + + + .. dropdown:: process-switch :class-container: anchor-id-process-switch-c diff --git a/testing/performance/android-resource/parse_resource_usage.py b/testing/performance/android-resource/parse_resource_usage.py index b86fc6e4af4..5811fdf4dd8 100644 --- a/testing/performance/android-resource/parse_resource_usage.py +++ b/testing/performance/android-resource/parse_resource_usage.py @@ -137,8 +137,8 @@ def parse_memory_usage(mem_file, binary): final_mems[curr_mem][name] = round(float(mem_usage.replace(",", "")), 2) measurements = { - "rss": {"tab": 0, "gpu": 0, "main": 0}, - "pss": {"tab": 0, "gpu": 0, "main": 0}, + "rss": {"tab": 0, "gpu": 0, "main": 0, "crashhelper": 0}, + "pss": {"tab": 0, "gpu": 0, "main": 0, "crashhelper": 0}, } for mem_type, mem_info in final_mems.items(): for name, mem_usage in mem_info.items(): @@ -180,7 +180,7 @@ def parse_cpu_usage(cpu_file, binary): final_times[name] = vals[-2] # Convert the final times to milliseconds - cpu_times = {"tab": 0, "gpu": 0, "main": 0} + cpu_times = {"tab": 0, "gpu": 0, "main": 0, "crashhelper": 0} for name, time in final_times.items(): # adb shell ps -o time+= gives us MIN:SEC.HUNDREDTHS. # That's why we divide dt.microseconds by 1000 for measuring in milliseconds. diff --git a/testing/raptor/browsertime/indexeddb_open.js b/testing/raptor/browsertime/indexeddb_open.js new file mode 100644 index 00000000000..30f4b0e700f --- /dev/null +++ b/testing/raptor/browsertime/indexeddb_open.js @@ -0,0 +1,119 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* eslint-env node */ + +const { logTest } = require("./utils/profiling"); + +module.exports = logTest( + "IndexedDB open test", + async function (context, commands) { + context.log.info("Starting an IndexedDB open test"); + + const post_startup_delay = context.options.browsertime.post_startup_delay; + const url = context.options.browsertime.url; + const iterations = context.options.browsertime.iterations; + const parallel = context.options.browsertime.parallel; + + await commands.navigate(url); + + context.log.info( + "Waiting for %d ms (post_startup_delay)", + post_startup_delay + ); + await commands.wait.byTime(post_startup_delay); + + await commands.measure.start(); + + const open_duration = await context.selenium.driver.executeAsyncScript(` + const notifyDone = arguments[arguments.length - 1]; + + const iterations = ${iterations}; + const parallel = ${parallel}; + + function startOpen() { + return new Promise((resolve, reject) => { + try { + const openRequest = indexedDB.open("rootsdb"); + openRequest.onsuccess = () => resolve(openRequest.result); + openRequest.onerror = () => reject(openRequest.error); + } catch (e) { + reject(e); + } + }); + } + + async function openInSequence() { + const result = []; + + for (let index = 0; index < iterations; index++) { + const database = await startOpen(); + result.push(database); + } + + return result; + } + + async function openInParallel() { + const openPromises = []; + + for (let index = 0; index < iterations; index++) { + openPromises.push(startOpen()); + } + + return Promise.all(openPromises); + } + + async function main() { + const databases = + parallel ? await openInParallel() : await openInSequence(); + + for (const database of databases) { + database.close(); + } + } + + const startTime = performance.now(); + main().then(() => { + notifyDone(performance.now() - startTime); + }); + `); + console.log("Open duration ", open_duration); + + const delete_duration = await context.selenium.driver.executeAsyncScript(` + const notifyDone = arguments[arguments.length - 1]; + + function startDelete() { + return new Promise((resolve, reject) => { + try { + const openRequest = indexedDB.deleteDatabase("rootsdb"); + openRequest.onsuccess = () => resolve(); + openRequest.onerror = () => reject(openRequest.error); + } catch (e) { + reject(e); + } + }); + } + + const startTime = performance.now(); + startDelete().then(() => { + notifyDone(performance.now() - startTime); + }); + `); + console.log("Delete duration ", delete_duration); + + const time_duration = open_duration + delete_duration; + console.log("Time duration ", time_duration); + + await commands.measure.stop(); + + await commands.measure.addObject({ + custom_data: { open_duration, delete_duration, time_duration }, + }); + + context.log.info("IndexedDB open test ended"); + + return true; + } +); diff --git a/testing/raptor/raptor/perfdocs/config.yml b/testing/raptor/raptor/perfdocs/config.yml index e289e6641a2..8771f6d555c 100644 --- a/testing/raptor/raptor/perfdocs/config.yml +++ b/testing/raptor/raptor/perfdocs/config.yml @@ -225,6 +225,10 @@ suites: browsertime: "Used to run vanilla browsertime tests through raptor. For example: `./mach raptor -t browsertime --browsertime-arg url=https://www.sitespeed.io --browsertime-arg iterations=3`" process-switch: "Measures process switch time" welcome: "Measures pageload metrics for the first-install about:welcome page" + idb-open-many-par: "Measures opening many IndexedDB databases in parallel" + idb-open-many-seq: "Measures opening many IndexedDB databases in sequence" + idb-open-few-par: "Measures opening a few IndexedDB databases in parallel" + idb-open-few-seq: "Measures opening a few IndexedDB databases in sequence" addMab1: "Use add API to send many small ArrayBuffers to IndexedDB in one transaction" addMabN: "Use add API to send many small ArrayBuffers to IndexedDB independently" addMar1: "Use add API to send many small Arrays to IndexedDB in one transaction" diff --git a/testing/raptor/raptor/tests/custom/browsertime-indexeddb.toml b/testing/raptor/raptor/tests/custom/browsertime-indexeddb.toml index 86f9ce21e60..a23d03f3b9e 100644 --- a/testing/raptor/raptor/tests/custom/browsertime-indexeddb.toml +++ b/testing/raptor/raptor/tests/custom/browsertime-indexeddb.toml @@ -21,6 +21,26 @@ playback_pageset_manifest = "mitm8-linux-firefox-example.manifest" test_url = "https://www.example.com" use_live_sites = false +# Meaning of the indexeddb_open names: +# 1. Iterations - how many open requests are made? (many => iterations=10000, few => iterations=1000) +# 2. Concurrency – should opens happen all at once (par => parallel=1) or one after another (seq => parallel=0)? + +["idb-open-many-par"] +browsertime_args = "--browsertime.iterations=10000 --browsertime.parallel=1" +test_script = "indexeddb_open.js" + +["idb-open-many-seq"] +browsertime_args = "--browsertime.iterations=10000 --browsertime.parallel=0" +test_script = "indexeddb_open.js" + +["idb-open-few-par"] +browsertime_args = "--browsertime.iterations=1000 --browsertime.parallel=1" +test_script = "indexeddb_open.js" + +["idb-open-few-seq"] +browsertime_args = "--browsertime.iterations=1000 --browsertime.parallel=0" +test_script = "indexeddb_open.js" + # Meaning of the idb_write names: # 1. Iterations - how many add/put requests are made? (k ~ few, M ~ many) # 2. Buffer type - what kind of buffer is supplied to each add/put, Array (ar), ArrayBuffer (ab) or Blob (bl)? diff --git a/testing/web-platform/meta/css/css-view-transitions/content-with-clip.html.ini b/testing/web-platform/meta/css/css-view-transitions/content-with-clip.html.ini new file mode 100644 index 00000000000..046323dcb0f --- /dev/null +++ b/testing/web-platform/meta/css/css-view-transitions/content-with-clip.html.ini @@ -0,0 +1,2 @@ +[content-with-clip.html] + expected: FAIL diff --git a/testing/web-platform/meta/css/css-view-transitions/massive-element-below-and-on-top-of-viewport-partially-onscreen-old.html.ini b/testing/web-platform/meta/css/css-view-transitions/massive-element-below-and-on-top-of-viewport-partially-onscreen-old.html.ini index 44c763fb236..1f70914513a 100644 --- a/testing/web-platform/meta/css/css-view-transitions/massive-element-below-and-on-top-of-viewport-partially-onscreen-old.html.ini +++ b/testing/web-platform/meta/css/css-view-transitions/massive-element-below-and-on-top-of-viewport-partially-onscreen-old.html.ini @@ -1,2 +1,4 @@ [massive-element-below-and-on-top-of-viewport-partially-onscreen-old.html] - expected: FAIL + expected: + if os == "mac": FAIL + if os == "android": FAIL diff --git a/testing/web-platform/meta/css/css-view-transitions/massive-element-right-and-left-of-viewport-partially-onscreen-old.html.ini b/testing/web-platform/meta/css/css-view-transitions/massive-element-right-and-left-of-viewport-partially-onscreen-old.html.ini index f8f9d4e9fa6..87ddbbdba0c 100644 --- a/testing/web-platform/meta/css/css-view-transitions/massive-element-right-and-left-of-viewport-partially-onscreen-old.html.ini +++ b/testing/web-platform/meta/css/css-view-transitions/massive-element-right-and-left-of-viewport-partially-onscreen-old.html.ini @@ -1,2 +1,4 @@ [massive-element-right-and-left-of-viewport-partially-onscreen-old.html] - expected: FAIL + expected: + if os == "mac": FAIL + if os == "android": FAIL diff --git a/testing/web-platform/meta/css/css-view-transitions/new-content-captures-different-size.html.ini b/testing/web-platform/meta/css/css-view-transitions/new-content-captures-different-size.html.ini index c9555baddae..025f762a0bc 100644 --- a/testing/web-platform/meta/css/css-view-transitions/new-content-captures-different-size.html.ini +++ b/testing/web-platform/meta/css/css-view-transitions/new-content-captures-different-size.html.ini @@ -1,2 +1,3 @@ [new-content-captures-different-size.html] - expected: FAIL + expected: + if os == "android": [PASS, FAIL] diff --git a/testing/web-platform/meta/css/css-view-transitions/old-content-captures-clip-path.html.ini b/testing/web-platform/meta/css/css-view-transitions/old-content-captures-clip-path.html.ini deleted file mode 100644 index 660a6e5a4e5..00000000000 --- a/testing/web-platform/meta/css/css-view-transitions/old-content-captures-clip-path.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[old-content-captures-clip-path.html] - expected: FAIL diff --git a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/api/operation/adapter/requestAdapter/cts.https.html.ini b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/api/operation/adapter/requestAdapter/cts.https.html.ini index 82bc4c76337..dc2d9dcf409 100644 --- a/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/api/operation/adapter/requestAdapter/cts.https.html.ini +++ b/testing/web-platform/mozilla/meta/webgpu/cts/webgpu/api/operation/adapter/requestAdapter/cts.https.html.ini @@ -36,23 +36,18 @@ [cts.https.html?q=webgpu:api,operation,adapter,requestAdapter:requestAdapter_invalid_featureLevel:*] implementation-status: backlog [:featureLevel=""] - expected: FAIL [:featureLevel="%20"] - expected: FAIL [:featureLevel="Core"] - expected: FAIL [:featureLevel="_undef_"] [:featureLevel="compatability"] - expected: FAIL [:featureLevel="compatibility"] [:featureLevel="cor"] - expected: FAIL [:featureLevel="core"] diff --git a/testing/web-platform/tests/css/css-display/display-contents-pseudo-click-target.html b/testing/web-platform/tests/css/css-display/display-contents-pseudo-click-target.html new file mode 100644 index 00000000000..2620c97312f --- /dev/null +++ b/testing/web-platform/tests/css/css-display/display-contents-pseudo-click-target.html @@ -0,0 +1,43 @@ + +Clicking a display: contents pseudo-element targets that element + + + + + + + + + + +
    + +
    + diff --git a/testing/web-platform/tests/selection/caret/move-around-generated-content.html b/testing/web-platform/tests/selection/caret/move-around-generated-content.html new file mode 100644 index 00000000000..ea22a399ae8 --- /dev/null +++ b/testing/web-platform/tests/selection/caret/move-around-generated-content.html @@ -0,0 +1,260 @@ + + + + +Test for caret movement around generated content + + + + + + + + + + +
    +

    abc

    +
      +
    • one
    • +

    • +
    • three
    • +
    +

    xyz

    +
    +
    +

    paragraph before the blockquote

    +
    quote
    +

    after the blockquote

    +
    + + diff --git a/testing/xpcshell/runxpcshelltests.py b/testing/xpcshell/runxpcshelltests.py index 436517c7845..2ac62c6fbd1 100755 --- a/testing/xpcshell/runxpcshelltests.py +++ b/testing/xpcshell/runxpcshelltests.py @@ -975,6 +975,7 @@ class XPCShellTestThread(Thread): return_code_ok and self.usingCrashReporter and not self.saw_crash_reporter_init + and len(process_output) > 0 ) passed = ( diff --git a/testing/xpcshell/selftest.py b/testing/xpcshell/selftest.py index 0bf9fba4aeb..4204201da12 100755 --- a/testing/xpcshell/selftest.py +++ b/testing/xpcshell/selftest.py @@ -12,11 +12,11 @@ import os import pprint import re -import shutil import sys import tempfile import unittest +import mozfile import mozinfo import six from mozlog import structured @@ -485,7 +485,7 @@ class XPCShellTestsTests(unittest.TestCase): self.x.harness_timeout = 30 if not mozinfo.info["ccov"] else 60 def tearDown(self): - shutil.rmtree(self.tempdir) + mozfile.remove(self.tempdir) self.x.shutdownNode() def writeFile(self, name, contents, mode="w"): diff --git a/third_party/rust/android-tzdata/.cargo-checksum.json b/third_party/rust/android-tzdata/.cargo-checksum.json new file mode 100644 index 00000000000..ce22ca81778 --- /dev/null +++ b/third_party/rust/android-tzdata/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"a87d9acc9827a50c7a96a88720c5dd055cbc08b1144dff95bd572ff977d4a79a","LICENSE-APACHE":"4458503dd48e88c4e0b945fb252a08b93c40ec757309b8ffa7c594dfa1e35104","LICENSE-MIT":"002c2696d92b5c8cf956c11072baa58eaf9f6ade995c031ea635c6a1ee342ad1","README.md":"6dfe0c602dc61eebe118900ed66a2c1f7887b9fe95b36e1c2974c4e8fa7ebd4b","src/lib.rs":"8f421233df83f82e737930ca8a2ad254966334183148bcc170f9c405df230de2","src/tzdata.rs":"78920925b04219910511e9a1f036f468cd2925c0054f280d6a00b106529046e7"},"package":"e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"} \ No newline at end of file diff --git a/third_party/rust/android-tzdata/Cargo.toml b/third_party/rust/android-tzdata/Cargo.toml new file mode 100644 index 00000000000..805128a82c6 --- /dev/null +++ b/third_party/rust/android-tzdata/Cargo.toml @@ -0,0 +1,34 @@ +# 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 = "2018" +name = "android-tzdata" +version = "0.1.1" +authors = ["RumovZ"] +include = [ + "src/**/*", + "LICENSE-*", + "README.md", +] +description = "Parser for the Android-specific tzdata file" +readme = "README.md" +keywords = [ + "parser", + "android", + "timezone", +] +categories = ["date-and-time"] +license = "MIT OR Apache-2.0" +repository = "https://github.com/RumovZ/android-tzdata" + +[dev-dependencies.zip] +version = "0.6.4" diff --git a/third_party/rust/android-tzdata/LICENSE-APACHE b/third_party/rust/android-tzdata/LICENSE-APACHE new file mode 100644 index 00000000000..c61b66391a3 --- /dev/null +++ b/third_party/rust/android-tzdata/LICENSE-APACHE @@ -0,0 +1,201 @@ + 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/third_party/rust/android-tzdata/LICENSE-MIT b/third_party/rust/android-tzdata/LICENSE-MIT new file mode 100644 index 00000000000..8aa26455d23 --- /dev/null +++ b/third_party/rust/android-tzdata/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) [year] [fullname] + +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/third_party/rust/android-tzdata/README.md b/third_party/rust/android-tzdata/README.md new file mode 100644 index 00000000000..d3f90313fb6 --- /dev/null +++ b/third_party/rust/android-tzdata/README.md @@ -0,0 +1,20 @@ +# android-tzdata + +Parser for the Android-specific tzdata file. + +## License + +Licensed under either of + +- Apache License, Version 2.0 + ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) +- MIT license + ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. diff --git a/third_party/rust/android-tzdata/src/lib.rs b/third_party/rust/android-tzdata/src/lib.rs new file mode 100644 index 00000000000..b6b0e36cf9e --- /dev/null +++ b/third_party/rust/android-tzdata/src/lib.rs @@ -0,0 +1,29 @@ +//! Parser for the Android-specific tzdata file. + +mod tzdata; + +/// Tries to locate the `tzdata` file, parse it, and return the entry for the +/// requested time zone. +/// +/// # Errors +/// +/// Returns an [std::io::Error] if the `tzdata` file cannot be found and parsed, or +/// if it does not contain the requested timezone entry. +/// +/// # Example +/// +/// ```rust +/// # use std::error::Error; +/// # use android_tzdata::find_tz_data; +/// # +/// # fn main() -> Result<(), Box> { +/// let tz_data = find_tz_data("Europe/Kiev")?; +/// // Check it's version 2 of the [Time Zone Information Format](https://www.ietf.org/archive/id/draft-murchison-rfc8536bis-02.html). +/// assert!(tz_data.starts_with(b"TZif2")); +/// # Ok(()) +/// # } +/// ``` +pub fn find_tz_data(tz_name: impl AsRef) -> Result, std::io::Error> { + let mut file = tzdata::find_file()?; + tzdata::find_tz_data_in_file(&mut file, tz_name.as_ref()) +} diff --git a/third_party/rust/android-tzdata/src/tzdata.rs b/third_party/rust/android-tzdata/src/tzdata.rs new file mode 100644 index 00000000000..92e4b621d1a --- /dev/null +++ b/third_party/rust/android-tzdata/src/tzdata.rs @@ -0,0 +1,166 @@ +//! Logic was mainly ported from https://android.googlesource.com/platform/libcore/+/jb-mr2-release/luni/src/main/java/libcore/util/ZoneInfoDB.java + +use core::{cmp::Ordering, convert::TryInto}; +use std::{ + fs::File, + io::{self, ErrorKind, Read, Seek, SeekFrom}, +}; + +// The database uses 32-bit (4 byte) integers. +const TZ_INT_SIZE: usize = 4; +// The first 12 bytes contain a special version string. +const MAGIC_SIZE: usize = 12; +const HEADER_SIZE: usize = MAGIC_SIZE + 3 * TZ_INT_SIZE; +// The database reserves 40 bytes for each id. +const TZ_NAME_SIZE: usize = 40; +const INDEX_ENTRY_SIZE: usize = TZ_NAME_SIZE + 3 * TZ_INT_SIZE; +const TZDATA_LOCATIONS: [TzdataLocation; 2] = [ + TzdataLocation { + env_var: "ANDROID_DATA", + path: "/misc/zoneinfo/", + }, + TzdataLocation { + env_var: "ANDROID_ROOT", + path: "/usr/share/zoneinfo/", + }, +]; + +#[derive(Debug)] +struct TzdataLocation { + env_var: &'static str, + path: &'static str, +} + +#[derive(Debug, Clone, Copy)] +struct Header { + index_offset: usize, + data_offset: usize, + _zonetab_offset: usize, +} + +#[derive(Debug)] +struct Index(Vec); + +#[derive(Debug, Clone, Copy)] +struct IndexEntry<'a> { + _name: &'a [u8], + offset: usize, + length: usize, + _raw_utc_offset: usize, +} + +pub(super) fn find_file() -> Result { + for location in &TZDATA_LOCATIONS { + if let Ok(env_value) = std::env::var(location.env_var) { + if let Ok(file) = File::open(format!("{}{}tzdata", env_value, location.path)) { + return Ok(file); + } + } + } + Err(io::Error::from(io::ErrorKind::NotFound)) +} + +pub(super) fn find_tz_data_in_file( + mut file: impl Read + Seek, + tz_name: &str, +) -> Result, io::Error> { + let header = Header::new(&mut file)?; + let index = Index::new(&mut file, header)?; + if let Some(entry) = index.find_entry(tz_name) { + file.seek(SeekFrom::Start((entry.offset + header.data_offset) as u64))?; + let mut tz_data = vec![0u8; entry.length]; + file.read_exact(&mut tz_data)?; + Ok(tz_data) + } else { + Err(io::Error::from(ErrorKind::NotFound)) + } +} + +impl Header { + fn new(mut file: impl Read + Seek) -> Result { + let mut buf = [0; HEADER_SIZE]; + file.read_exact(&mut buf)?; + if !buf.starts_with(b"tzdata") || buf[MAGIC_SIZE - 1] != 0u8 { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "invalid magic number", + )); + } + Ok(Self { + index_offset: parse_tz_int(&buf, MAGIC_SIZE) as usize, + data_offset: parse_tz_int(&buf, MAGIC_SIZE + TZ_INT_SIZE) as usize, + _zonetab_offset: parse_tz_int(&buf, MAGIC_SIZE + 2 * TZ_INT_SIZE) as usize, + }) + } +} + +impl Index { + fn new(mut file: impl Read + Seek, header: Header) -> Result { + file.seek(SeekFrom::Start(header.index_offset as u64))?; + let size = header.data_offset - header.index_offset; + let mut bytes = vec![0; size]; + file.read_exact(&mut bytes)?; + Ok(Self(bytes)) + } + + fn find_entry(&self, name: &str) -> Option { + let name_bytes = name.as_bytes(); + let name_len = name_bytes.len(); + if name_len > TZ_NAME_SIZE { + return None; + } + + let zeros = [0u8; TZ_NAME_SIZE]; + let cmp = |chunk: &&[u8]| -> Ordering { + // tz names always have TZ_NAME_SIZE bytes and are right-padded with 0s + // so we check that a chunk starts with `name` and the remaining bytes are 0 + chunk[..name_len] + .cmp(name_bytes) + .then_with(|| chunk[name_len..TZ_NAME_SIZE].cmp(&zeros[name_len..])) + }; + + let chunks: Vec<_> = self.0.chunks_exact(INDEX_ENTRY_SIZE).collect(); + chunks + .binary_search_by(cmp) + .map(|idx| IndexEntry::new(chunks[idx])) + .ok() + } +} + +impl<'a> IndexEntry<'a> { + fn new(bytes: &'a [u8]) -> Self { + Self { + _name: bytes[..TZ_NAME_SIZE] + .splitn(2, |&b| b == 0u8) + .next() + .unwrap(), + offset: parse_tz_int(bytes, TZ_NAME_SIZE) as usize, + length: parse_tz_int(bytes, TZ_NAME_SIZE + TZ_INT_SIZE) as usize, + _raw_utc_offset: parse_tz_int(bytes, TZ_NAME_SIZE + 2 * TZ_INT_SIZE) as usize, + } + } +} + +/// Panics if slice does not contain [TZ_INT_SIZE] bytes beginning at start. +fn parse_tz_int(slice: &[u8], start: usize) -> u32 { + u32::from_be_bytes(slice[start..start + TZ_INT_SIZE].try_into().unwrap()) +} + +#[cfg(test)] +mod test { + use super::*; + use std::fs::File; + use std::io::Cursor; + + #[test] + fn parse() { + let mut archive = File::open("tests/resources/tzdata.zip").unwrap(); + let mut zip = zip::ZipArchive::new(&mut archive).unwrap(); + let mut file = zip.by_index(0).unwrap(); + let mut data = Vec::new(); + file.read_to_end(&mut data).unwrap(); + let cursor = Cursor::new(data); + let tz = find_tz_data_in_file(cursor, "Europe/Kiev").unwrap(); + assert!(tz.starts_with(b"TZif2")); + } +} diff --git a/third_party/rust/chrono/.cargo-checksum.json b/third_party/rust/chrono/.cargo-checksum.json index 278002f2aab..a0699fff071 100644 --- a/third_party/rust/chrono/.cargo-checksum.json +++ b/third_party/rust/chrono/.cargo-checksum.json @@ -1 +1 @@ -{"files":{"AUTHORS.txt":"bcca43c486176e81edcb64d065d418986cb5ca71af7ee4a648236a644305960d","CHANGELOG.md":"a925f71022f1c9e34e5df7c016ab2a32e9be22fc503929aa87d567900ebe064b","Cargo.toml":"13fab9c53580e2754c4fa075768ee9591e8d4b4a437ac9dbae503c4d4b6daf09","LICENSE.txt":"46610329ff0b38effb9cb05979ff1ef761e465fed96b2eaca39e439d00129fd7","README.md":"39985917c9bc71ee87a24808369ddfab22f3c6343da7c87c2274c965e1eec9db","benches/chrono.rs":"4de07b4c7bc907926e5a6ed20fc00a30e4e0491604f3d78eece81f1a8b45870a","benches/serde.rs":"e1a9624bcad4892c4cc7b76d5ad14607a016305a27b2231c2c77814b1d4448a8","rustfmt.toml":"f74204a6f92aa7422a16ecb2ffe2d5bae0f123b778d08b5db1a398a3c9ca4306","src/date.rs":"ec234e777efa9d8cd2f0c7f87db9296eda04bd4d56c994232a3329eb863acd34","src/datetime.rs":"63e582cd17f3070cbcb586bea15b5102ead1a898252e14b1817a498750043102","src/div.rs":"6c0a08648bb688b418b42f1c7be8387057a9db9774d3f09e97df2a183b79cd9f","src/format/locales.rs":"8c14cb93c68b747947b74ab2933aae53c8a0edd009ff16815e756015fbea6e9f","src/format/mod.rs":"09c66dee24e69325ce9a7be5b6c830317515ee37f7c2a26f48835484ed155c89","src/format/parse.rs":"c98217756370c6186bdab117373912208c018a2c6411f9be0fd1aecab54fb10c","src/format/parsed.rs":"6d4453a5fedc753fae268e0f545fcc817d48f9ce6803231d1c83aeb1f626c138","src/format/scan.rs":"1846554a45c776d164239ec6874881c6c97468631a1c00c8c956630420ea408f","src/format/strftime.rs":"8bbe43ca06c8a5e71187431890a54ff1faeb3be990c0d9d8c2fd8ee8b5c1361f","src/lib.rs":"522163d278acefa80fd1af4afeec2fdd2648e13e3fbdde8e6d31214253b013f9","src/naive/date.rs":"8be6146b3c15c395a71d30449a799b6e4387d1cd515e7e849509b824161fa6e3","src/naive/datetime.rs":"fd0de90d13793e5a8ecf99f63fa27592a6add57ba39a116495f9b2b253323fce","src/naive/internals.rs":"8c1aa5ab3373e04b51d36ed1591d667e1450055208b8c44d3fb720a496722c57","src/naive/isoweek.rs":"a8c5ae43ee1b916a2f79d42230aea448bf85691d615c4d271fcf1809e763df01","src/naive/time.rs":"b83e4ae0a809badce9131a19e3c5c75dbb823db4ef2f79d50bd522126ba0b48f","src/offset/fixed.rs":"19b97271300b821407756e14f64a42d48eb25a71c7011b2967885e4946e089ef","src/offset/local.rs":"9bc3af0ebf35a49858647302ac7e44abe344cbb76819d273311e50d234d1e6b2","src/offset/mod.rs":"a1036f75fc686603b216f9bb45b1689c8b34198b617b204800ceafb87b66ec45","src/offset/utc.rs":"2940ade0e834a9e1247600e92d38ee7bb11653f2d267862f99f292f617e25ca4","src/oldtime.rs":"780bc4ae5652affa8f7020580bb5977e9f32b304b0905c742124fd87a82ae50e","src/round.rs":"f7ae453ec0caacffc23bc0bad38b2c59b616c097ccaa0a15c0e7bcb8d1e1aed3","src/sys.rs":"4a3e8a96a2060e7df82c57405a5de4fffa54132a77fdd919970d51f3c43442cb","src/sys/stub.rs":"78babcdbe867ce5978bd69935e141ee15313ee7d90edce52355a19ab906a019b","src/sys/unix.rs":"c838ba088423c2b26643bd7191fe1914f099c14d632c9874f236c9c2c9dbd2d6","src/sys/windows.rs":"5c19383e9ffe11c16102008b5984dbd3455fd4003df15ac1565720c0a5edfac8","tests/wasm.rs":"d152681d5a79d9bbb69684433388bb8d7ca90e181387d4690cf5bda8cf25d17f"},"package":"670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"} \ No newline at end of file +{"files":{"CITATION.cff":"6eb81794e04ee1d4bb56dd2767a2d5c6e2200f35f5d976437d7e299059494c66","Cargo.lock":"2b2fabd7ad614c4e3f0cbeaa2de479bd489ce477b7a28640d5b6bb5ef5038557","Cargo.toml":"41cec533b67eba086cd152582c428f2cd5baa5f29b0a5783d4c2f7ea4978190e","LICENSE.txt":"a3d4e565d73d108b07074757733e1dcd3ee20617350a135e1122935881311d50","src/date.rs":"10f03590ccab03c3efc076ef55bf218f1e93fdd5f85e4440f46b7590373619b1","src/datetime/mod.rs":"be0e61a845a465fda353fb579948087d9aadad6543313071fd8740f7a2c1f384","src/datetime/serde.rs":"3b9fbeb0b7f269d3162aeb7373e9829b58690603860ec5e23cdebb63a06efca2","src/datetime/tests.rs":"602dfd5b78c3aa7e6b30b0567f070e08d485649ce02bb40a511d1710f7301e63","src/format/formatting.rs":"0a3286b78a0c0b08adedbbd06c1894ce3ace1cf919db80cfe3ba603f7604aaeb","src/format/locales.rs":"d49de3da43941c94b1108f8fe84915cc5e1c13a1f1c84fd7356f9b7c61f19242","src/format/mod.rs":"df752a29ae2293fa71d75374b070925b1874198cc6ddbf5bcd717b26457bf44a","src/format/parse.rs":"b9d57bf8174b453864ed373d1ebb328202c0f67bd727a4270f430c088c311017","src/format/parsed.rs":"8d653af39d192a4c5234e36f72880757c9ff55ff55e979ec23c665b2fd6f34a5","src/format/scan.rs":"687117a94f7c825a8dd9f6462f9a6dbc400adaf13774d0fb8cdf025598d21cd2","src/format/strftime.rs":"066b8bea999aa69e760c05c17b683a0a411612b28a5bbf645eb8f9307fc708e4","src/lib.rs":"ea193c5d2214424137ffeb42fde6024f6003ed952525bcea331f1700bd6fad64","src/month.rs":"0f174e6b0047a1138f1713e596889097228eed89e267aa744bfecdd0a1efc5e4","src/naive/date/mod.rs":"4076847fa7926dfd70843de2f0322790354cb1cd4c9fd2e62455bacc4f00b318","src/naive/date/tests.rs":"d90f09052d4b4bbab760450d37b19ca698153a10e27249d05f3c11dc297a3e62","src/naive/datetime/mod.rs":"1465f96a1ed702e1080bc0129e84d435960987039f6425b9f7d09c0dcb8c2241","src/naive/datetime/serde.rs":"eeb8e5a6ea9632799673a6f9e4b72de0ae606ada29b20cbc1b1dbb0f9312b41c","src/naive/datetime/tests.rs":"f890c8ba06a0426d823ec4ee538d84fe38ccba6e3bd1503442cca9853186d3e5","src/naive/internals.rs":"6e2da15f601a1d15742e2ac8eaf0e23aaf61a3c92480c749c86c460a56907701","src/naive/isoweek.rs":"5c253901e2ee50316839e5f2490c59f5ca3c714d7523e01406cbdfc4e5bc6e7e","src/naive/mod.rs":"3d13c4e36a4d9f7a759fd2edf11c6e9e0b54c0c4380a671281964fe3e0315101","src/naive/time/mod.rs":"305c105fb5837b12f773aa6b90e9973bfe90895a97d0d2a20a67c704cbbf64c8","src/naive/time/serde.rs":"6c260c6d0c45dec113f3dcff6056450c524511c37f7c30da9324ad80aff2288b","src/naive/time/tests.rs":"70143375785969ed348fcc1ceab50b670d239209191b938823dd7b25a97ced40","src/offset/fixed.rs":"cfd1c9d6ffedb9dd219e26b966e5bdd4fa52d24f3fdb598142822cb6f8b51388","src/offset/local/mod.rs":"d27850947cfb649ada0bce052136e01e27035f06f8753adcb4f7bfc0c3332e40","src/offset/local/tz_info/mod.rs":"c8096637c04687ea396e444c25fdf0008e2b9975ab3c173a41dd63ac213c7877","src/offset/local/tz_info/parser.rs":"ec21d8739a86fb4e77551733e13af9964fbc01f80c87d7a164f6185ca9928c22","src/offset/local/tz_info/rule.rs":"bfc9e6257aeaaa23edef69b00acee58233846702eb969b8011d1b2425d15d10c","src/offset/local/tz_info/timezone.rs":"91863819f1b17d73682f62a46d7420a3390088075f0c93577e42e048fafe5a6f","src/offset/local/unix.rs":"3d701d5d37be23b90ac97d16832553ff6c458e98d1e293616528d85885db8c57","src/offset/local/win_bindings.rs":"a7e705048f99c594fcb5eca2ed619b9ceec6ac4f00d4bf21dc132d0a8e079281","src/offset/local/win_bindings.txt":"d680de1e8fd07d435d20143877dcb3d719d72e24ff7fe5baae97e956c24f7fed","src/offset/local/windows.rs":"e9909f06e84c6334433fe24e1db14ca61c116889df9db6903917a3083fdd4606","src/offset/mod.rs":"ab1021bd280e921c46fa28ae64e1ba5a30522a5f1dcdc5ebfe384c1c8a721f75","src/offset/utc.rs":"4b5449427cebf609cb5bbcc07ae70faeb3eab4f742d5a6a521e9118227d6eb2d","src/round.rs":"81f4cb707e723256429daf7a15d8d4f510f6f6baa892dc220fa2862da7f24e08","src/time_delta.rs":"b62f63d0ed22a55b2dab53fce16cc36db4c8b73ad0f4fb2fdc536b970c89ea27","src/traits.rs":"14ecbb8c4faaef20aea69a24f4df61d472d87771fef57ae990b10bf950a46315","src/weekday.rs":"6f8374bf39300fbf08ce6b3f64247afb29b2da9d60b5ff78f72b201b05272305","tests/dateutils.rs":"905e0f9f8d07f7cbee1b1a36369a1402e4cf62370b52dda1557c563b7a0de758","tests/wasm.rs":"252f16aeeaacbf26ca268b9a5e53aee3560cd1f071142a7a16d1509a02758a17","tests/win_bindings.rs":"62206936e3bd2ae1cc889c45d65d6182f553dc6c2b29c83cb0d897b2406a9040"},"package":"1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"} \ No newline at end of file diff --git a/third_party/rust/chrono/AUTHORS.txt b/third_party/rust/chrono/AUTHORS.txt deleted file mode 100644 index 9501a9d059e..00000000000 --- a/third_party/rust/chrono/AUTHORS.txt +++ /dev/null @@ -1,41 +0,0 @@ -Chrono is mainly written by Kang Seonghoon , -and also the following people (in ascending order): - -Alex Mikhalev -Alexander Bulaev -Ashley Mannix -Ben Boeckel -Ben Eills -Brandon W Maister -Brandon W Maister -Cecile Tonglet -Colin Ray -Corey Farwell -Dan -Danilo Bargen -David Hewson -David Ross -David Tolnay -David Willie -Eric Findlay -Eunchong Yu -Frans Skarman -Huon Wilson -Igor Gnatenko -Jim Turner -Jisoo Park -Joe Wilm -John Heitmann -John Nagle -Jonas mg -János Illés -Ken Tossell -Martin Risell Lilja -Richard Petrie -Ryan Lewis -Sergey V. Shadoy -Sergey V. Galtsev -Steve Klabnik -Tom Gallacher -klutzy -kud1ing diff --git a/third_party/rust/chrono/CHANGELOG.md b/third_party/rust/chrono/CHANGELOG.md deleted file mode 100644 index be289ae880d..00000000000 --- a/third_party/rust/chrono/CHANGELOG.md +++ /dev/null @@ -1,740 +0,0 @@ -ChangeLog for Chrono -==================== - -This documents all notable changes to [Chrono](https://github.com/chronotope/chrono). - -Chrono obeys the principle of [Semantic Versioning](http://semver.org/), with one caveat: we may -move previously-existing code behind a feature gate and put it behind a new feature. This new -feature will always be placed in the `previously-default` feature, which you can use to prevent -breakage if you use `no-default-features`. - -There were/are numerous minor versions before 1.0 due to the language changes. -Versions with only mechanical changes will be omitted from the following list. - -## 0.4.20 (unreleased) - -## 0.4.19 - -* Correct build on solaris/illumos - -## 0.4.18 - -* Restore support for x86_64-fortanix-unknown-sgx - -## 0.4.17 - -* Fix a name resolution error in wasm-bindgen code introduced by removing the dependency on time - v0.1 - -## 0.4.16 - -### Features - -* Add %Z specifier to the `FromStr`, similar to the glibc strptime - (does not set the offset from the timezone name) - -* Drop the dependency on time v0.1, which is deprecated, unless the `oldtime` - feature is active. This feature is active by default in v0.4.16 for backwards - compatibility, but will likely be removed in v0.5. Code that imports - `time::Duration` should be switched to import `chrono::Duration` instead to - avoid breakage. - -## 0.4.15 - -### Fixes - -* Correct usage of vec in specific feature combinations (@quodlibetor) - -## 0.4.14 **YANKED** - -### Features - -* Add day and week iterators for `NaiveDate` (@gnzlbg & @robyoung) -* Add a `Month` enum (@hhamana) -* Add `locales`. All format functions can now use locales, see the documentation for the - `unstable-locales` feature. -* Fix `Local.from_local_datetime` method for wasm - -### Improvements - -* Added MIN and MAX values for `NaiveTime`, `NaiveDateTime` and `DateTime`. - -## 0.4.13 - -### Features - -* Add `DurationRound` trait that allows rounding and truncating by `Duration` (@robyoung) - -### Internal Improvements - -* Code improvements to impl `From` for `js_sys` in wasm to reuse code (@schrieveslaach) - -## 0.4.12 - -### New Methods and impls - -* `Duration::abs` to ensure that a duration is just a magnitude (#418 @abreis). - -### Compatibility improvements - -* impl `From` for `js_sys` in wasm (#424 @schrieveslaach) -* Bump required version of `time` for redox support. - -### Bugfixes - -* serde modules do a better job with `Option` types (#417 @mwkroening and #429 - @fx-kirin) -* Use js runtime when using wasmbind to get the local offset (#412 - @quodlibetor) - -### Internal Improvements - -* Migrate to github actions from travis-ci, make the overall CI experience more comprehensible, - significantly faster and more correct (#439 @quodlibetor) - -## 0.4.11 - -### Improvements - -* Support a space or `T` in `FromStr` for `DateTime`, meaning that e.g. - `dt.to_string().parse::>()` now correctly works on round-trip. - (@quodlibetor in #378) -* Support "negative UTC" in `parse_from_rfc2822` (@quodlibetor #368 reported in - #102) -* Support comparisons of DateTimes with different timezones (@dlalic in #375) -* Many documentation improvements - -### Bitrot and external integration fixes - -* Don't use wasmbind on wasi (@coolreader18 #365) -* Avoid deprecation warnings for `Error::description` (@AnderEnder and - @quodlibetor #376) - -### Internal improvements - -* Use Criterion for benchmarks (@quodlibetor) - -## 0.4.10 - -### Compatibility notes - -* Putting some functionality behind an `alloc` feature to improve no-std - support (in #341) means that if you were relying on chrono with - `no-default-features` *and* using any of the functions that require alloc - support (i.e. any of the string-generating functions like `to_rfc3339`) you - will need to add the `alloc` feature in your Cargo.toml. - -### Improvements - -* `DateTime::parse_from_str` is more than 2x faster in some cases. (@michalsrb - #358) -* Significant improvements to no-std and alloc support (This should also make - many format/serialization operations induce zero unnecessary allocations) - (@CryZe #341) - -### Features - -* Functions that were accepting `Iterator` of `Item`s (for example - `format_with_items`) now accept `Iterator` of `Borrow`, so one can - use values or references. (@michalsrb #358) -* Add built-in support for structs with nested `Option` etc fields - (@manifest #302) - -### Internal/doc improvements - -* Use markdown footnotes on the `strftime` docs page (@qudlibetor #359) -* Migrate from `try!` -> `?` (question mark) because it is now emitting - deprecation warnings and has been stable since rustc 1.13.0 -* Deny dead code - -## 0.4.9 - -### Fixes - -* Make Datetime arithmatic adjust their offsets after discovering their new - timestamps (@quodlibetor #337) -* Put wasm-bindgen related code and dependencies behind a `wasmbind` feature - gate. (@quodlibetor #335) - -## 0.4.8 - -### Fixes - -* Add '0' to single-digit days in rfc2822 date format (@wyhaya #323) -* Correctly pad DelayedFormat (@SamokhinIlya #320) - -### Features - -* Support `wasm-unknown-unknown` via wasm-bindgen (in addition to - emscripten/`wasm-unknown-emscripten`). (finished by @evq in #331, initial - work by @jjpe #287) - -## 0.4.7 - -### Fixes - -* Disable libc default features so that CI continues to work on rust 1.13 -* Fix panic on negative inputs to timestamp_millis (@cmars #292) -* Make `LocalResult` `Copy/Eq/Hash` - -### Features - -* Add `std::convert::From` conversions between the different timezone formats - (@mqudsi #271) -* Add `timestamp_nanos` methods (@jean-airoldie #308) -* Documentation improvements - -## 0.4.6 - -### Maintenance - -* Doc improvements -- improve README CI verification, external links -* winapi upgrade to 0.3 - -## Unreleased - -### Features - -* Added `NaiveDate::from_weekday_of_month{,_opt}` for getting eg. the 2nd Friday of March 2017. - -## 0.4.5 - -### Features - -* Added several more serde deserialization helpers (@novacrazy #258) -* Enabled all features on the playground (@davidtwco #267) -* Derive `Hash` on `FixedOffset` (@LuoZijun #254) -* Improved docs (@storyfeet #261, @quodlibetor #252) - -## 0.4.4 - -### Features - -* Added support for parsing nanoseconds without the leading dot (@emschwartz #251) - -## 0.4.3 - -### Features - -* Added methods to DateTime/NaiveDateTime to present the stored value as a number - of nanoseconds since the UNIX epoch (@harkonenbade #247) -* Added a serde serialise/deserialise module for nanosecond timestamps. (@harkonenbade #247) -* Added "Permissive" timezone parsing which allows a numeric timezone to - be specified without minutes. (@quodlibetor #242) - -## 0.4.2 - -### Deprecations - -* More strongly deprecate RustcSerialize: remove it from documentation unless - the feature is enabled, issue a deprecation warning if the rustc-serialize - feature is enabled (@quodlibetor #174) - -### Features - -* Move all uses of the system clock behind a `clock` feature, for use in - environments where we don't have access to the current time. (@jethrogb #236) -* Implement subtraction of two `Date`s, `Time`s, or `DateTime`s, returning a - `Duration` (@tobz1000 #237) - -## 0.4.1 - -### Bug Fixes - -* Allow parsing timestamps with subsecond precision (@jonasbb) -* RFC2822 allows times to not include the second (@upsuper) - -### Features - -* New `timestamp_millis` method on `DateTime` and `NaiveDateTim` that returns - number of milliseconds since the epoch. (@quodlibetor) -* Support exact decimal width on subsecond display for RFC3339 via a new - `to_rfc3339_opts` method on `DateTime` (@dekellum) -* Use no_std-compatible num dependencies (@cuviper) -* Add `SubsecRound` trait that allows rounding to the nearest second - (@dekellum) - -### Code Hygiene and Docs - -* Docs! (@alatiera @kosta @quodlibetor @kennytm) -* Run clippy and various fixes (@quodlibetor) - -## 0.4.0 (2017-06-22) - -This was originally planned as a minor release but was pushed to a major -release due to the compatibility concern raised. - -### Added - -- `IsoWeek` has been added for the ISO week without time zone. - -- The `+=` and `-=` operators against `time::Duration` are now supported for - `NaiveDate`, `NaiveTime` and `NaiveDateTime`. (#99) - - (Note that this does not invalidate the eventual deprecation of `time::Duration`.) - -- `SystemTime` and `DateTime` types can be now converted to each other via `From`. - Due to the obvious lack of time zone information in `SystemTime`, - the forward direction is limited to `DateTime` and `DateTime` only. - -### Changed - -- Intermediate implementation modules have been flattened (#161), - and `UTC` has been renamed to `Utc` in accordance with the current convention (#148). - - The full list of changes is as follows: - - Before | After - ---------------------------------------- | ---------------------------- - `chrono::date::Date` | `chrono::Date` - `chrono::date::MIN` | `chrono::MIN_DATE` - `chrono::date::MAX` | `chrono::MAX_DATE` - `chrono::datetime::DateTime` | `chrono::DateTime` - `chrono::naive::time::NaiveTime` | `chrono::naive::NaiveTime` - `chrono::naive::date::NaiveDate` | `chrono::naive::NaiveDate` - `chrono::naive::date::MIN` | `chrono::naive::MIN_DATE` - `chrono::naive::date::MAX` | `chrono::naive::MAX_DATE` - `chrono::naive::datetime::NaiveDateTime` | `chrono::naive::NaiveDateTime` - `chrono::offset::utc::UTC` | `chrono::offset::Utc` - `chrono::offset::fixed::FixedOffset` | `chrono::offset::FixedOffset` - `chrono::offset::local::Local` | `chrono::offset::Local` - `chrono::format::parsed::Parsed` | `chrono::format::Parsed` - - With an exception of `Utc`, this change does not affect any direct usage of - `chrono::*` or `chrono::prelude::*` types. - -- `Datelike::isoweekdate` is replaced by `Datelike::iso_week` which only returns the ISO week. - - The original method used to return a tuple of year number, week number and day of the week, - but this duplicated the `Datelike::weekday` method and it had been hard to deal with - the raw year and week number for the ISO week date. - This change isolates any logic and API for the week date into a separate type. - -- `NaiveDateTime` and `DateTime` can now be deserialized from an integral UNIX timestamp. (#125) - - This turns out to be very common input for web-related usages. - The existing string representation is still supported as well. - -- `chrono::serde` and `chrono::naive::serde` modules have been added - for the serialization utilities. (#125) - - Currently they contain the `ts_seconds` modules that can be used to - serialize `NaiveDateTime` and `DateTime` values into an integral UNIX timestamp. - This can be combined with Serde's `[de]serialize_with` attributes - to fully support the (de)serialization to/from the timestamp. - - For rustc-serialize, there are separate `chrono::TsSeconds` and `chrono::naive::TsSeconds` types - that are newtype wrappers implementing different (de)serialization logics. - This is a suboptimal API, however, and it is strongly recommended to migrate to Serde. - -### Fixed - -- The major version was made to fix the broken Serde dependency issues. (#146, #156, #158, #159) - - The original intention to technically break the dependency was - to facilitate the use of Serde 1.0 at the expense of temporary breakage. - Whether this was appropriate or not is quite debatable, - but it became clear that there are several high-profile crates requiring Serde 0.9 - and it is not feasible to force them to use Serde 1.0 anyway. - - To the end, the new major release was made with some known lower-priority breaking changes. - 0.3.1 is now yanked and any remaining 0.3 users can safely roll back to 0.3.0. - -- Various documentation fixes and goodies. (#92, #131, #136) - -## 0.3.1 (2017-05-02) - -### Added - -- `Weekday` now implements `FromStr`, `Serialize` and `Deserialize`. (#113) - - The syntax is identical to `%A`, i.e. either the shortest or the longest form of English names. - -### Changed - -- Serde 1.0 is now supported. (#142) - - This is technically a breaking change because Serde 0.9 and 1.0 are not compatible, - but this time we decided not to issue a minor version because - we have already seen Serde 0.8 and 0.9 compatibility problems even after 0.3.0 and - a new minor version turned out to be not very helpful for this kind of issues. - -### Fixed - -- Fixed a bug that the leap second can be mapped wrongly in the local time zone. - Only occurs when the local time zone is behind UTC. (#130) - -## 0.3.0 (2017-02-07) - -The project has moved to the [Chronotope](https://github.com/chronotope/) organization. - -### Added - -- `chrono::prelude` module has been added. All other glob imports are now discouraged. - -- `FixedOffset` can be added to or subtracted from any timelike types. - - - `FixedOffset::local_minus_utc` and `FixedOffset::utc_minus_local` methods have been added. - Note that the old `Offset::local_minus_utc` method is gone; see below. - -- Serde support for non-self-describing formats like Bincode is added. (#89) - -- Added `Item::Owned{Literal,Space}` variants for owned formatting items. (#76) - -- Formatting items and the `Parsed` type have been slightly adjusted so that - they can be internally extended without breaking any compatibility. - -- `Weekday` is now `Hash`able. (#109) - -- `ParseError` now implements `Eq` as well as `PartialEq`. (#114) - -- More documentation improvements. (#101, #108, #112) - -### Changed - -- Chrono now only supports Rust 1.13.0 or later (previously: Rust 1.8.0 or later). - -- Serde 0.9 is now supported. - Due to the API difference, support for 0.8 or older is discontinued. (#122) - -- Rustc-serialize implementations are now on par with corresponding Serde implementations. - They both standardize on the `std::fmt::Debug` textual output. - - **This is a silent breaking change (hopefully the last though).** - You should be prepared for the format change if you depended on rustc-serialize. - -- `Offset::local_minus_utc` is now `Offset::fix`, and returns `FixedOffset` instead of a duration. - - This makes every time zone operation operate within a bias less than one day, - and vastly simplifies many logics. - -- `chrono::format::format` now receives `FixedOffset` instead of `time::Duration`. - -- The following methods and implementations have been renamed and older names have been *removed*. - The older names will be reused for the same methods with `std::time::Duration` in the future. - - - `checked_*` → `checked_*_signed` in `Date`, `DateTime`, `NaiveDate` and `NaiveDateTime` types - - - `overflowing_*` → `overflowing_*_signed` in the `NaiveTime` type - - - All subtraction implementations between two time instants have been moved to - `signed_duration_since`, following the naming in `std::time`. - -### Fixed - -- Fixed a panic when the `Local` offset receives a leap second. (#123) - -### Removed - -- Rustc-serialize support for `Date` types and all offset types has been dropped. - - These implementations were automatically derived and never had been in a good shape. - Moreover there are no corresponding Serde implementations, limiting their usefulness. - In the future they may be revived with more complete implementations. - -- The following method aliases deprecated in the 0.2 branch have been removed. - - - `DateTime::num_seconds_from_unix_epoch` (→ `DateTime::timestamp`) - - `NaiveDateTime::from_num_seconds_from_unix_epoch` (→ `NaiveDateTime::from_timestamp`) - - `NaiveDateTime::from_num_seconds_from_unix_epoch_opt` (→ `NaiveDateTime::from_timestamp_opt`) - - `NaiveDateTime::num_seconds_unix_epoch` (→ `NaiveDateTime::timestamp`) - -- Formatting items are no longer `Copy`, except for `chrono::format::Pad`. - -- `chrono::offset::add_with_leapsecond` has been removed. - Use a direct addition with `FixedOffset` instead. - -## 0.2.25 (2016-08-04) - -This is the last version officially supports Rust 1.12.0 or older. - -(0.2.24 was accidentally uploaded without a proper check for warnings in the default state, -and replaced by 0.2.25 very shortly. Duh.) - -### Added - -- Serde 0.8 is now supported. 0.7 also remains supported. (#86) - -### Fixed - -- The deserialization implementation for rustc-serialize now properly verifies the input. - All serialization codes are also now thoroughly tested. (#42) - -## 0.2.23 (2016-08-03) - -### Added - -- The documentation was greatly improved for several types, - and tons of cross-references have been added. (#77, #78, #80, #82) - -- `DateTime::timestamp_subsec_{millis,micros,nanos}` methods have been added. (#81) - -### Fixed - -- When the system time records a leap second, - the nanosecond component was mistakenly reset to zero. (#84) - -- `Local` offset misbehaves in Windows for August and later, - due to the long-standing libtime bug (dates back to mid-2015). - Workaround has been implemented. (#85) - -## 0.2.22 (2016-04-22) - -### Fixed - -- `%.6f` and `%.9f` used to print only three digits when the nanosecond part is zero. (#71) -- The documentation for `%+` has been updated to reflect the current status. (#71) - -## 0.2.21 (2016-03-29) - -### Fixed - -- `Fixed::LongWeekdayName` was unable to recognize `"sunday"` (whoops). (#66) - -## 0.2.20 (2016-03-06) - -### Changed - -- `serde` dependency has been updated to 0.7. (#63, #64) - -## 0.2.19 (2016-02-05) - -### Added - -- The documentation for `Date` is made clear about its ambiguity and guarantees. - -### Fixed - -- `DateTime::date` had been wrong when the local date and the UTC date is in disagreement. (#61) - -## 0.2.18 (2016-01-23) - -### Fixed - -- Chrono no longer pulls a superfluous `rand` dependency. (#57) - -## 0.2.17 (2015-11-22) - -### Added - -- Naive date and time types and `DateTime` now have a `serde` support. - They serialize as an ISO 8601 / RFC 3339 string just like `Debug`. (#51) - -## 0.2.16 (2015-09-06) - -### Added - -- Added `%.3f`, `%.6f` and `%.9f` specifier for formatting fractional seconds - up to 3, 6 or 9 decimal digits. This is a natural extension to the existing `%f`. - Note that this is (not yet) generic, no other value of precision is supported. (#45) - -### Changed - -- Forbade unsized types from implementing `Datelike` and `Timelike`. - This does not make a big harm as any type implementing them should be already sized - to be practical, but this change still can break highly generic codes. (#46) - -### Fixed - -- Fixed a broken link in the `README.md`. (#41) - -## 0.2.15 (2015-07-05) - -### Added - -- Padding modifiers `%_?`, `%-?` and `%0?` are implemented. - They are glibc extensions which seem to be reasonably widespread (e.g. Ruby). - -- Added `%:z` specifier and corresponding formatting items - which is essentially the same as `%z` but with a colon. - -- Added a new specifier `%.f` which precision adapts from the input. - This was added as a response to the UX problems in the original nanosecond specifier `%f`. - -### Fixed - -- `Numeric::Timestamp` specifier (`%s`) was ignoring the time zone offset when provided. - -- Improved the documentation and associated tests for `strftime`. - -## 0.2.14 (2015-05-15) - -### Fixed - -- `NaiveDateTime +/- Duration` or `NaiveTime +/- Duration` could have gone wrong - when the `Duration` to be added is negative and has a fractional second part. - This was caused by an underflow in the conversion from `Duration` to the parts; - the lack of tests for this case allowed a bug. (#37) - -## 0.2.13 (2015-04-29) - -### Added - -- The optional dependency on `rustc_serialize` and - relevant `Rustc{En,De}codable` implementations for supported types has been added. - This is enabled by the `rustc-serialize` Cargo feature. (#34) - -### Changed - -- `chrono::Duration` reexport is changed to that of crates.io `time` crate. - This enables Rust 1.0 beta compatibility. - -## 0.2.4 (2015-03-03) - -### Fixed - -- Clarified the meaning of `Date` and fixed unwanted conversion problem - that only occurs with positive UTC offsets. (#27) - -## 0.2.3 (2015-02-27) - -### Added - -- `DateTime` and `Date` is now `Copy`/`Send` when `Tz::Offset` is `Copy`/`Send`. - The implementations for them were mistakenly omitted. (#25) - -### Fixed - -- `Local::from_utc_datetime` didn't set a correct offset. (#26) - -## 0.2.1 (2015-02-21) - -### Changed - -- `DelayedFormat` no longer conveys a redundant lifetime. - -## 0.2.0 (2015-02-19) - -### Added - -- `Offset` is splitted into `TimeZone` (constructor) and `Offset` (storage) types. - You would normally see only the former, as the latter is mostly an implementation detail. - Most importantly, `Local` now can be used to directly construct timezone-aware values. - - Some types (currently, `UTC` and `FixedOffset`) are both `TimeZone` and `Offset`, - but others aren't (e.g. `Local` is not what is being stored to each `DateTime` values). - -- `LocalResult::map` convenience method has been added. - -- `TimeZone` now allows a construction of `DateTime` values from UNIX timestamp, - via `timestamp` and `timestamp_opt` methods. - -- `TimeZone` now also has a method for parsing `DateTime`, namely `datetime_from_str`. - -- The following methods have been added to all date and time types: - - - `checked_add` - - `checked_sub` - - `format_with_items` - -- The following methods have been added to all timezone-aware types: - - - `timezone` - - `with_timezone` - - `naive_utc` - - `naive_local` - -- `parse_from_str` method has been added to all naive types and `DateTime`. - -- All naive types and instances of `DateTime` with time zones `UTC`, `Local` and `FixedOffset` - implement the `FromStr` trait. They parse what `std::fmt::Debug` would print. - -- `chrono::format` has been greatly rewritten. - - - The formatting syntax parser is modular now, available at `chrono::format::strftime`. - - - The parser and resolution algorithm is also modular, the former is available at - `chrono::format::parse` while the latter is available at `chrono::format::parsed`. - - - Explicit support for RFC 2822 and 3339 syntaxes is landed. - - - There is a minor formatting difference with atypical values, - e.g. for years not between 1 BCE and 9999 CE. - -### Changed - -- Most uses of `Offset` are converted to `TimeZone`. - In fact, *all* user-facing code is expected to be `Offset`-free. - -- `[Naive]DateTime::*num_seconds_from_unix_epoch*` methods have been renamed to - simply `timestamp` or `from_timestamp*`. The original names have been deprecated. - -### Removed - -- `Time` has been removed. This also prompts a related set of methods in `TimeZone`. - - This is in principle possible, but in practice has seen a little use - because it can only be meaningfully constructed via an existing `DateTime` value. - This made many operations to `Time` unintuitive or ambiguous, - so we simply let it go. - - In the case that `Time` is really required, one can use a simpler `NaiveTime`. - `NaiveTime` and `NaiveDate` can be freely combined and splitted, - and `TimeZone::from_{local,utc}_datetime` can be used to convert from/to the local time. - -- `with_offset` method has been removed. Use `with_timezone` method instead. - (This is not deprecated since it is an integral part of offset reform.) - -## 0.1.14 (2015-01-10) - -### Added - -- Added a missing `std::fmt::String` impl for `Local`. - -## 0.1.13 (2015-01-10) - -### Changed - -- Most types now implement both `std::fmt::Show` and `std::fmt::String`, - with the former used for the stricter output and the latter used for more casual output. - -### Removed - -- `Offset::name` has been replaced by a `std::fmt::String` implementation to `Offset`. - -## 0.1.12 (2015-01-08) - -### Removed - -- `Duration + T` no longer works due to the updated impl reachability rules. - Use `T + Duration` as a workaround. - -## 0.1.4 (2014-12-13) - -### Fixed - -- Fixed a bug that `Date::and_*` methods with an offset that can change the date are - off by one day. - -## 0.1.3 (2014-11-28) - -### Added - -- `{Date,Time,DateTime}::with_offset` methods have been added. - -- `LocalResult` now implements a common set of traits. - -- `LocalResult::and_*` methods have been added. - They are useful for safely chaining `LocalResult>` methods - to make `LocalResult>`. - -### Changed - -- `Offset::name` now returns `SendStr`. - -- `{Date,Time} - Duration` overloadings are now allowed. - -## 0.1.2 (2014-11-24) - -### Added - -- `Duration + Date` overloading is now allowed. - -### Changed - -- Chrono no longer needs `num` dependency. - -## 0.1.0 (2014-11-20) - -The initial version that was available to `crates.io`. - diff --git a/third_party/rust/chrono/CITATION.cff b/third_party/rust/chrono/CITATION.cff new file mode 100644 index 00000000000..21c47e7cde7 --- /dev/null +++ b/third_party/rust/chrono/CITATION.cff @@ -0,0 +1,33 @@ +# Parser settings. +cff-version: 1.2.0 +message: Please cite this crate using these information. + +# Version information. +date-released: 2025-02-26 +version: 0.4.40 + +# Project information. +abstract: Date and time library for Rust +authors: + - alias: quodlibetor + family-names: Maister + given-names: Brandon W. + - alias: djc + family-names: Ochtman + given-names: Dirkjan + - alias: lifthrasiir + family-names: Seonghoon + given-names: Kang + - alias: esheppa + family-names: Sheppard + given-names: Eric + - alias: pitdicker + family-names: Dicker + given-names: Paul +license: + - Apache-2.0 + - MIT +repository-artifact: https://crates.io/crates/chrono +repository-code: https://github.com/chronotope/chrono +title: chrono +url: https://docs.rs/chrono diff --git a/third_party/rust/chrono/Cargo.lock b/third_party/rust/chrono/Cargo.lock new file mode 100644 index 00000000000..f0680d8d20e --- /dev/null +++ b/third_party/rust/chrono/Cargo.lock @@ -0,0 +1,808 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "bstr" +version = "1.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" +dependencies = [ + "memchr", + "regex-automata", + "serde", +] + +[[package]] +name = "bumpalo" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" + +[[package]] +name = "bytecheck" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" +dependencies = [ + "bytecheck_derive", + "ptr_meta", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "cc" +version = "1.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c736e259eea577f443d5c86c304f9f4ae0295c43f3ba05c21f1d66b5f06001af" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.40" +dependencies = [ + "android-tzdata", + "arbitrary", + "bincode", + "iana-time-zone", + "js-sys", + "num-traits", + "pure-rust-locales", + "rkyv", + "serde", + "serde_derive", + "serde_json", + "similar-asserts", + "wasm-bindgen", + "wasm-bindgen-test", + "windows-bindgen", + "windows-link", +] + +[[package]] +name = "console" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "windows-sys", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "derive_arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "either" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7914353092ddf589ad78f25c5c1c21b7f80b0ff8621e7c814c3485b5306da9d" + +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.170" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" + +[[package]] +name = "log" +version = "0.4.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "minicov" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27fe9f1cc3c22e1687f9446c2083c4c5fc7f0bcf1c7a86bdbded14985895b4b" +dependencies = [ + "cc", + "walkdir", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" + +[[package]] +name = "proc-macro2" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "pure-rust-locales" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1190fd18ae6ce9e137184f207593877e70f39b015040156b1e05081cdfe3733a" + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" + +[[package]] +name = "rend" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" +dependencies = [ + "bytecheck", +] + +[[package]] +name = "rkyv" +version = "0.7.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" +dependencies = [ + "bitvec", + "bytecheck", + "hashbrown", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "rustversion" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" + +[[package]] +name = "ryu" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" + +[[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 = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + +[[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 2.0.98", +] + +[[package]] +name = "serde_json" +version = "1.0.139" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[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" +dependencies = [ + "bstr", + "unicode-segmentation", +] + +[[package]] +name = "similar-asserts" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b441962c817e33508847a22bd82f03a30cff43642dc2fae8b050566121eb9a" +dependencies = [ + "console", + "similar", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tinyvec" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "unicode-ident" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.98", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-bindgen-test" +version = "0.3.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66c8d5e33ca3b6d9fa3b4676d774c5778031d27a578c2b007f905acf816152c3" +dependencies = [ + "js-sys", + "minicov", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", +] + +[[package]] +name = "wasm-bindgen-test-macro" +version = "0.3.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17d5042cc5fa009658f9a7333ef24291b1291a25b6382dd68862a7f3b969f69b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows-bindgen" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d72f8d00d9d4bd00634be0ddbb927cec37d010b55753c4c07a401f4035da6268" +dependencies = [ + "rayon", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-link" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3" + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] diff --git a/third_party/rust/chrono/Cargo.toml b/third_party/rust/chrono/Cargo.toml index 2d3e7643377..2c4e0f282ee 100644 --- a/third_party/rust/chrono/Cargo.toml +++ b/third_party/rust/chrono/Cargo.toml @@ -3,84 +3,141 @@ # 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 +# to registry (e.g., crates.io) dependencies. # -# If you believe there's an error in this file please file an -# issue against the rust-lang/cargo repository. If you're -# editing this file be aware that the upstream Cargo.toml -# will likely look very different (and much more reasonable) +# 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.61.0" name = "chrono" -version = "0.4.19" -authors = ["Kang Seonghoon ", "Brandon W Maister "] -exclude = ["/ci/*", "/.travis.yml", "/appveyor.yml", "/Makefile"] +version = "0.4.40" +build = false +include = [ + "src/*", + "tests/*.rs", + "LICENSE.txt", + "CITATION.cff", +] +autolib = false +autobins = false +autoexamples = false +autotests = false +autobenches = false description = "Date and time library for Rust" homepage = "https://github.com/chronotope/chrono" documentation = "https://docs.rs/chrono/" readme = "README.md" -keywords = ["date", "time", "calendar"] +keywords = [ + "date", + "time", + "calendar", +] categories = ["date-and-time"] -license = "MIT/Apache-2.0" +license = "MIT OR Apache-2.0" repository = "https://github.com/chronotope/chrono" + [package.metadata.docs.rs] -features = ["serde"] +features = [ + "arbitrary", + "rkyv", + "serde", + "unstable-locales", +] +rustdoc-args = [ + "--cfg", + "docsrs", +] [package.metadata.playground] features = ["serde"] +[features] +__internal_bench = [] +alloc = [] +clock = [ + "winapi", + "iana-time-zone", + "android-tzdata", + "now", +] +default = [ + "clock", + "std", + "oldtime", + "wasmbind", +] +libc = [] +now = ["std"] +oldtime = [] +rkyv = [ + "dep:rkyv", + "rkyv/size_32", +] +rkyv-16 = [ + "dep:rkyv", + "rkyv?/size_16", +] +rkyv-32 = [ + "dep:rkyv", + "rkyv?/size_32", +] +rkyv-64 = [ + "dep:rkyv", + "rkyv?/size_64", +] +rkyv-validation = ["rkyv?/validation"] +std = ["alloc"] +unstable-locales = ["pure-rust-locales"] +wasmbind = [ + "wasm-bindgen", + "js-sys", +] +winapi = ["windows-link"] + [lib] name = "chrono" +path = "src/lib.rs" -[[bench]] -name = "chrono" -harness = false -required-features = ["__internal_bench"] +[[test]] +name = "dateutils" +path = "tests/dateutils.rs" -[[bench]] -name = "serde" -harness = false -required-features = ["serde"] -[dependencies.libc] -version = "0.2.69" +[[test]] +name = "wasm" +path = "tests/wasm.rs" + +[[test]] +name = "win_bindings" +path = "tests/win_bindings.rs" + +[dependencies.arbitrary] +version = "1.0.0" +features = ["derive"] optional = true -[dependencies.num-integer] -version = "0.1.36" -default-features = false - [dependencies.num-traits] version = "0.2" default-features = false [dependencies.pure-rust-locales] -version = "0.5.2" +version = "0.8" optional = true -[dependencies.rustc-serialize] -version = "0.3.20" +[dependencies.rkyv] +version = "0.7.43" optional = true +default-features = false [dependencies.serde] version = "1.0.99" optional = true default-features = false -[dependencies.time] -version = "0.1.43" -optional = true [dev-dependencies.bincode] -version = "0.8.0" - -[dev-dependencies.criterion] -version = "0.3" - -[dev-dependencies.doc-comment] -version = "0.3" - -[dev-dependencies.num-iter] -version = "0.1.35" -default-features = false +version = "1.3.0" [dev-dependencies.serde_derive] version = "1" @@ -89,31 +146,32 @@ default-features = false [dev-dependencies.serde_json] version = "1" -[features] -__doctest = [] -__internal_bench = [] -alloc = [] -clock = ["libc", "std", "winapi"] -default = ["clock", "std", "oldtime"] -oldtime = ["time"] -std = [] -unstable-locales = ["pure-rust-locales", "alloc"] -wasmbind = ["wasm-bindgen", "js-sys"] -[target."cfg(all(target_arch = \"wasm32\", not(any(target_os = \"emscripten\", target_os = \"wasi\"))))".dependencies.js-sys] +[dev-dependencies.similar-asserts] +version = "1.6.1" + +[target.'cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))'.dependencies.js-sys] version = "0.3" optional = true -[target."cfg(all(target_arch = \"wasm32\", not(any(target_os = \"emscripten\", target_os = \"wasi\"))))".dependencies.wasm-bindgen] +[target.'cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))'.dependencies.wasm-bindgen] version = "0.2" optional = true -[target."cfg(all(target_arch = \"wasm32\", not(any(target_os = \"emscripten\", target_os = \"wasi\"))))".dev-dependencies.wasm-bindgen-test] -version = "0.3" -[target."cfg(windows)".dependencies.winapi] -version = "0.3.0" -features = ["std", "minwinbase", "minwindef", "timezoneapi"] -optional = true -[badges.appveyor] -repository = "chronotope/chrono" -[badges.travis-ci] -repository = "chronotope/chrono" +[target.'cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))'.dev-dependencies.wasm-bindgen-test] +version = "0.3" + +[target.'cfg(target_os = "android")'.dependencies.android-tzdata] +version = "0.1.1" +optional = true + +[target."cfg(unix)".dependencies.iana-time-zone] +version = "0.1.45" +features = ["fallback"] +optional = true + +[target."cfg(windows)".dependencies.windows-link] +version = "0.1" +optional = true + +[target."cfg(windows)".dev-dependencies.windows-bindgen] +version = "0.60" diff --git a/third_party/rust/chrono/LICENSE.txt b/third_party/rust/chrono/LICENSE.txt index 924ff57f227..0f9e3cfda87 100644 --- a/third_party/rust/chrono/LICENSE.txt +++ b/third_party/rust/chrono/LICENSE.txt @@ -1,5 +1,5 @@ Rust-chrono is dual-licensed under The MIT License [1] and -Apache 2.0 License [2]. Copyright (c) 2014--2017, Kang Seonghoon and +Apache 2.0 License [2]. Copyright (c) 2014--2025, Kang Seonghoon and contributors. Nota Bene: This is same as the Rust Project's own license. diff --git a/third_party/rust/chrono/README.md b/third_party/rust/chrono/README.md deleted file mode 100644 index 5a5a74b42ab..00000000000 --- a/third_party/rust/chrono/README.md +++ /dev/null @@ -1,419 +0,0 @@ -[Chrono][docsrs]: Date and Time for Rust -======================================== - -[![Chrono GitHub Actions][gh-image]][gh-checks] -[![Chrono on crates.io][cratesio-image]][cratesio] -[![Chrono on docs.rs][docsrs-image]][docsrs] -[![Join the chat at https://gitter.im/chrono-rs/chrono][gitter-image]][gitter] - -[gh-image]: https://github.com/chronotope/chrono/workflows/test/badge.svg -[gh-checks]: https://github.com/chronotope/chrono/actions?query=workflow%3Atest -[cratesio-image]: https://img.shields.io/crates/v/chrono.svg -[cratesio]: https://crates.io/crates/chrono -[docsrs-image]: https://docs.rs/chrono/badge.svg -[docsrs]: https://docs.rs/chrono -[gitter-image]: https://badges.gitter.im/chrono-rs/chrono.svg -[gitter]: https://gitter.im/chrono-rs/chrono - -It aims to be a feature-complete superset of -the [time](https://github.com/rust-lang-deprecated/time) library. -In particular, - -* Chrono strictly adheres to ISO 8601. -* Chrono is timezone-aware by default, with separate timezone-naive types. -* Chrono is space-optimal and (while not being the primary goal) reasonably efficient. - -There were several previous attempts to bring a good date and time library to Rust, -which Chrono builds upon and should acknowledge: - -* [Initial research on - the wiki](https://github.com/rust-lang/rust-wiki-backup/blob/master/Lib-datetime.md) -* Dietrich Epp's [datetime-rs](https://github.com/depp/datetime-rs) -* Luis de Bethencourt's [rust-datetime](https://github.com/luisbg/rust-datetime) - -Any significant changes to Chrono are documented in -the [`CHANGELOG.md`](https://github.com/chronotope/chrono/blob/main/CHANGELOG.md) file. - - -## Usage - -Put this in your `Cargo.toml`: - -```toml -[dependencies] -chrono = "0.4" -``` - -### Features - -Chrono supports various runtime environments and operating systems, and has -several features that may be enabled or disabled. - -Default features: - -- `alloc`: Enable features that depend on allocation (primarily string formatting) -- `std`: Enables functionality that depends on the standard library. This - is a superset of `alloc` and adds interoperation with standard library types - and traits. -- `clock`: enables reading the system time (`now`), independent of whether - `std::time::SystemTime` is present, depends on having a libc. - -Optional features: - -- `wasmbind`: Enable integration with [wasm-bindgen][] and its `js-sys` project -- [`serde`][]: Enable serialization/deserialization via serde. -- `unstable-locales`: Enable localization. This adds various methods with a - `_localized` suffix. The implementation and API may change or even be - removed in a patch release. Feedback welcome. - -[`serde`]: https://github.com/serde-rs/serde -[wasm-bindgen]: https://github.com/rustwasm/wasm-bindgen - -See the [cargo docs][] for examples of specifying features. - -[cargo docs]: https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#choosing-features - -## Overview - -### Duration - -Chrono currently uses its own [`Duration`] type to represent the magnitude -of a time span. Since this has the same name as the newer, standard type for -duration, the reference will refer this type as `OldDuration`. - -Note that this is an "accurate" duration represented as seconds and -nanoseconds and does not represent "nominal" components such as days or -months. - -When the `oldtime` feature is enabled, [`Duration`] is an alias for the -[`time::Duration`](https://docs.rs/time/0.1.40/time/struct.Duration.html) -type from v0.1 of the time crate. time v0.1 is deprecated, so new code -should disable the `oldtime` feature and use the `chrono::Duration` type -instead. The `oldtime` feature is enabled by default for backwards -compatibility, but future versions of Chrono are likely to remove the -feature entirely. - -Chrono does not yet natively support -the standard [`Duration`](https://doc.rust-lang.org/std/time/struct.Duration.html) type, -but it will be supported in the future. -Meanwhile you can convert between two types with -[`Duration::from_std`](https://docs.rs/time/0.1.40/time/struct.Duration.html#method.from_std) -and -[`Duration::to_std`](https://docs.rs/time/0.1.40/time/struct.Duration.html#method.to_std) -methods. - -### Date and Time - -Chrono provides a -[**`DateTime`**](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html) -type to represent a date and a time in a timezone. - -For more abstract moment-in-time tracking such as internal timekeeping -that is unconcerned with timezones, consider -[`time::SystemTime`](https://doc.rust-lang.org/std/time/struct.SystemTime.html), -which tracks your system clock, or -[`time::Instant`](https://doc.rust-lang.org/std/time/struct.Instant.html), which -is an opaque but monotonically-increasing representation of a moment in time. - -`DateTime` is timezone-aware and must be constructed from -the [**`TimeZone`**](https://docs.rs/chrono/0.4/chrono/offset/trait.TimeZone.html) object, -which defines how the local date is converted to and back from the UTC date. -There are three well-known `TimeZone` implementations: - -* [**`Utc`**](https://docs.rs/chrono/0.4/chrono/offset/struct.Utc.html) specifies the UTC time zone. It is most efficient. - -* [**`Local`**](https://docs.rs/chrono/0.4/chrono/offset/struct.Local.html) specifies the system local time zone. - -* [**`FixedOffset`**](https://docs.rs/chrono/0.4/chrono/offset/struct.FixedOffset.html) specifies - an arbitrary, fixed time zone such as UTC+09:00 or UTC-10:30. - This often results from the parsed textual date and time. - Since it stores the most information and does not depend on the system environment, - you would want to normalize other `TimeZone`s into this type. - -`DateTime`s with different `TimeZone` types are distinct and do not mix, -but can be converted to each other using -the [`DateTime::with_timezone`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.with_timezone) method. - -You can get the current date and time in the UTC time zone -([`Utc::now()`](https://docs.rs/chrono/0.4/chrono/offset/struct.Utc.html#method.now)) -or in the local time zone -([`Local::now()`](https://docs.rs/chrono/0.4/chrono/offset/struct.Local.html#method.now)). - -```rust -use chrono::prelude::*; - -let utc: DateTime = Utc::now(); // e.g. `2014-11-28T12:45:59.324310806Z` -let local: DateTime = Local::now(); // e.g. `2014-11-28T21:45:59.324310806+09:00` -``` - -Alternatively, you can create your own date and time. -This is a bit verbose due to Rust's lack of function and method overloading, -but in turn we get a rich combination of initialization methods. - -```rust -use chrono::prelude::*; -use chrono::offset::LocalResult; - -let dt = Utc.ymd(2014, 7, 8).and_hms(9, 10, 11); // `2014-07-08T09:10:11Z` -// July 8 is 188th day of the year 2014 (`o` for "ordinal") -assert_eq!(dt, Utc.yo(2014, 189).and_hms(9, 10, 11)); -// July 8 is Tuesday in ISO week 28 of the year 2014. -assert_eq!(dt, Utc.isoywd(2014, 28, Weekday::Tue).and_hms(9, 10, 11)); - -let dt = Utc.ymd(2014, 7, 8).and_hms_milli(9, 10, 11, 12); // `2014-07-08T09:10:11.012Z` -assert_eq!(dt, Utc.ymd(2014, 7, 8).and_hms_micro(9, 10, 11, 12_000)); -assert_eq!(dt, Utc.ymd(2014, 7, 8).and_hms_nano(9, 10, 11, 12_000_000)); - -// dynamic verification -assert_eq!(Utc.ymd_opt(2014, 7, 8).and_hms_opt(21, 15, 33), - LocalResult::Single(Utc.ymd(2014, 7, 8).and_hms(21, 15, 33))); -assert_eq!(Utc.ymd_opt(2014, 7, 8).and_hms_opt(80, 15, 33), LocalResult::None); -assert_eq!(Utc.ymd_opt(2014, 7, 38).and_hms_opt(21, 15, 33), LocalResult::None); - -// other time zone objects can be used to construct a local datetime. -// obviously, `local_dt` is normally different from `dt`, but `fixed_dt` should be identical. -let local_dt = Local.ymd(2014, 7, 8).and_hms_milli(9, 10, 11, 12); -let fixed_dt = FixedOffset::east(9 * 3600).ymd(2014, 7, 8).and_hms_milli(18, 10, 11, 12); -assert_eq!(dt, fixed_dt); -``` - -Various properties are available to the date and time, and can be altered individually. -Most of them are defined in the traits [`Datelike`](https://docs.rs/chrono/0.4/chrono/trait.Datelike.html) and -[`Timelike`](https://docs.rs/chrono/0.4/chrono/trait.Timelike.html) which you should `use` before. -Addition and subtraction is also supported. -The following illustrates most supported operations to the date and time: - -```rust - -use chrono::prelude::*; -use chrono::Duration; - -// assume this returned `2014-11-28T21:45:59.324310806+09:00`: -let dt = FixedOffset::east(9*3600).ymd(2014, 11, 28).and_hms_nano(21, 45, 59, 324310806); - -// property accessors -assert_eq!((dt.year(), dt.month(), dt.day()), (2014, 11, 28)); -assert_eq!((dt.month0(), dt.day0()), (10, 27)); // for unfortunate souls -assert_eq!((dt.hour(), dt.minute(), dt.second()), (21, 45, 59)); -assert_eq!(dt.weekday(), Weekday::Fri); -assert_eq!(dt.weekday().number_from_monday(), 5); // Mon=1, ..., Sun=7 -assert_eq!(dt.ordinal(), 332); // the day of year -assert_eq!(dt.num_days_from_ce(), 735565); // the number of days from and including Jan 1, 1 - -// time zone accessor and manipulation -assert_eq!(dt.offset().fix().local_minus_utc(), 9 * 3600); -assert_eq!(dt.timezone(), FixedOffset::east(9 * 3600)); -assert_eq!(dt.with_timezone(&Utc), Utc.ymd(2014, 11, 28).and_hms_nano(12, 45, 59, 324310806)); - -// a sample of property manipulations (validates dynamically) -assert_eq!(dt.with_day(29).unwrap().weekday(), Weekday::Sat); // 2014-11-29 is Saturday -assert_eq!(dt.with_day(32), None); -assert_eq!(dt.with_year(-300).unwrap().num_days_from_ce(), -109606); // November 29, 301 BCE - -// arithmetic operations -let dt1 = Utc.ymd(2014, 11, 14).and_hms(8, 9, 10); -let dt2 = Utc.ymd(2014, 11, 14).and_hms(10, 9, 8); -assert_eq!(dt1.signed_duration_since(dt2), Duration::seconds(-2 * 3600 + 2)); -assert_eq!(dt2.signed_duration_since(dt1), Duration::seconds(2 * 3600 - 2)); -assert_eq!(Utc.ymd(1970, 1, 1).and_hms(0, 0, 0) + Duration::seconds(1_000_000_000), - Utc.ymd(2001, 9, 9).and_hms(1, 46, 40)); -assert_eq!(Utc.ymd(1970, 1, 1).and_hms(0, 0, 0) - Duration::seconds(1_000_000_000), - Utc.ymd(1938, 4, 24).and_hms(22, 13, 20)); -``` - -### Formatting and Parsing - -Formatting is done via the [`format`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.format) method, -which format is equivalent to the familiar `strftime` format. - -See [`format::strftime`](https://docs.rs/chrono/0.4/chrono/format/strftime/index.html#specifiers) -documentation for full syntax and list of specifiers. - -The default `to_string` method and `{:?}` specifier also give a reasonable representation. -Chrono also provides [`to_rfc2822`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.to_rfc2822) and -[`to_rfc3339`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.to_rfc3339) methods -for well-known formats. - -Chrono now also provides date formatting in almost any language without the -help of an additional C library. This functionality is under the feature -`unstable-locales`: - -```text -chrono { version = "0.4", features = ["unstable-locales"] -``` - -The `unstable-locales` feature requires and implies at least the `alloc` feature. - -```rust -use chrono::prelude::*; - -let dt = Utc.ymd(2014, 11, 28).and_hms(12, 0, 9); -assert_eq!(dt.format("%Y-%m-%d %H:%M:%S").to_string(), "2014-11-28 12:00:09"); -assert_eq!(dt.format("%a %b %e %T %Y").to_string(), "Fri Nov 28 12:00:09 2014"); -assert_eq!(dt.format_localized("%A %e %B %Y, %T", Locale::fr_BE).to_string(), "vendredi 28 novembre 2014, 12:00:09"); -assert_eq!(dt.format("%a %b %e %T %Y").to_string(), dt.format("%c").to_string()); - -assert_eq!(dt.to_string(), "2014-11-28 12:00:09 UTC"); -assert_eq!(dt.to_rfc2822(), "Fri, 28 Nov 2014 12:00:09 +0000"); -assert_eq!(dt.to_rfc3339(), "2014-11-28T12:00:09+00:00"); -assert_eq!(format!("{:?}", dt), "2014-11-28T12:00:09Z"); - -// Note that milli/nanoseconds are only printed if they are non-zero -let dt_nano = Utc.ymd(2014, 11, 28).and_hms_nano(12, 0, 9, 1); -assert_eq!(format!("{:?}", dt_nano), "2014-11-28T12:00:09.000000001Z"); -``` - -Parsing can be done with three methods: - -1. The standard [`FromStr`](https://doc.rust-lang.org/std/str/trait.FromStr.html) trait - (and [`parse`](https://doc.rust-lang.org/std/primitive.str.html#method.parse) method - on a string) can be used for parsing `DateTime`, `DateTime` and - `DateTime` values. This parses what the `{:?}` - ([`std::fmt::Debug`](https://doc.rust-lang.org/std/fmt/trait.Debug.html)) - format specifier prints, and requires the offset to be present. - -2. [`DateTime::parse_from_str`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.parse_from_str) parses - a date and time with offsets and returns `DateTime`. - This should be used when the offset is a part of input and the caller cannot guess that. - It *cannot* be used when the offset can be missing. - [`DateTime::parse_from_rfc2822`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.parse_from_rfc2822) - and - [`DateTime::parse_from_rfc3339`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.parse_from_rfc3339) - are similar but for well-known formats. - -3. [`Offset::datetime_from_str`](https://docs.rs/chrono/0.4/chrono/offset/trait.TimeZone.html#method.datetime_from_str) is - similar but returns `DateTime` of given offset. - When the explicit offset is missing from the input, it simply uses given offset. - It issues an error when the input contains an explicit offset different - from the current offset. - -More detailed control over the parsing process is available via -[`format`](https://docs.rs/chrono/0.4/chrono/format/index.html) module. - -```rust -use chrono::prelude::*; - -let dt = Utc.ymd(2014, 11, 28).and_hms(12, 0, 9); -let fixed_dt = dt.with_timezone(&FixedOffset::east(9*3600)); - -// method 1 -assert_eq!("2014-11-28T12:00:09Z".parse::>(), Ok(dt.clone())); -assert_eq!("2014-11-28T21:00:09+09:00".parse::>(), Ok(dt.clone())); -assert_eq!("2014-11-28T21:00:09+09:00".parse::>(), Ok(fixed_dt.clone())); - -// method 2 -assert_eq!(DateTime::parse_from_str("2014-11-28 21:00:09 +09:00", "%Y-%m-%d %H:%M:%S %z"), - Ok(fixed_dt.clone())); -assert_eq!(DateTime::parse_from_rfc2822("Fri, 28 Nov 2014 21:00:09 +0900"), - Ok(fixed_dt.clone())); -assert_eq!(DateTime::parse_from_rfc3339("2014-11-28T21:00:09+09:00"), Ok(fixed_dt.clone())); - -// method 3 -assert_eq!(Utc.datetime_from_str("2014-11-28 12:00:09", "%Y-%m-%d %H:%M:%S"), Ok(dt.clone())); -assert_eq!(Utc.datetime_from_str("Fri Nov 28 12:00:09 2014", "%a %b %e %T %Y"), Ok(dt.clone())); - -// oops, the year is missing! -assert!(Utc.datetime_from_str("Fri Nov 28 12:00:09", "%a %b %e %T %Y").is_err()); -// oops, the format string does not include the year at all! -assert!(Utc.datetime_from_str("Fri Nov 28 12:00:09", "%a %b %e %T").is_err()); -// oops, the weekday is incorrect! -assert!(Utc.datetime_from_str("Sat Nov 28 12:00:09 2014", "%a %b %e %T %Y").is_err()); -``` - -Again : See [`format::strftime`](https://docs.rs/chrono/0.4/chrono/format/strftime/index.html#specifiers) -documentation for full syntax and list of specifiers. - -### Conversion from and to EPOCH timestamps - -Use [`Utc.timestamp(seconds, nanoseconds)`](https://docs.rs/chrono/0.4/chrono/offset/trait.TimeZone.html#method.timestamp) -to construct a [`DateTime`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html) from a UNIX timestamp -(seconds, nanoseconds that passed since January 1st 1970). - -Use [`DateTime.timestamp`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.timestamp) to get the timestamp (in seconds) -from a [`DateTime`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html). Additionally, you can use -[`DateTime.timestamp_subsec_nanos`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.timestamp_subsec_nanos) -to get the number of additional number of nanoseconds. - -```rust -// We need the trait in scope to use Utc::timestamp(). -use chrono::{DateTime, TimeZone, Utc}; - -// Construct a datetime from epoch: -let dt = Utc.timestamp(1_500_000_000, 0); -assert_eq!(dt.to_rfc2822(), "Fri, 14 Jul 2017 02:40:00 +0000"); - -// Get epoch value from a datetime: -let dt = DateTime::parse_from_rfc2822("Fri, 14 Jul 2017 02:40:00 +0000").unwrap(); -assert_eq!(dt.timestamp(), 1_500_000_000); -``` - -### Individual date - -Chrono also provides an individual date type ([**`Date`**](https://docs.rs/chrono/0.4/chrono/struct.Date.html)). -It also has time zones attached, and have to be constructed via time zones. -Most operations available to `DateTime` are also available to `Date` whenever appropriate. - -```rust -use chrono::prelude::*; -use chrono::offset::LocalResult; - -assert_eq!(Utc::today(), Utc::now().date()); -assert_eq!(Local::today(), Local::now().date()); - -assert_eq!(Utc.ymd(2014, 11, 28).weekday(), Weekday::Fri); -assert_eq!(Utc.ymd_opt(2014, 11, 31), LocalResult::None); -assert_eq!(Utc.ymd(2014, 11, 28).and_hms_milli(7, 8, 9, 10).format("%H%M%S").to_string(), - "070809"); -``` - -There is no timezone-aware `Time` due to the lack of usefulness and also the complexity. - -`DateTime` has [`date`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.date) method -which returns a `Date` which represents its date component. -There is also a [`time`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.time) method, -which simply returns a naive local time described below. - -### Naive date and time - -Chrono provides naive counterparts to `Date`, (non-existent) `Time` and `DateTime` -as [**`NaiveDate`**](https://docs.rs/chrono/0.4/chrono/naive/struct.NaiveDate.html), -[**`NaiveTime`**](https://docs.rs/chrono/0.4/chrono/naive/struct.NaiveTime.html) and -[**`NaiveDateTime`**](https://docs.rs/chrono/0.4/chrono/naive/struct.NaiveDateTime.html) respectively. - -They have almost equivalent interfaces as their timezone-aware twins, -but are not associated to time zones obviously and can be quite low-level. -They are mostly useful for building blocks for higher-level types. - -Timezone-aware `DateTime` and `Date` types have two methods returning naive versions: -[`naive_local`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.naive_local) returns -a view to the naive local time, -and [`naive_utc`](https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.naive_utc) returns -a view to the naive UTC time. - -## Limitations - -Only proleptic Gregorian calendar (i.e. extended to support older dates) is supported. -Be very careful if you really have to deal with pre-20C dates, they can be in Julian or others. - -Date types are limited in about +/- 262,000 years from the common epoch. -Time types are limited in the nanosecond accuracy. - -[Leap seconds are supported in the representation but -Chrono doesn't try to make use of them](https://docs.rs/chrono/0.4/chrono/naive/struct.NaiveTime.html#leap-second-handling). -(The main reason is that leap seconds are not really predictable.) -Almost *every* operation over the possible leap seconds will ignore them. -Consider using `NaiveDateTime` with the implicit TAI (International Atomic Time) scale -if you want. - -Chrono inherently does not support an inaccurate or partial date and time representation. -Any operation that can be ambiguous will return `None` in such cases. -For example, "a month later" of 2014-01-30 is not well-defined -and consequently `Utc.ymd(2014, 1, 30).with_month(2)` returns `None`. - -Non ISO week handling is not yet supported. -For now you can use the [chrono_ext](https://crates.io/crates/chrono_ext) -crate ([sources](https://github.com/bcourtine/chrono-ext/)). - -Advanced time zone handling is not yet supported. -For now you can try the [Chrono-tz](https://github.com/chronotope/chrono-tz/) crate instead. - diff --git a/third_party/rust/chrono/benches/chrono.rs b/third_party/rust/chrono/benches/chrono.rs deleted file mode 100644 index 1c640634ac1..00000000000 --- a/third_party/rust/chrono/benches/chrono.rs +++ /dev/null @@ -1,116 +0,0 @@ -//! Benchmarks for chrono that just depend on std - -extern crate chrono; -extern crate criterion; - -use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; - -use chrono::prelude::*; -use chrono::{DateTime, FixedOffset, Utc, __BenchYearFlags}; - -fn bench_datetime_parse_from_rfc2822(c: &mut Criterion) { - c.bench_function("bench_datetime_parse_from_rfc2822", |b| { - b.iter(|| { - let str = black_box("Wed, 18 Feb 2015 23:16:09 +0000"); - DateTime::parse_from_rfc2822(str).unwrap() - }) - }); -} - -fn bench_datetime_parse_from_rfc3339(c: &mut Criterion) { - c.bench_function("bench_datetime_parse_from_rfc3339", |b| { - b.iter(|| { - let str = black_box("2015-02-18T23:59:60.234567+05:00"); - DateTime::parse_from_rfc3339(str).unwrap() - }) - }); -} - -fn bench_datetime_from_str(c: &mut Criterion) { - c.bench_function("bench_datetime_from_str", |b| { - b.iter(|| { - use std::str::FromStr; - let str = black_box("2019-03-30T18:46:57.193Z"); - DateTime::::from_str(str).unwrap() - }) - }); -} - -fn bench_datetime_to_rfc2822(c: &mut Criterion) { - let pst = FixedOffset::east(8 * 60 * 60); - let dt = pst.ymd(2018, 1, 11).and_hms_nano(10, 5, 13, 084_660_000); - c.bench_function("bench_datetime_to_rfc2822", |b| b.iter(|| black_box(dt).to_rfc2822())); -} - -fn bench_datetime_to_rfc3339(c: &mut Criterion) { - let pst = FixedOffset::east(8 * 60 * 60); - let dt = pst.ymd(2018, 1, 11).and_hms_nano(10, 5, 13, 084_660_000); - c.bench_function("bench_datetime_to_rfc3339", |b| b.iter(|| black_box(dt).to_rfc3339())); -} - -fn bench_year_flags_from_year(c: &mut Criterion) { - c.bench_function("bench_year_flags_from_year", |b| { - b.iter(|| { - for year in -999i32..1000 { - __BenchYearFlags::from_year(year); - } - }) - }); -} - -/// Returns the number of multiples of `div` in the range `start..end`. -/// -/// If the range `start..end` is back-to-front, i.e. `start` is greater than `end`, the -/// behaviour is defined by the following equation: -/// `in_between(start, end, div) == - in_between(end, start, div)`. -/// -/// When `div` is 1, this is equivalent to `end - start`, i.e. the length of `start..end`. -/// -/// # Panics -/// -/// Panics if `div` is not positive. -fn in_between(start: i32, end: i32, div: i32) -> i32 { - assert!(div > 0, "in_between: nonpositive div = {}", div); - let start = (start.div_euclid(div), start.rem_euclid(div)); - let end = (end.div_euclid(div), end.rem_euclid(div)); - // The lowest multiple of `div` greater than or equal to `start`, divided. - let start = start.0 + (start.1 != 0) as i32; - // The lowest multiple of `div` greater than or equal to `end`, divided. - let end = end.0 + (end.1 != 0) as i32; - end - start -} - -/// Alternative implementation to `Datelike::num_days_from_ce` -fn num_days_from_ce_alt(date: &Date) -> i32 { - let year = date.year(); - let diff = move |div| in_between(1, year, div); - // 365 days a year, one more in leap years. In the gregorian calendar, leap years are all - // the multiples of 4 except multiples of 100 but including multiples of 400. - date.ordinal() as i32 + 365 * diff(1) + diff(4) - diff(100) + diff(400) -} - -fn bench_num_days_from_ce(c: &mut Criterion) { - let mut group = c.benchmark_group("num_days_from_ce"); - for year in &[1, 500, 2000, 2019] { - let d = NaiveDate::from_ymd(*year, 1, 1); - group.bench_with_input(BenchmarkId::new("new", year), &d, |b, y| { - b.iter(|| num_days_from_ce_alt(y)) - }); - group.bench_with_input(BenchmarkId::new("classic", year), &d, |b, y| { - b.iter(|| y.num_days_from_ce()) - }); - } -} - -criterion_group!( - benches, - bench_datetime_parse_from_rfc2822, - bench_datetime_parse_from_rfc3339, - bench_datetime_from_str, - bench_datetime_to_rfc2822, - bench_datetime_to_rfc3339, - bench_year_flags_from_year, - bench_num_days_from_ce, -); - -criterion_main!(benches); diff --git a/third_party/rust/chrono/benches/serde.rs b/third_party/rust/chrono/benches/serde.rs deleted file mode 100644 index 860b06e1ae3..00000000000 --- a/third_party/rust/chrono/benches/serde.rs +++ /dev/null @@ -1,30 +0,0 @@ -extern crate chrono; -extern crate criterion; - -use criterion::{black_box, criterion_group, criterion_main, Criterion}; - -use chrono::NaiveDateTime; - -fn bench_ser_naivedatetime_string(c: &mut Criterion) { - c.bench_function("bench_ser_naivedatetime_string", |b| { - let dt: NaiveDateTime = "2000-01-01T00:00:00".parse().unwrap(); - b.iter(|| { - black_box(serde_json::to_string(&dt)).unwrap(); - }); - }); -} - -fn bench_ser_naivedatetime_writer(c: &mut Criterion) { - c.bench_function("bench_ser_naivedatetime_writer", |b| { - let mut s: Vec = Vec::with_capacity(20); - let dt: NaiveDateTime = "2000-01-01T00:00:00".parse().unwrap(); - b.iter(|| { - let s = &mut s; - s.clear(); - black_box(serde_json::to_writer(s, &dt)).unwrap(); - }); - }); -} - -criterion_group!(benches, bench_ser_naivedatetime_writer, bench_ser_naivedatetime_string); -criterion_main!(benches); diff --git a/third_party/rust/chrono/rustfmt.toml b/third_party/rust/chrono/rustfmt.toml deleted file mode 100644 index 2a35f0230c6..00000000000 --- a/third_party/rust/chrono/rustfmt.toml +++ /dev/null @@ -1 +0,0 @@ -use_small_heuristics = "Max" diff --git a/third_party/rust/chrono/src/date.rs b/third_party/rust/chrono/src/date.rs index 0012d360498..a66882cecce 100644 --- a/third_party/rust/chrono/src/date.rs +++ b/third_party/rust/chrono/src/date.rs @@ -2,73 +2,88 @@ // See README.md and LICENSE.txt for details. //! ISO 8601 calendar date with time zone. +#![allow(deprecated)] -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(feature = "alloc")] use core::borrow::Borrow; use core::cmp::Ordering; -use core::ops::{Add, Sub}; +use core::ops::{Add, AddAssign, Sub, SubAssign}; use core::{fmt, hash}; -use oldtime::Duration as OldDuration; -#[cfg(feature = "unstable-locales")] -use format::Locale; -#[cfg(any(feature = "alloc", feature = "std", test))] -use format::{DelayedFormat, Item, StrftimeItems}; -use naive::{self, IsoWeek, NaiveDate, NaiveTime}; -use offset::{TimeZone, Utc}; -use DateTime; -use {Datelike, Weekday}; +#[cfg(feature = "rkyv")] +use rkyv::{Archive, Deserialize, Serialize}; + +#[cfg(all(feature = "unstable-locales", feature = "alloc"))] +use crate::format::Locale; +#[cfg(feature = "alloc")] +use crate::format::{DelayedFormat, Item, StrftimeItems}; +use crate::naive::{IsoWeek, NaiveDate, NaiveTime}; +use crate::offset::{TimeZone, Utc}; +use crate::{DateTime, Datelike, TimeDelta, Weekday}; /// ISO 8601 calendar date with time zone. /// -/// This type should be considered ambiguous at best, -/// due to the inherent lack of precision required for the time zone resolution. -/// For serialization and deserialization uses, it is best to use `NaiveDate` instead. +/// You almost certainly want to be using a [`NaiveDate`] instead of this type. +/// +/// This type primarily exists to aid in the construction of DateTimes that +/// have a timezone by way of the [`TimeZone`] datelike constructors (e.g. +/// [`TimeZone::ymd`]). +/// +/// This type should be considered ambiguous at best, due to the inherent lack +/// of precision required for the time zone resolution. +/// /// There are some guarantees on the usage of `Date`: /// -/// - If properly constructed via `TimeZone::ymd` and others without an error, +/// - If properly constructed via [`TimeZone::ymd`] and others without an error, /// the corresponding local date should exist for at least a moment. /// (It may still have a gap from the offset changes.) /// -/// - The `TimeZone` is free to assign *any* `Offset` to the local date, -/// as long as that offset did occur in given day. +/// - The `TimeZone` is free to assign *any* [`Offset`](crate::offset::Offset) to the +/// local date, as long as that offset did occur in given day. +/// /// For example, if `2015-03-08T01:59-08:00` is followed by `2015-03-08T03:00-07:00`, /// it may produce either `2015-03-08-08:00` or `2015-03-08-07:00` /// but *not* `2015-03-08+00:00` and others. /// -/// - Once constructed as a full `DateTime`, -/// `DateTime::date` and other associated methods should return those for the original `Date`. -/// For example, if `dt = tz.ymd(y,m,d).hms(h,n,s)` were valid, `dt.date() == tz.ymd(y,m,d)`. +/// - Once constructed as a full `DateTime`, [`DateTime::date`] and other associated +/// methods should return those for the original `Date`. For example, if `dt = +/// tz.ymd_opt(y,m,d).unwrap().hms(h,n,s)` were valid, `dt.date() == tz.ymd_opt(y,m,d).unwrap()`. /// /// - The date is timezone-agnostic up to one day (i.e. practically always), /// so the local date and UTC date should be equal for most cases -/// even though the raw calculation between `NaiveDate` and `Duration` may not. +/// even though the raw calculation between `NaiveDate` and `TimeDelta` may not. +#[deprecated(since = "0.4.23", note = "Use `NaiveDate` or `DateTime` instead")] #[derive(Clone)] +#[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] pub struct Date { date: NaiveDate, offset: Tz::Offset, } /// The minimum possible `Date`. -pub const MIN_DATE: Date = Date { date: naive::MIN_DATE, offset: Utc }; +#[allow(deprecated)] +#[deprecated(since = "0.4.20", note = "Use Date::MIN_UTC instead")] +pub const MIN_DATE: Date = Date::::MIN_UTC; /// The maximum possible `Date`. -pub const MAX_DATE: Date = Date { date: naive::MAX_DATE, offset: Utc }; +#[allow(deprecated)] +#[deprecated(since = "0.4.20", note = "Use Date::MAX_UTC instead")] +pub const MAX_DATE: Date = Date::::MAX_UTC; impl Date { /// Makes a new `Date` with given *UTC* date and offset. /// The local date should be constructed via the `TimeZone` trait. - // - // note: this constructor is purposely not named to `new` to discourage the direct usage. #[inline] + #[must_use] pub fn from_utc(date: NaiveDate, offset: Tz::Offset) -> Date { - Date { date: date, offset: offset } + Date { date, offset } } /// Makes a new `DateTime` from the current date and given `NaiveTime`. /// The offset in the current date is preserved. /// - /// Panics on invalid datetime. + /// Returns `None` on invalid datetime. #[inline] + #[must_use] pub fn and_time(&self, time: NaiveTime) -> Option> { let localdt = self.naive_local().and_time(time); self.timezone().from_local_datetime(&localdt).single() @@ -78,7 +93,9 @@ impl Date { /// The offset in the current date is preserved. /// /// Panics on invalid hour, minute and/or second. + #[deprecated(since = "0.4.23", note = "Use and_hms_opt() instead")] #[inline] + #[must_use] pub fn and_hms(&self, hour: u32, min: u32, sec: u32) -> DateTime { self.and_hms_opt(hour, min, sec).expect("invalid time") } @@ -88,6 +105,7 @@ impl Date { /// /// Returns `None` on invalid hour, minute and/or second. #[inline] + #[must_use] pub fn and_hms_opt(&self, hour: u32, min: u32, sec: u32) -> Option> { NaiveTime::from_hms_opt(hour, min, sec).and_then(|time| self.and_time(time)) } @@ -97,7 +115,9 @@ impl Date { /// The offset in the current date is preserved. /// /// Panics on invalid hour, minute, second and/or millisecond. + #[deprecated(since = "0.4.23", note = "Use and_hms_milli_opt() instead")] #[inline] + #[must_use] pub fn and_hms_milli(&self, hour: u32, min: u32, sec: u32, milli: u32) -> DateTime { self.and_hms_milli_opt(hour, min, sec, milli).expect("invalid time") } @@ -108,6 +128,7 @@ impl Date { /// /// Returns `None` on invalid hour, minute, second and/or millisecond. #[inline] + #[must_use] pub fn and_hms_milli_opt( &self, hour: u32, @@ -123,7 +144,9 @@ impl Date { /// The offset in the current date is preserved. /// /// Panics on invalid hour, minute, second and/or microsecond. + #[deprecated(since = "0.4.23", note = "Use and_hms_micro_opt() instead")] #[inline] + #[must_use] pub fn and_hms_micro(&self, hour: u32, min: u32, sec: u32, micro: u32) -> DateTime { self.and_hms_micro_opt(hour, min, sec, micro).expect("invalid time") } @@ -134,6 +157,7 @@ impl Date { /// /// Returns `None` on invalid hour, minute, second and/or microsecond. #[inline] + #[must_use] pub fn and_hms_micro_opt( &self, hour: u32, @@ -149,7 +173,9 @@ impl Date { /// The offset in the current date is preserved. /// /// Panics on invalid hour, minute, second and/or nanosecond. + #[deprecated(since = "0.4.23", note = "Use and_hms_nano_opt() instead")] #[inline] + #[must_use] pub fn and_hms_nano(&self, hour: u32, min: u32, sec: u32, nano: u32) -> DateTime { self.and_hms_nano_opt(hour, min, sec, nano).expect("invalid time") } @@ -160,6 +186,7 @@ impl Date { /// /// Returns `None` on invalid hour, minute, second and/or nanosecond. #[inline] + #[must_use] pub fn and_hms_nano_opt( &self, hour: u32, @@ -173,7 +200,9 @@ impl Date { /// Makes a new `Date` for the next date. /// /// Panics when `self` is the last representable date. + #[deprecated(since = "0.4.23", note = "Use succ_opt() instead")] #[inline] + #[must_use] pub fn succ(&self) -> Date { self.succ_opt().expect("out of bound") } @@ -182,6 +211,7 @@ impl Date { /// /// Returns `None` when `self` is the last representable date. #[inline] + #[must_use] pub fn succ_opt(&self) -> Option> { self.date.succ_opt().map(|date| Date::from_utc(date, self.offset.clone())) } @@ -189,7 +219,9 @@ impl Date { /// Makes a new `Date` for the prior date. /// /// Panics when `self` is the first representable date. + #[deprecated(since = "0.4.23", note = "Use pred_opt() instead")] #[inline] + #[must_use] pub fn pred(&self) -> Date { self.pred_opt().expect("out of bound") } @@ -198,18 +230,21 @@ impl Date { /// /// Returns `None` when `self` is the first representable date. #[inline] + #[must_use] pub fn pred_opt(&self) -> Option> { self.date.pred_opt().map(|date| Date::from_utc(date, self.offset.clone())) } /// Retrieves an associated offset from UTC. #[inline] + #[must_use] pub fn offset(&self) -> &Tz::Offset { &self.offset } /// Retrieves an associated time zone. #[inline] + #[must_use] pub fn timezone(&self) -> Tz { TimeZone::from_offset(&self.offset) } @@ -217,40 +252,45 @@ impl Date { /// Changes the associated time zone. /// This does not change the actual `Date` (but will change the string representation). #[inline] + #[must_use] pub fn with_timezone(&self, tz: &Tz2) -> Date { tz.from_utc_date(&self.date) } - /// Adds given `Duration` to the current date. + /// Adds given `TimeDelta` to the current date. /// /// Returns `None` when it will result in overflow. #[inline] - pub fn checked_add_signed(self, rhs: OldDuration) -> Option> { - let date = try_opt!(self.date.checked_add_signed(rhs)); - Some(Date { date: date, offset: self.offset }) + #[must_use] + pub fn checked_add_signed(self, rhs: TimeDelta) -> Option> { + let date = self.date.checked_add_signed(rhs)?; + Some(Date { date, offset: self.offset }) } - /// Subtracts given `Duration` from the current date. + /// Subtracts given `TimeDelta` from the current date. /// /// Returns `None` when it will result in overflow. #[inline] - pub fn checked_sub_signed(self, rhs: OldDuration) -> Option> { - let date = try_opt!(self.date.checked_sub_signed(rhs)); - Some(Date { date: date, offset: self.offset }) + #[must_use] + pub fn checked_sub_signed(self, rhs: TimeDelta) -> Option> { + let date = self.date.checked_sub_signed(rhs)?; + Some(Date { date, offset: self.offset }) } /// Subtracts another `Date` from the current date. - /// Returns a `Duration` of integral numbers. + /// Returns a `TimeDelta` of integral numbers. /// /// This does not overflow or underflow at all, - /// as all possible output fits in the range of `Duration`. + /// as all possible output fits in the range of `TimeDelta`. #[inline] - pub fn signed_duration_since(self, rhs: Date) -> OldDuration { + #[must_use] + pub fn signed_duration_since(self, rhs: Date) -> TimeDelta { self.date.signed_duration_since(rhs.date) } /// Returns a view to the naive UTC date. #[inline] + #[must_use] pub fn naive_utc(&self) -> NaiveDate { self.date } @@ -261,9 +301,21 @@ impl Date { /// because the offset is restricted to never exceed one day, /// but provided for the consistency. #[inline] + #[must_use] pub fn naive_local(&self) -> NaiveDate { self.date } + + /// Returns the number of whole years from the given `base` until `self`. + #[must_use] + pub fn years_since(&self, base: Self) -> Option { + self.date.years_since(base.date) + } + + /// The minimum possible `Date`. + pub const MIN_UTC: Date = Date { date: NaiveDate::MIN, offset: Utc }; + /// The maximum possible `Date`. + pub const MAX_UTC: Date = Date { date: NaiveDate::MAX, offset: Utc }; } /// Maps the local date to other date with given conversion function. @@ -279,8 +331,9 @@ where Tz::Offset: fmt::Display, { /// Formats the date with the specified formatting items. - #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg(feature = "alloc")] #[inline] + #[must_use] pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat where I: Iterator + Clone, @@ -290,17 +343,19 @@ where } /// Formats the date with the specified format string. - /// See the [`format::strftime` module](./format/strftime/index.html) + /// See the [`crate::format::strftime`] module /// on the supported escape sequences. - #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg(feature = "alloc")] #[inline] + #[must_use] pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { self.format_with_items(StrftimeItems::new(fmt)) } /// Formats the date with the specified formatting items and locale. - #[cfg(feature = "unstable-locales")] + #[cfg(all(feature = "unstable-locales", feature = "alloc"))] #[inline] + #[must_use] pub fn format_localized_with_items<'a, I, B>( &self, items: I, @@ -320,10 +375,11 @@ where } /// Formats the date with the specified format string and locale. - /// See the [`format::strftime` module](./format/strftime/index.html) + /// See the [`crate::format::strftime`] module /// on the supported escape sequences. - #[cfg(feature = "unstable-locales")] + #[cfg(all(feature = "unstable-locales", feature = "alloc"))] #[inline] + #[must_use] pub fn format_localized<'a>( &self, fmt: &'a str, @@ -421,7 +477,7 @@ impl Eq for Date {} impl PartialOrd for Date { fn partial_cmp(&self, other: &Date) -> Option { - self.date.partial_cmp(&other.date) + Some(self.cmp(other)) } } @@ -437,36 +493,51 @@ impl hash::Hash for Date { } } -impl Add for Date { +impl Add for Date { type Output = Date; #[inline] - fn add(self, rhs: OldDuration) -> Date { - self.checked_add_signed(rhs).expect("`Date + Duration` overflowed") + fn add(self, rhs: TimeDelta) -> Date { + self.checked_add_signed(rhs).expect("`Date + TimeDelta` overflowed") } } -impl Sub for Date { +impl AddAssign for Date { + #[inline] + fn add_assign(&mut self, rhs: TimeDelta) { + self.date = self.date.checked_add_signed(rhs).expect("`Date + TimeDelta` overflowed"); + } +} + +impl Sub for Date { type Output = Date; #[inline] - fn sub(self, rhs: OldDuration) -> Date { - self.checked_sub_signed(rhs).expect("`Date - Duration` overflowed") + fn sub(self, rhs: TimeDelta) -> Date { + self.checked_sub_signed(rhs).expect("`Date - TimeDelta` overflowed") + } +} + +impl SubAssign for Date { + #[inline] + fn sub_assign(&mut self, rhs: TimeDelta) { + self.date = self.date.checked_sub_signed(rhs).expect("`Date - TimeDelta` overflowed"); } } impl Sub> for Date { - type Output = OldDuration; + type Output = TimeDelta; #[inline] - fn sub(self, rhs: Date) -> OldDuration { + fn sub(self, rhs: Date) -> TimeDelta { self.signed_duration_since(rhs) } } impl fmt::Debug for Date { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:?}{:?}", self.naive_local(), self.offset) + self.naive_local().fmt(f)?; + self.offset.fmt(f) } } @@ -475,6 +546,118 @@ where Tz::Offset: fmt::Display, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}{}", self.naive_local(), self.offset) + self.naive_local().fmt(f)?; + self.offset.fmt(f) + } +} + +// Note that implementation of Arbitrary cannot be automatically derived for Date, due to +// the nontrivial bound ::Offset: Arbitrary. +#[cfg(all(feature = "arbitrary", feature = "std"))] +impl<'a, Tz> arbitrary::Arbitrary<'a> for Date +where + Tz: TimeZone, + ::Offset: arbitrary::Arbitrary<'a>, +{ + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result> { + let date = NaiveDate::arbitrary(u)?; + let offset = ::Offset::arbitrary(u)?; + Ok(Date::from_utc(date, offset)) + } +} + +#[cfg(test)] +mod tests { + use super::Date; + + use crate::{FixedOffset, NaiveDate, TimeDelta, Utc}; + + #[cfg(feature = "clock")] + use crate::offset::{Local, TimeZone}; + + #[test] + #[cfg(feature = "clock")] + fn test_years_elapsed() { + const WEEKS_PER_YEAR: f32 = 52.1775; + + // This is always at least one year because 1 year = 52.1775 weeks. + let one_year_ago = Utc::today() - TimeDelta::weeks((WEEKS_PER_YEAR * 1.5).ceil() as i64); + // A bit more than 2 years. + let two_year_ago = Utc::today() - TimeDelta::weeks((WEEKS_PER_YEAR * 2.5).ceil() as i64); + + assert_eq!(Utc::today().years_since(one_year_ago), Some(1)); + assert_eq!(Utc::today().years_since(two_year_ago), Some(2)); + + // If the given DateTime is later than now, the function will always return 0. + let future = Utc::today() + TimeDelta::weeks(12); + assert_eq!(Utc::today().years_since(future), None); + } + + #[test] + fn test_date_add_assign() { + let naivedate = NaiveDate::from_ymd_opt(2000, 1, 1).unwrap(); + let date = Date::::from_utc(naivedate, Utc); + let mut date_add = date; + + date_add += TimeDelta::days(5); + assert_eq!(date_add, date + TimeDelta::days(5)); + + let timezone = FixedOffset::east_opt(60 * 60).unwrap(); + let date = date.with_timezone(&timezone); + let date_add = date_add.with_timezone(&timezone); + + assert_eq!(date_add, date + TimeDelta::days(5)); + + let timezone = FixedOffset::west_opt(2 * 60 * 60).unwrap(); + let date = date.with_timezone(&timezone); + let date_add = date_add.with_timezone(&timezone); + + assert_eq!(date_add, date + TimeDelta::days(5)); + } + + #[test] + #[cfg(feature = "clock")] + fn test_date_add_assign_local() { + let naivedate = NaiveDate::from_ymd_opt(2000, 1, 1).unwrap(); + + let date = Local.from_utc_date(&naivedate); + let mut date_add = date; + + date_add += TimeDelta::days(5); + assert_eq!(date_add, date + TimeDelta::days(5)); + } + + #[test] + fn test_date_sub_assign() { + let naivedate = NaiveDate::from_ymd_opt(2000, 1, 1).unwrap(); + let date = Date::::from_utc(naivedate, Utc); + let mut date_sub = date; + + date_sub -= TimeDelta::days(5); + assert_eq!(date_sub, date - TimeDelta::days(5)); + + let timezone = FixedOffset::east_opt(60 * 60).unwrap(); + let date = date.with_timezone(&timezone); + let date_sub = date_sub.with_timezone(&timezone); + + assert_eq!(date_sub, date - TimeDelta::days(5)); + + let timezone = FixedOffset::west_opt(2 * 60 * 60).unwrap(); + let date = date.with_timezone(&timezone); + let date_sub = date_sub.with_timezone(&timezone); + + assert_eq!(date_sub, date - TimeDelta::days(5)); + } + + #[test] + #[cfg(feature = "clock")] + fn test_date_sub_assign_local() { + let naivedate = NaiveDate::from_ymd_opt(2000, 1, 1).unwrap(); + + let date = Local.from_utc_date(&naivedate); + let mut date_sub = date; + + date_sub -= TimeDelta::days(5); + assert_eq!(date_sub, date - TimeDelta::days(5)); } } diff --git a/third_party/rust/chrono/src/datetime.rs b/third_party/rust/chrono/src/datetime.rs deleted file mode 100644 index ecd4642507e..00000000000 --- a/third_party/rust/chrono/src/datetime.rs +++ /dev/null @@ -1,2589 +0,0 @@ -// This is a part of Chrono. -// See README.md and LICENSE.txt for details. - -//! ISO 8601 date and time with time zone. - -use core::cmp::Ordering; -use core::ops::{Add, Sub}; -use core::{fmt, hash, str}; -use oldtime::Duration as OldDuration; -#[cfg(any(feature = "std", test))] -use std::time::{SystemTime, UNIX_EPOCH}; - -#[cfg(all(not(feature = "std"), feature = "alloc"))] -use alloc::string::{String, ToString}; -#[cfg(feature = "std")] -use std::string::ToString; - -#[cfg(any(feature = "alloc", feature = "std", test))] -use core::borrow::Borrow; -#[cfg(any(feature = "alloc", feature = "std", test))] -use format::DelayedFormat; -#[cfg(feature = "unstable-locales")] -use format::Locale; -use format::{parse, ParseError, ParseResult, Parsed, StrftimeItems}; -use format::{Fixed, Item}; -use naive::{self, IsoWeek, NaiveDateTime, NaiveTime}; -#[cfg(feature = "clock")] -use offset::Local; -use offset::{FixedOffset, Offset, TimeZone, Utc}; -use Date; -use {Datelike, Timelike, Weekday}; - -/// Specific formatting options for seconds. This may be extended in the -/// future, so exhaustive matching in external code is not recommended. -/// -/// See the `TimeZone::to_rfc3339_opts` function for usage. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum SecondsFormat { - /// Format whole seconds only, with no decimal point nor subseconds. - Secs, - - /// Use fixed 3 subsecond digits. This corresponds to - /// [Fixed::Nanosecond3](format/enum.Fixed.html#variant.Nanosecond3). - Millis, - - /// Use fixed 6 subsecond digits. This corresponds to - /// [Fixed::Nanosecond6](format/enum.Fixed.html#variant.Nanosecond6). - Micros, - - /// Use fixed 9 subsecond digits. This corresponds to - /// [Fixed::Nanosecond9](format/enum.Fixed.html#variant.Nanosecond9). - Nanos, - - /// Automatically select one of `Secs`, `Millis`, `Micros`, or `Nanos` to - /// display all available non-zero sub-second digits. This corresponds to - /// [Fixed::Nanosecond](format/enum.Fixed.html#variant.Nanosecond). - AutoSi, - - // Do not match against this. - #[doc(hidden)] - __NonExhaustive, -} - -/// ISO 8601 combined date and time with time zone. -/// -/// There are some constructors implemented here (the `from_*` methods), but -/// the general-purpose constructors are all via the methods on the -/// [`TimeZone`](./offset/trait.TimeZone.html) implementations. -#[derive(Clone)] -pub struct DateTime { - datetime: NaiveDateTime, - offset: Tz::Offset, -} - -/// The minimum possible `DateTime`. -pub const MIN_DATETIME: DateTime = DateTime { datetime: naive::MIN_DATETIME, offset: Utc }; -/// The maximum possible `DateTime`. -pub const MAX_DATETIME: DateTime = DateTime { datetime: naive::MAX_DATETIME, offset: Utc }; - -impl DateTime { - /// Makes a new `DateTime` with given *UTC* datetime and offset. - /// The local datetime should be constructed via the `TimeZone` trait. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{DateTime, TimeZone, NaiveDateTime, Utc}; - /// - /// let dt = DateTime::::from_utc(NaiveDateTime::from_timestamp(61, 0), Utc); - /// assert_eq!(Utc.timestamp(61, 0), dt); - /// ~~~~ - // - // note: this constructor is purposely not named to `new` to discourage the direct usage. - #[inline] - pub fn from_utc(datetime: NaiveDateTime, offset: Tz::Offset) -> DateTime { - DateTime { datetime: datetime, offset: offset } - } - - /// Retrieves a date component. - #[inline] - pub fn date(&self) -> Date { - Date::from_utc(self.naive_local().date(), self.offset.clone()) - } - - /// Retrieves a time component. - /// Unlike `date`, this is not associated to the time zone. - #[inline] - pub fn time(&self) -> NaiveTime { - self.datetime.time() + self.offset.fix() - } - - /// Returns the number of non-leap seconds since January 1, 1970 0:00:00 UTC - /// (aka "UNIX timestamp"). - #[inline] - pub fn timestamp(&self) -> i64 { - self.datetime.timestamp() - } - - /// Returns the number of non-leap-milliseconds since January 1, 1970 UTC - /// - /// Note that this does reduce the number of years that can be represented - /// from ~584 Billion to ~584 Million. (If this is a problem, please file - /// an issue to let me know what domain needs millisecond precision over - /// billions of years, I'm curious.) - /// - /// # Example - /// - /// ~~~~ - /// use chrono::Utc; - /// use chrono::TimeZone; - /// - /// let dt = Utc.ymd(1970, 1, 1).and_hms_milli(0, 0, 1, 444); - /// assert_eq!(dt.timestamp_millis(), 1_444); - /// - /// let dt = Utc.ymd(2001, 9, 9).and_hms_milli(1, 46, 40, 555); - /// assert_eq!(dt.timestamp_millis(), 1_000_000_000_555); - /// ~~~~ - #[inline] - pub fn timestamp_millis(&self) -> i64 { - self.datetime.timestamp_millis() - } - - /// Returns the number of non-leap-nanoseconds since January 1, 1970 UTC - /// - /// Note that this does reduce the number of years that can be represented - /// from ~584 Billion to ~584. (If this is a problem, please file - /// an issue to let me know what domain needs nanosecond precision over - /// millennia, I'm curious.) - /// - /// # Example - /// - /// ~~~~ - /// use chrono::Utc; - /// use chrono::TimeZone; - /// - /// let dt = Utc.ymd(1970, 1, 1).and_hms_nano(0, 0, 1, 444); - /// assert_eq!(dt.timestamp_nanos(), 1_000_000_444); - /// - /// let dt = Utc.ymd(2001, 9, 9).and_hms_nano(1, 46, 40, 555); - /// assert_eq!(dt.timestamp_nanos(), 1_000_000_000_000_000_555); - /// ~~~~ - #[inline] - pub fn timestamp_nanos(&self) -> i64 { - self.datetime.timestamp_nanos() - } - - /// Returns the number of milliseconds since the last second boundary - /// - /// warning: in event of a leap second, this may exceed 999 - /// - /// note: this is not the number of milliseconds since January 1, 1970 0:00:00 UTC - #[inline] - pub fn timestamp_subsec_millis(&self) -> u32 { - self.datetime.timestamp_subsec_millis() - } - - /// Returns the number of microseconds since the last second boundary - /// - /// warning: in event of a leap second, this may exceed 999_999 - /// - /// note: this is not the number of microseconds since January 1, 1970 0:00:00 UTC - #[inline] - pub fn timestamp_subsec_micros(&self) -> u32 { - self.datetime.timestamp_subsec_micros() - } - - /// Returns the number of nanoseconds since the last second boundary - /// - /// warning: in event of a leap second, this may exceed 999_999_999 - /// - /// note: this is not the number of nanoseconds since January 1, 1970 0:00:00 UTC - #[inline] - pub fn timestamp_subsec_nanos(&self) -> u32 { - self.datetime.timestamp_subsec_nanos() - } - - /// Retrieves an associated offset from UTC. - #[inline] - pub fn offset(&self) -> &Tz::Offset { - &self.offset - } - - /// Retrieves an associated time zone. - #[inline] - pub fn timezone(&self) -> Tz { - TimeZone::from_offset(&self.offset) - } - - /// Changes the associated time zone. - /// This does not change the actual `DateTime` (but will change the string representation). - #[inline] - pub fn with_timezone(&self, tz: &Tz2) -> DateTime { - tz.from_utc_datetime(&self.datetime) - } - - /// Adds given `Duration` to the current date and time. - /// - /// Returns `None` when it will result in overflow. - #[inline] - pub fn checked_add_signed(self, rhs: OldDuration) -> Option> { - let datetime = try_opt!(self.datetime.checked_add_signed(rhs)); - let tz = self.timezone(); - Some(tz.from_utc_datetime(&datetime)) - } - - /// Subtracts given `Duration` from the current date and time. - /// - /// Returns `None` when it will result in overflow. - #[inline] - pub fn checked_sub_signed(self, rhs: OldDuration) -> Option> { - let datetime = try_opt!(self.datetime.checked_sub_signed(rhs)); - let tz = self.timezone(); - Some(tz.from_utc_datetime(&datetime)) - } - - /// Subtracts another `DateTime` from the current date and time. - /// This does not overflow or underflow at all. - #[inline] - pub fn signed_duration_since(self, rhs: DateTime) -> OldDuration { - self.datetime.signed_duration_since(rhs.datetime) - } - - /// Returns a view to the naive UTC datetime. - #[inline] - pub fn naive_utc(&self) -> NaiveDateTime { - self.datetime - } - - /// Returns a view to the naive local datetime. - #[inline] - pub fn naive_local(&self) -> NaiveDateTime { - self.datetime + self.offset.fix() - } -} - -/// Convert a `DateTime` instance into a `DateTime` instance. -impl From> for DateTime { - /// Convert this `DateTime` instance into a `DateTime` instance. - /// - /// Conversion is done via [`DateTime::with_timezone`]. Note that the converted value returned by - /// this will be created with a fixed timezone offset of 0. - fn from(src: DateTime) -> Self { - src.with_timezone(&FixedOffset::east(0)) - } -} - -/// Convert a `DateTime` instance into a `DateTime` instance. -#[cfg(feature = "clock")] -impl From> for DateTime { - /// Convert this `DateTime` instance into a `DateTime` instance. - /// - /// Conversion is performed via [`DateTime::with_timezone`], accounting for the difference in timezones. - fn from(src: DateTime) -> Self { - src.with_timezone(&Local) - } -} - -/// Convert a `DateTime` instance into a `DateTime` instance. -impl From> for DateTime { - /// Convert this `DateTime` instance into a `DateTime` instance. - /// - /// Conversion is performed via [`DateTime::with_timezone`], accounting for the timezone - /// difference. - fn from(src: DateTime) -> Self { - src.with_timezone(&Utc) - } -} - -/// Convert a `DateTime` instance into a `DateTime` instance. -#[cfg(feature = "clock")] -impl From> for DateTime { - /// Convert this `DateTime` instance into a `DateTime` instance. - /// - /// Conversion is performed via [`DateTime::with_timezone`]. Returns the equivalent value in local - /// time. - fn from(src: DateTime) -> Self { - src.with_timezone(&Local) - } -} - -/// Convert a `DateTime` instance into a `DateTime` instance. -#[cfg(feature = "clock")] -impl From> for DateTime { - /// Convert this `DateTime` instance into a `DateTime` instance. - /// - /// Conversion is performed via [`DateTime::with_timezone`], accounting for the difference in - /// timezones. - fn from(src: DateTime) -> Self { - src.with_timezone(&Utc) - } -} - -/// Convert a `DateTime` instance into a `DateTime` instance. -#[cfg(feature = "clock")] -impl From> for DateTime { - /// Convert this `DateTime` instance into a `DateTime` instance. - /// - /// Conversion is performed via [`DateTime::with_timezone`]. Note that the converted value returned - /// by this will be created with a fixed timezone offset of 0. - fn from(src: DateTime) -> Self { - src.with_timezone(&FixedOffset::east(0)) - } -} - -/// Maps the local datetime to other datetime with given conversion function. -fn map_local(dt: &DateTime, mut f: F) -> Option> -where - F: FnMut(NaiveDateTime) -> Option, -{ - f(dt.naive_local()).and_then(|datetime| dt.timezone().from_local_datetime(&datetime).single()) -} - -impl DateTime { - /// Parses an RFC 2822 date and time string such as `Tue, 1 Jul 2003 10:52:37 +0200`, - /// then returns a new `DateTime` with a parsed `FixedOffset`. - /// - /// RFC 2822 is the internet message standard that specifices the - /// representation of times in HTTP and email headers. - /// - /// ``` - /// # use chrono::{DateTime, FixedOffset, TimeZone}; - /// assert_eq!( - /// DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 GMT").unwrap(), - /// FixedOffset::east(0).ymd(2015, 2, 18).and_hms(23, 16, 9) - /// ); - /// ``` - pub fn parse_from_rfc2822(s: &str) -> ParseResult> { - const ITEMS: &'static [Item<'static>] = &[Item::Fixed(Fixed::RFC2822)]; - let mut parsed = Parsed::new(); - parse(&mut parsed, s, ITEMS.iter())?; - parsed.to_datetime() - } - - /// Parses an RFC 3339 and ISO 8601 date and time string such as `1996-12-19T16:39:57-08:00`, - /// then returns a new `DateTime` with a parsed `FixedOffset`. - /// - /// Why isn't this named `parse_from_iso8601`? That's because ISO 8601 allows some freedom - /// over the syntax and RFC 3339 exercises that freedom to rigidly define a fixed format. - pub fn parse_from_rfc3339(s: &str) -> ParseResult> { - const ITEMS: &'static [Item<'static>] = &[Item::Fixed(Fixed::RFC3339)]; - let mut parsed = Parsed::new(); - parse(&mut parsed, s, ITEMS.iter())?; - parsed.to_datetime() - } - - /// Parses a string with the specified format string and - /// returns a new `DateTime` with a parsed `FixedOffset`. - /// See the [`format::strftime` module](./format/strftime/index.html) - /// on the supported escape sequences. - /// - /// See also `Offset::datetime_from_str` which gives a local `DateTime` on specific time zone. - /// - /// Note that this method *requires a timezone* in the string. See - /// [`NaiveDateTime::parse_from_str`](./naive/struct.NaiveDateTime.html#method.parse_from_str) - /// for a version that does not require a timezone in the to-be-parsed str. - /// - /// # Example - /// - /// ```rust - /// use chrono::{DateTime, FixedOffset, TimeZone}; - /// - /// let dt = DateTime::parse_from_str( - /// "1983 Apr 13 12:09:14.274 +0000", "%Y %b %d %H:%M:%S%.3f %z"); - /// assert_eq!(dt, Ok(FixedOffset::east(0).ymd(1983, 4, 13).and_hms_milli(12, 9, 14, 274))); - /// ``` - pub fn parse_from_str(s: &str, fmt: &str) -> ParseResult> { - let mut parsed = Parsed::new(); - parse(&mut parsed, s, StrftimeItems::new(fmt))?; - parsed.to_datetime() - } -} - -impl DateTime -where - Tz::Offset: fmt::Display, -{ - /// Returns an RFC 2822 date and time string such as `Tue, 1 Jul 2003 10:52:37 +0200`. - #[cfg(any(feature = "alloc", feature = "std", test))] - pub fn to_rfc2822(&self) -> String { - const ITEMS: &'static [Item<'static>] = &[Item::Fixed(Fixed::RFC2822)]; - self.format_with_items(ITEMS.iter()).to_string() - } - - /// Returns an RFC 3339 and ISO 8601 date and time string such as `1996-12-19T16:39:57-08:00`. - #[cfg(any(feature = "alloc", feature = "std", test))] - pub fn to_rfc3339(&self) -> String { - const ITEMS: &'static [Item<'static>] = &[Item::Fixed(Fixed::RFC3339)]; - self.format_with_items(ITEMS.iter()).to_string() - } - - /// Return an RFC 3339 and ISO 8601 date and time string with subseconds - /// formatted as per a `SecondsFormat`. If passed `use_z` true and the - /// timezone is UTC (offset 0), use 'Z', as per - /// [Fixed::TimezoneOffsetColonZ](format/enum.Fixed.html#variant.TimezoneOffsetColonZ). - /// If passed `use_z` false, use - /// [Fixed::TimezoneOffsetColon](format/enum.Fixed.html#variant.TimezoneOffsetColon). - /// - /// # Examples - /// - /// ```rust - /// # use chrono::{DateTime, FixedOffset, SecondsFormat, TimeZone, Utc}; - /// let dt = Utc.ymd(2018, 1, 26).and_hms_micro(18, 30, 9, 453_829); - /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Millis, false), - /// "2018-01-26T18:30:09.453+00:00"); - /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Millis, true), - /// "2018-01-26T18:30:09.453Z"); - /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Secs, true), - /// "2018-01-26T18:30:09Z"); - /// - /// let pst = FixedOffset::east(8 * 60 * 60); - /// let dt = pst.ymd(2018, 1, 26).and_hms_micro(10, 30, 9, 453_829); - /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Secs, true), - /// "2018-01-26T10:30:09+08:00"); - /// ``` - #[cfg(any(feature = "alloc", feature = "std", test))] - pub fn to_rfc3339_opts(&self, secform: SecondsFormat, use_z: bool) -> String { - use format::Numeric::*; - use format::Pad::Zero; - use SecondsFormat::*; - - debug_assert!(secform != __NonExhaustive, "Do not use __NonExhaustive!"); - - const PREFIX: &'static [Item<'static>] = &[ - Item::Numeric(Year, Zero), - Item::Literal("-"), - Item::Numeric(Month, Zero), - Item::Literal("-"), - Item::Numeric(Day, Zero), - Item::Literal("T"), - Item::Numeric(Hour, Zero), - Item::Literal(":"), - Item::Numeric(Minute, Zero), - Item::Literal(":"), - Item::Numeric(Second, Zero), - ]; - - let ssitem = match secform { - Secs => None, - Millis => Some(Item::Fixed(Fixed::Nanosecond3)), - Micros => Some(Item::Fixed(Fixed::Nanosecond6)), - Nanos => Some(Item::Fixed(Fixed::Nanosecond9)), - AutoSi => Some(Item::Fixed(Fixed::Nanosecond)), - __NonExhaustive => unreachable!(), - }; - - let tzitem = Item::Fixed(if use_z { - Fixed::TimezoneOffsetColonZ - } else { - Fixed::TimezoneOffsetColon - }); - - match ssitem { - None => self.format_with_items(PREFIX.iter().chain([tzitem].iter())).to_string(), - Some(s) => self.format_with_items(PREFIX.iter().chain([s, tzitem].iter())).to_string(), - } - } - - /// Formats the combined date and time with the specified formatting items. - #[cfg(any(feature = "alloc", feature = "std", test))] - #[inline] - pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat - where - I: Iterator + Clone, - B: Borrow>, - { - let local = self.naive_local(); - DelayedFormat::new_with_offset(Some(local.date()), Some(local.time()), &self.offset, items) - } - - /// Formats the combined date and time with the specified format string. - /// See the [`format::strftime` module](./format/strftime/index.html) - /// on the supported escape sequences. - #[cfg(any(feature = "alloc", feature = "std", test))] - #[inline] - pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { - self.format_with_items(StrftimeItems::new(fmt)) - } - - /// Formats the combined date and time with the specified formatting items and locale. - #[cfg(feature = "unstable-locales")] - #[inline] - pub fn format_localized_with_items<'a, I, B>( - &self, - items: I, - locale: Locale, - ) -> DelayedFormat - where - I: Iterator + Clone, - B: Borrow>, - { - let local = self.naive_local(); - DelayedFormat::new_with_offset_and_locale( - Some(local.date()), - Some(local.time()), - &self.offset, - items, - locale, - ) - } - - /// Formats the combined date and time with the specified format string and locale. - /// See the [`format::strftime` module](./format/strftime/index.html) - /// on the supported escape sequences. - #[cfg(feature = "unstable-locales")] - #[inline] - pub fn format_localized<'a>( - &self, - fmt: &'a str, - locale: Locale, - ) -> DelayedFormat> { - self.format_localized_with_items(StrftimeItems::new_with_locale(fmt, locale), locale) - } -} - -impl Datelike for DateTime { - #[inline] - fn year(&self) -> i32 { - self.naive_local().year() - } - #[inline] - fn month(&self) -> u32 { - self.naive_local().month() - } - #[inline] - fn month0(&self) -> u32 { - self.naive_local().month0() - } - #[inline] - fn day(&self) -> u32 { - self.naive_local().day() - } - #[inline] - fn day0(&self) -> u32 { - self.naive_local().day0() - } - #[inline] - fn ordinal(&self) -> u32 { - self.naive_local().ordinal() - } - #[inline] - fn ordinal0(&self) -> u32 { - self.naive_local().ordinal0() - } - #[inline] - fn weekday(&self) -> Weekday { - self.naive_local().weekday() - } - #[inline] - fn iso_week(&self) -> IsoWeek { - self.naive_local().iso_week() - } - - #[inline] - fn with_year(&self, year: i32) -> Option> { - map_local(self, |datetime| datetime.with_year(year)) - } - - #[inline] - fn with_month(&self, month: u32) -> Option> { - map_local(self, |datetime| datetime.with_month(month)) - } - - #[inline] - fn with_month0(&self, month0: u32) -> Option> { - map_local(self, |datetime| datetime.with_month0(month0)) - } - - #[inline] - fn with_day(&self, day: u32) -> Option> { - map_local(self, |datetime| datetime.with_day(day)) - } - - #[inline] - fn with_day0(&self, day0: u32) -> Option> { - map_local(self, |datetime| datetime.with_day0(day0)) - } - - #[inline] - fn with_ordinal(&self, ordinal: u32) -> Option> { - map_local(self, |datetime| datetime.with_ordinal(ordinal)) - } - - #[inline] - fn with_ordinal0(&self, ordinal0: u32) -> Option> { - map_local(self, |datetime| datetime.with_ordinal0(ordinal0)) - } -} - -impl Timelike for DateTime { - #[inline] - fn hour(&self) -> u32 { - self.naive_local().hour() - } - #[inline] - fn minute(&self) -> u32 { - self.naive_local().minute() - } - #[inline] - fn second(&self) -> u32 { - self.naive_local().second() - } - #[inline] - fn nanosecond(&self) -> u32 { - self.naive_local().nanosecond() - } - - #[inline] - fn with_hour(&self, hour: u32) -> Option> { - map_local(self, |datetime| datetime.with_hour(hour)) - } - - #[inline] - fn with_minute(&self, min: u32) -> Option> { - map_local(self, |datetime| datetime.with_minute(min)) - } - - #[inline] - fn with_second(&self, sec: u32) -> Option> { - map_local(self, |datetime| datetime.with_second(sec)) - } - - #[inline] - fn with_nanosecond(&self, nano: u32) -> Option> { - map_local(self, |datetime| datetime.with_nanosecond(nano)) - } -} - -// we need them as automatic impls cannot handle associated types -impl Copy for DateTime where ::Offset: Copy {} -unsafe impl Send for DateTime where ::Offset: Send {} - -impl PartialEq> for DateTime { - fn eq(&self, other: &DateTime) -> bool { - self.datetime == other.datetime - } -} - -impl Eq for DateTime {} - -impl PartialOrd> for DateTime { - /// Compare two DateTimes based on their true time, ignoring time zones - /// - /// # Example - /// - /// ``` - /// use chrono::prelude::*; - /// - /// let earlier = Utc.ymd(2015, 5, 15).and_hms(2, 0, 0).with_timezone(&FixedOffset::west(1 * 3600)); - /// let later = Utc.ymd(2015, 5, 15).and_hms(3, 0, 0).with_timezone(&FixedOffset::west(5 * 3600)); - /// - /// assert_eq!(earlier.to_string(), "2015-05-15 01:00:00 -01:00"); - /// assert_eq!(later.to_string(), "2015-05-14 22:00:00 -05:00"); - /// - /// assert!(later > earlier); - /// ``` - fn partial_cmp(&self, other: &DateTime) -> Option { - self.datetime.partial_cmp(&other.datetime) - } -} - -impl Ord for DateTime { - fn cmp(&self, other: &DateTime) -> Ordering { - self.datetime.cmp(&other.datetime) - } -} - -impl hash::Hash for DateTime { - fn hash(&self, state: &mut H) { - self.datetime.hash(state) - } -} - -impl Add for DateTime { - type Output = DateTime; - - #[inline] - fn add(self, rhs: OldDuration) -> DateTime { - self.checked_add_signed(rhs).expect("`DateTime + Duration` overflowed") - } -} - -impl Sub for DateTime { - type Output = DateTime; - - #[inline] - fn sub(self, rhs: OldDuration) -> DateTime { - self.checked_sub_signed(rhs).expect("`DateTime - Duration` overflowed") - } -} - -impl Sub> for DateTime { - type Output = OldDuration; - - #[inline] - fn sub(self, rhs: DateTime) -> OldDuration { - self.signed_duration_since(rhs) - } -} - -impl fmt::Debug for DateTime { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:?}{:?}", self.naive_local(), self.offset) - } -} - -impl fmt::Display for DateTime -where - Tz::Offset: fmt::Display, -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{} {}", self.naive_local(), self.offset) - } -} - -impl str::FromStr for DateTime { - type Err = ParseError; - - fn from_str(s: &str) -> ParseResult> { - s.parse::>().map(|dt| dt.with_timezone(&Utc)) - } -} - -#[cfg(feature = "clock")] -impl str::FromStr for DateTime { - type Err = ParseError; - - fn from_str(s: &str) -> ParseResult> { - s.parse::>().map(|dt| dt.with_timezone(&Local)) - } -} - -#[cfg(any(feature = "std", test))] -impl From for DateTime { - fn from(t: SystemTime) -> DateTime { - let (sec, nsec) = match t.duration_since(UNIX_EPOCH) { - Ok(dur) => (dur.as_secs() as i64, dur.subsec_nanos()), - Err(e) => { - // unlikely but should be handled - let dur = e.duration(); - let (sec, nsec) = (dur.as_secs() as i64, dur.subsec_nanos()); - if nsec == 0 { - (-sec, 0) - } else { - (-sec - 1, 1_000_000_000 - nsec) - } - } - }; - Utc.timestamp(sec, nsec) - } -} - -#[cfg(feature = "clock")] -impl From for DateTime { - fn from(t: SystemTime) -> DateTime { - DateTime::::from(t).with_timezone(&Local) - } -} - -#[cfg(any(feature = "std", test))] -impl From> for SystemTime { - fn from(dt: DateTime) -> SystemTime { - use std::time::Duration; - - let sec = dt.timestamp(); - let nsec = dt.timestamp_subsec_nanos(); - if sec < 0 { - // unlikely but should be handled - UNIX_EPOCH - Duration::new(-sec as u64, 0) + Duration::new(0, nsec) - } else { - UNIX_EPOCH + Duration::new(sec as u64, nsec) - } - } -} - -#[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind"))] -impl From for DateTime { - fn from(date: js_sys::Date) -> DateTime { - DateTime::::from(&date) - } -} - -#[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind"))] -impl From<&js_sys::Date> for DateTime { - fn from(date: &js_sys::Date) -> DateTime { - let millisecs_since_unix_epoch: u64 = date.get_time() as u64; - let secs = millisecs_since_unix_epoch / 1000; - let nanos = 1_000_000 * (millisecs_since_unix_epoch % 1000); - let naive = NaiveDateTime::from_timestamp(secs as i64, nanos as u32); - DateTime::from_utc(naive, Utc) - } -} - -#[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind"))] -impl From> for js_sys::Date { - fn from(date: DateTime) -> js_sys::Date { - let js_date = js_sys::Date::new_0(); - - js_date.set_utc_full_year_with_month_date( - date.year() as u32, - date.month0() as i32, - date.day() as i32, - ); - - js_date.set_utc_hours(date.hour()); - js_date.set_utc_minutes(date.minute()); - js_date.set_utc_seconds(date.second()); - - js_date - } -} - -#[test] -fn test_auto_conversion() { - let utc_dt = Utc.ymd(2018, 9, 5).and_hms(23, 58, 0); - let cdt_dt = FixedOffset::west(5 * 60 * 60).ymd(2018, 9, 5).and_hms(18, 58, 0); - let utc_dt2: DateTime = cdt_dt.into(); - assert_eq!(utc_dt, utc_dt2); -} - -#[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))] -fn test_encodable_json(to_string_utc: FUtc, to_string_fixed: FFixed) -where - FUtc: Fn(&DateTime) -> Result, - FFixed: Fn(&DateTime) -> Result, - E: ::core::fmt::Debug, -{ - assert_eq!( - to_string_utc(&Utc.ymd(2014, 7, 24).and_hms(12, 34, 6)).ok(), - Some(r#""2014-07-24T12:34:06Z""#.into()) - ); - - assert_eq!( - to_string_fixed(&FixedOffset::east(3660).ymd(2014, 7, 24).and_hms(12, 34, 6)).ok(), - Some(r#""2014-07-24T12:34:06+01:01""#.into()) - ); - assert_eq!( - to_string_fixed(&FixedOffset::east(3650).ymd(2014, 7, 24).and_hms(12, 34, 6)).ok(), - Some(r#""2014-07-24T12:34:06+01:00:50""#.into()) - ); -} - -#[cfg(all(test, feature = "clock", any(feature = "rustc-serialize", feature = "serde")))] -fn test_decodable_json( - utc_from_str: FUtc, - fixed_from_str: FFixed, - local_from_str: FLocal, -) where - FUtc: Fn(&str) -> Result, E>, - FFixed: Fn(&str) -> Result, E>, - FLocal: Fn(&str) -> Result, E>, - E: ::core::fmt::Debug, -{ - // should check against the offset as well (the normal DateTime comparison will ignore them) - fn norm(dt: &Option>) -> Option<(&DateTime, &Tz::Offset)> { - dt.as_ref().map(|dt| (dt, dt.offset())) - } - - assert_eq!( - norm(&utc_from_str(r#""2014-07-24T12:34:06Z""#).ok()), - norm(&Some(Utc.ymd(2014, 7, 24).and_hms(12, 34, 6))) - ); - assert_eq!( - norm(&utc_from_str(r#""2014-07-24T13:57:06+01:23""#).ok()), - norm(&Some(Utc.ymd(2014, 7, 24).and_hms(12, 34, 6))) - ); - - assert_eq!( - norm(&fixed_from_str(r#""2014-07-24T12:34:06Z""#).ok()), - norm(&Some(FixedOffset::east(0).ymd(2014, 7, 24).and_hms(12, 34, 6))) - ); - assert_eq!( - norm(&fixed_from_str(r#""2014-07-24T13:57:06+01:23""#).ok()), - norm(&Some(FixedOffset::east(60 * 60 + 23 * 60).ymd(2014, 7, 24).and_hms(13, 57, 6))) - ); - - // we don't know the exact local offset but we can check that - // the conversion didn't change the instant itself - assert_eq!( - local_from_str(r#""2014-07-24T12:34:06Z""#).expect("local shouuld parse"), - Utc.ymd(2014, 7, 24).and_hms(12, 34, 6) - ); - assert_eq!( - local_from_str(r#""2014-07-24T13:57:06+01:23""#).expect("local should parse with offset"), - Utc.ymd(2014, 7, 24).and_hms(12, 34, 6) - ); - - assert!(utc_from_str(r#""2014-07-32T12:34:06Z""#).is_err()); - assert!(fixed_from_str(r#""2014-07-32T12:34:06Z""#).is_err()); -} - -#[cfg(all(test, feature = "clock", feature = "rustc-serialize"))] -fn test_decodable_json_timestamps( - utc_from_str: FUtc, - fixed_from_str: FFixed, - local_from_str: FLocal, -) where - FUtc: Fn(&str) -> Result, E>, - FFixed: Fn(&str) -> Result, E>, - FLocal: Fn(&str) -> Result, E>, - E: ::core::fmt::Debug, -{ - fn norm(dt: &Option>) -> Option<(&DateTime, &Tz::Offset)> { - dt.as_ref().map(|dt| (dt, dt.offset())) - } - - assert_eq!( - norm(&utc_from_str("0").ok().map(DateTime::from)), - norm(&Some(Utc.ymd(1970, 1, 1).and_hms(0, 0, 0))) - ); - assert_eq!( - norm(&utc_from_str("-1").ok().map(DateTime::from)), - norm(&Some(Utc.ymd(1969, 12, 31).and_hms(23, 59, 59))) - ); - - assert_eq!( - norm(&fixed_from_str("0").ok().map(DateTime::from)), - norm(&Some(FixedOffset::east(0).ymd(1970, 1, 1).and_hms(0, 0, 0))) - ); - assert_eq!( - norm(&fixed_from_str("-1").ok().map(DateTime::from)), - norm(&Some(FixedOffset::east(0).ymd(1969, 12, 31).and_hms(23, 59, 59))) - ); - - assert_eq!( - *fixed_from_str("0").expect("0 timestamp should parse"), - Utc.ymd(1970, 1, 1).and_hms(0, 0, 0) - ); - assert_eq!( - *local_from_str("-1").expect("-1 timestamp should parse"), - Utc.ymd(1969, 12, 31).and_hms(23, 59, 59) - ); -} - -#[cfg(feature = "rustc-serialize")] -pub mod rustc_serialize { - use super::DateTime; - use core::fmt; - use core::ops::Deref; - #[cfg(feature = "clock")] - use offset::Local; - use offset::{FixedOffset, LocalResult, TimeZone, Utc}; - use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; - - impl Encodable for DateTime { - fn encode(&self, s: &mut S) -> Result<(), S::Error> { - format!("{:?}", self).encode(s) - } - } - - // lik? function to convert a LocalResult into a serde-ish Result - fn from(me: LocalResult, d: &mut D) -> Result - where - D: Decoder, - T: fmt::Display, - { - match me { - LocalResult::None => Err(d.error("value is not a legal timestamp")), - LocalResult::Ambiguous(..) => Err(d.error("value is an ambiguous timestamp")), - LocalResult::Single(val) => Ok(val), - } - } - - impl Decodable for DateTime { - fn decode(d: &mut D) -> Result, D::Error> { - d.read_str()? - .parse::>() - .map_err(|_| d.error("invalid date and time")) - } - } - - #[allow(deprecated)] - impl Decodable for TsSeconds { - #[allow(deprecated)] - fn decode(d: &mut D) -> Result, D::Error> { - from(FixedOffset::east(0).timestamp_opt(d.read_i64()?, 0), d).map(TsSeconds) - } - } - - impl Decodable for DateTime { - fn decode(d: &mut D) -> Result, D::Error> { - d.read_str()? - .parse::>() - .map(|dt| dt.with_timezone(&Utc)) - .map_err(|_| d.error("invalid date and time")) - } - } - - /// A `DateTime` that can be deserialized from a timestamp - /// - /// A timestamp here is seconds since the epoch - #[derive(Debug)] - pub struct TsSeconds(DateTime); - - #[allow(deprecated)] - impl From> for DateTime { - /// Pull the inner DateTime out - #[allow(deprecated)] - fn from(obj: TsSeconds) -> DateTime { - obj.0 - } - } - - #[allow(deprecated)] - impl Deref for TsSeconds { - type Target = DateTime; - - fn deref(&self) -> &Self::Target { - &self.0 - } - } - - #[allow(deprecated)] - impl Decodable for TsSeconds { - fn decode(d: &mut D) -> Result, D::Error> { - from(Utc.timestamp_opt(d.read_i64()?, 0), d).map(TsSeconds) - } - } - - #[cfg(feature = "clock")] - impl Decodable for DateTime { - fn decode(d: &mut D) -> Result, D::Error> { - match d.read_str()?.parse::>() { - Ok(dt) => Ok(dt.with_timezone(&Local)), - Err(_) => Err(d.error("invalid date and time")), - } - } - } - - #[cfg(feature = "clock")] - #[allow(deprecated)] - impl Decodable for TsSeconds { - #[allow(deprecated)] - fn decode(d: &mut D) -> Result, D::Error> { - from(Utc.timestamp_opt(d.read_i64()?, 0), d) - .map(|dt| TsSeconds(dt.with_timezone(&Local))) - } - } - - #[cfg(test)] - use rustc_serialize::json; - - #[test] - fn test_encodable() { - super::test_encodable_json(json::encode, json::encode); - } - - #[cfg(feature = "clock")] - #[test] - fn test_decodable() { - super::test_decodable_json(json::decode, json::decode, json::decode); - } - - #[cfg(feature = "clock")] - #[test] - fn test_decodable_timestamps() { - super::test_decodable_json_timestamps(json::decode, json::decode, json::decode); - } -} - -/// documented at re-export site -#[cfg(feature = "serde")] -pub mod serde { - use super::DateTime; - use core::fmt; - #[cfg(feature = "clock")] - use offset::Local; - use offset::{FixedOffset, LocalResult, TimeZone, Utc}; - use serdelib::{de, ser}; - use {ne_timestamp, SerdeError}; - - #[doc(hidden)] - #[derive(Debug)] - pub struct SecondsTimestampVisitor; - - #[doc(hidden)] - #[derive(Debug)] - pub struct NanoSecondsTimestampVisitor; - - #[doc(hidden)] - #[derive(Debug)] - pub struct MilliSecondsTimestampVisitor; - - // lik? function to convert a LocalResult into a serde-ish Result - fn serde_from(me: LocalResult, ts: &V) -> Result - where - E: de::Error, - V: fmt::Display, - T: fmt::Display, - { - match me { - LocalResult::None => Err(E::custom(ne_timestamp(ts))), - LocalResult::Ambiguous(min, max) => { - Err(E::custom(SerdeError::Ambiguous { timestamp: ts, min: min, max: max })) - } - LocalResult::Single(val) => Ok(val), - } - } - - /// Ser/de to/from timestamps in nanoseconds - /// - /// Intended for use with `serde`'s `with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{TimeZone, DateTime, Utc}; - /// use chrono::serde::ts_nanoseconds; - /// #[derive(Deserialize, Serialize)] - /// struct S { - /// #[serde(with = "ts_nanoseconds")] - /// time: DateTime - /// } - /// - /// # fn example() -> Result { - /// let time = Utc.ymd(2018, 5, 17).and_hms_nano(02, 04, 59, 918355733); - /// let my_s = S { - /// time: time.clone(), - /// }; - /// - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); - /// let my_s: S = serde_json::from_str(&as_string)?; - /// assert_eq!(my_s.time, time); - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub mod ts_nanoseconds { - use core::fmt; - use serdelib::{de, ser}; - - use offset::TimeZone; - use {DateTime, Utc}; - - use super::{serde_from, NanoSecondsTimestampVisitor}; - - /// Serialize a UTC datetime into an integer number of nanoseconds since the epoch - /// - /// Intended for use with `serde`s `serialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{TimeZone, DateTime, Utc}; - /// use chrono::serde::ts_nanoseconds::serialize as to_nano_ts; - /// #[derive(Serialize)] - /// struct S { - /// #[serde(serialize_with = "to_nano_ts")] - /// time: DateTime - /// } - /// - /// # fn example() -> Result { - /// let my_s = S { - /// time: Utc.ymd(2018, 5, 17).and_hms_nano(02, 04, 59, 918355733), - /// }; - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); - /// # Ok(as_string) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn serialize(dt: &DateTime, serializer: S) -> Result - where - S: ser::Serializer, - { - serializer.serialize_i64(dt.timestamp_nanos()) - } - - /// Deserialize a `DateTime` from a nanosecond timestamp - /// - /// Intended for use with `serde`s `deserialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{DateTime, Utc}; - /// use chrono::serde::ts_nanoseconds::deserialize as from_nano_ts; - /// #[derive(Deserialize)] - /// struct S { - /// #[serde(deserialize_with = "from_nano_ts")] - /// time: DateTime - /// } - /// - /// # fn example() -> Result { - /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355733 }"#)?; - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn deserialize<'de, D>(d: D) -> Result, D::Error> - where - D: de::Deserializer<'de>, - { - Ok(d.deserialize_i64(NanoSecondsTimestampVisitor)?) - } - - impl<'de> de::Visitor<'de> for NanoSecondsTimestampVisitor { - type Value = DateTime; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - write!(formatter, "a unix timestamp in nanoseconds") - } - - /// Deserialize a timestamp in nanoseconds since the epoch - fn visit_i64(self, value: i64) -> Result, E> - where - E: de::Error, - { - serde_from( - Utc.timestamp_opt(value / 1_000_000_000, (value % 1_000_000_000) as u32), - &value, - ) - } - - /// Deserialize a timestamp in nanoseconds since the epoch - fn visit_u64(self, value: u64) -> Result, E> - where - E: de::Error, - { - serde_from( - Utc.timestamp_opt( - (value / 1_000_000_000) as i64, - (value % 1_000_000_000) as u32, - ), - &value, - ) - } - } - } - - /// Ser/de to/from optional timestamps in nanoseconds - /// - /// Intended for use with `serde`'s `with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{TimeZone, DateTime, Utc}; - /// use chrono::serde::ts_nanoseconds_option; - /// #[derive(Deserialize, Serialize)] - /// struct S { - /// #[serde(with = "ts_nanoseconds_option")] - /// time: Option> - /// } - /// - /// # fn example() -> Result { - /// let time = Some(Utc.ymd(2018, 5, 17).and_hms_nano(02, 04, 59, 918355733)); - /// let my_s = S { - /// time: time.clone(), - /// }; - /// - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); - /// let my_s: S = serde_json::from_str(&as_string)?; - /// assert_eq!(my_s.time, time); - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub mod ts_nanoseconds_option { - use core::fmt; - use serdelib::{de, ser}; - - use {DateTime, Utc}; - - use super::NanoSecondsTimestampVisitor; - - /// Serialize a UTC datetime into an integer number of nanoseconds since the epoch or none - /// - /// Intended for use with `serde`s `serialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{TimeZone, DateTime, Utc}; - /// use chrono::serde::ts_nanoseconds_option::serialize as to_nano_tsopt; - /// #[derive(Serialize)] - /// struct S { - /// #[serde(serialize_with = "to_nano_tsopt")] - /// time: Option> - /// } - /// - /// # fn example() -> Result { - /// let my_s = S { - /// time: Some(Utc.ymd(2018, 5, 17).and_hms_nano(02, 04, 59, 918355733)), - /// }; - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); - /// # Ok(as_string) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn serialize(opt: &Option>, serializer: S) -> Result - where - S: ser::Serializer, - { - match *opt { - Some(ref dt) => serializer.serialize_some(&dt.timestamp_nanos()), - None => serializer.serialize_none(), - } - } - - /// Deserialize a `DateTime` from a nanosecond timestamp or none - /// - /// Intended for use with `serde`s `deserialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{DateTime, Utc}; - /// use chrono::serde::ts_nanoseconds_option::deserialize as from_nano_tsopt; - /// #[derive(Deserialize)] - /// struct S { - /// #[serde(deserialize_with = "from_nano_tsopt")] - /// time: Option> - /// } - /// - /// # fn example() -> Result { - /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355733 }"#)?; - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn deserialize<'de, D>(d: D) -> Result>, D::Error> - where - D: de::Deserializer<'de>, - { - Ok(d.deserialize_option(OptionNanoSecondsTimestampVisitor)?) - } - - struct OptionNanoSecondsTimestampVisitor; - - impl<'de> de::Visitor<'de> for OptionNanoSecondsTimestampVisitor { - type Value = Option>; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a unix timestamp in nanoseconds or none") - } - - /// Deserialize a timestamp in seconds since the epoch - fn visit_some(self, d: D) -> Result>, D::Error> - where - D: de::Deserializer<'de>, - { - d.deserialize_i64(NanoSecondsTimestampVisitor).map(Some) - } - - /// Deserialize a timestamp in seconds since the epoch - fn visit_none(self) -> Result>, E> - where - E: de::Error, - { - Ok(None) - } - - /// Deserialize a timestamp in seconds since the epoch - fn visit_unit(self) -> Result>, E> - where - E: de::Error, - { - Ok(None) - } - } - } - - /// Ser/de to/from timestamps in milliseconds - /// - /// Intended for use with `serde`s `with` attribute. - /// - /// # Example - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{TimeZone, DateTime, Utc}; - /// use chrono::serde::ts_milliseconds; - /// #[derive(Deserialize, Serialize)] - /// struct S { - /// #[serde(with = "ts_milliseconds")] - /// time: DateTime - /// } - /// - /// # fn example() -> Result { - /// let time = Utc.ymd(2018, 5, 17).and_hms_milli(02, 04, 59, 918); - /// let my_s = S { - /// time: time.clone(), - /// }; - /// - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1526522699918}"#); - /// let my_s: S = serde_json::from_str(&as_string)?; - /// assert_eq!(my_s.time, time); - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub mod ts_milliseconds { - use core::fmt; - use serdelib::{de, ser}; - - use offset::TimeZone; - use {DateTime, Utc}; - - use super::{serde_from, MilliSecondsTimestampVisitor}; - - /// Serialize a UTC datetime into an integer number of milliseconds since the epoch - /// - /// Intended for use with `serde`s `serialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{TimeZone, DateTime, Utc}; - /// use chrono::serde::ts_milliseconds::serialize as to_milli_ts; - /// #[derive(Serialize)] - /// struct S { - /// #[serde(serialize_with = "to_milli_ts")] - /// time: DateTime - /// } - /// - /// # fn example() -> Result { - /// let my_s = S { - /// time: Utc.ymd(2018, 5, 17).and_hms_milli(02, 04, 59, 918), - /// }; - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1526522699918}"#); - /// # Ok(as_string) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn serialize(dt: &DateTime, serializer: S) -> Result - where - S: ser::Serializer, - { - serializer.serialize_i64(dt.timestamp_millis()) - } - - /// Deserialize a `DateTime` from a millisecond timestamp - /// - /// Intended for use with `serde`s `deserialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{DateTime, Utc}; - /// use chrono::serde::ts_milliseconds::deserialize as from_milli_ts; - /// #[derive(Deserialize)] - /// struct S { - /// #[serde(deserialize_with = "from_milli_ts")] - /// time: DateTime - /// } - /// - /// # fn example() -> Result { - /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918 }"#)?; - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn deserialize<'de, D>(d: D) -> Result, D::Error> - where - D: de::Deserializer<'de>, - { - Ok(d.deserialize_i64(MilliSecondsTimestampVisitor).map(|dt| dt.with_timezone(&Utc))?) - } - - impl<'de> de::Visitor<'de> for MilliSecondsTimestampVisitor { - type Value = DateTime; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a unix timestamp in milliseconds") - } - - /// Deserialize a timestamp in milliseconds since the epoch - fn visit_i64(self, value: i64) -> Result, E> - where - E: de::Error, - { - serde_from( - Utc.timestamp_opt(value / 1000, ((value % 1000) * 1_000_000) as u32), - &value, - ) - } - - /// Deserialize a timestamp in milliseconds since the epoch - fn visit_u64(self, value: u64) -> Result, E> - where - E: de::Error, - { - serde_from( - Utc.timestamp_opt((value / 1000) as i64, ((value % 1000) * 1_000_000) as u32), - &value, - ) - } - } - } - - /// Ser/de to/from optional timestamps in milliseconds - /// - /// Intended for use with `serde`s `with` attribute. - /// - /// # Example - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{TimeZone, DateTime, Utc}; - /// use chrono::serde::ts_milliseconds_option; - /// #[derive(Deserialize, Serialize)] - /// struct S { - /// #[serde(with = "ts_milliseconds_option")] - /// time: Option> - /// } - /// - /// # fn example() -> Result { - /// let time = Some(Utc.ymd(2018, 5, 17).and_hms_milli(02, 04, 59, 918)); - /// let my_s = S { - /// time: time.clone(), - /// }; - /// - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1526522699918}"#); - /// let my_s: S = serde_json::from_str(&as_string)?; - /// assert_eq!(my_s.time, time); - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub mod ts_milliseconds_option { - use core::fmt; - use serdelib::{de, ser}; - - use {DateTime, Utc}; - - use super::MilliSecondsTimestampVisitor; - - /// Serialize a UTC datetime into an integer number of milliseconds since the epoch or none - /// - /// Intended for use with `serde`s `serialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{TimeZone, DateTime, Utc}; - /// use chrono::serde::ts_milliseconds_option::serialize as to_milli_tsopt; - /// #[derive(Serialize)] - /// struct S { - /// #[serde(serialize_with = "to_milli_tsopt")] - /// time: Option> - /// } - /// - /// # fn example() -> Result { - /// let my_s = S { - /// time: Some(Utc.ymd(2018, 5, 17).and_hms_milli(02, 04, 59, 918)), - /// }; - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1526522699918}"#); - /// # Ok(as_string) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn serialize(opt: &Option>, serializer: S) -> Result - where - S: ser::Serializer, - { - match *opt { - Some(ref dt) => serializer.serialize_some(&dt.timestamp_millis()), - None => serializer.serialize_none(), - } - } - - /// Deserialize a `DateTime` from a millisecond timestamp or none - /// - /// Intended for use with `serde`s `deserialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::prelude::*; - /// use chrono::serde::ts_milliseconds_option::deserialize as from_milli_tsopt; - /// - /// #[derive(Deserialize, PartialEq, Debug)] - /// #[serde(untagged)] - /// enum E { - /// V(T), - /// } - /// - /// #[derive(Deserialize, PartialEq, Debug)] - /// struct S { - /// #[serde(default, deserialize_with = "from_milli_tsopt")] - /// time: Option> - /// } - /// - /// # fn example() -> Result<(), serde_json::Error> { - /// let my_s: E = serde_json::from_str(r#"{ "time": 1526522699918 }"#)?; - /// assert_eq!(my_s, E::V(S { time: Some(Utc.timestamp(1526522699, 918000000)) })); - /// let s: E = serde_json::from_str(r#"{ "time": null }"#)?; - /// assert_eq!(s, E::V(S { time: None })); - /// let t: E = serde_json::from_str(r#"{}"#)?; - /// assert_eq!(t, E::V(S { time: None })); - /// # Ok(()) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn deserialize<'de, D>(d: D) -> Result>, D::Error> - where - D: de::Deserializer<'de>, - { - Ok(d.deserialize_option(OptionMilliSecondsTimestampVisitor) - .map(|opt| opt.map(|dt| dt.with_timezone(&Utc)))?) - } - - struct OptionMilliSecondsTimestampVisitor; - - impl<'de> de::Visitor<'de> for OptionMilliSecondsTimestampVisitor { - type Value = Option>; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a unix timestamp in milliseconds or none") - } - - /// Deserialize a timestamp in seconds since the epoch - fn visit_some(self, d: D) -> Result>, D::Error> - where - D: de::Deserializer<'de>, - { - d.deserialize_i64(MilliSecondsTimestampVisitor).map(Some) - } - - /// Deserialize a timestamp in seconds since the epoch - fn visit_none(self) -> Result>, E> - where - E: de::Error, - { - Ok(None) - } - - /// Deserialize a timestamp in seconds since the epoch - fn visit_unit(self) -> Result>, E> - where - E: de::Error, - { - Ok(None) - } - } - } - - /// Ser/de to/from timestamps in seconds - /// - /// Intended for use with `serde`'s `with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{TimeZone, DateTime, Utc}; - /// use chrono::serde::ts_seconds; - /// #[derive(Deserialize, Serialize)] - /// struct S { - /// #[serde(with = "ts_seconds")] - /// time: DateTime - /// } - /// - /// # fn example() -> Result { - /// let time = Utc.ymd(2015, 5, 15).and_hms(10, 0, 0); - /// let my_s = S { - /// time: time.clone(), - /// }; - /// - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1431684000}"#); - /// let my_s: S = serde_json::from_str(&as_string)?; - /// assert_eq!(my_s.time, time); - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub mod ts_seconds { - use core::fmt; - use serdelib::{de, ser}; - - use offset::TimeZone; - use {DateTime, Utc}; - - use super::{serde_from, SecondsTimestampVisitor}; - - /// Serialize a UTC datetime into an integer number of seconds since the epoch - /// - /// Intended for use with `serde`s `serialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{TimeZone, DateTime, Utc}; - /// use chrono::serde::ts_seconds::serialize as to_ts; - /// #[derive(Serialize)] - /// struct S { - /// #[serde(serialize_with = "to_ts")] - /// time: DateTime - /// } - /// - /// # fn example() -> Result { - /// let my_s = S { - /// time: Utc.ymd(2015, 5, 15).and_hms(10, 0, 0), - /// }; - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1431684000}"#); - /// # Ok(as_string) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn serialize(dt: &DateTime, serializer: S) -> Result - where - S: ser::Serializer, - { - serializer.serialize_i64(dt.timestamp()) - } - - /// Deserialize a `DateTime` from a seconds timestamp - /// - /// Intended for use with `serde`s `deserialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{DateTime, Utc}; - /// use chrono::serde::ts_seconds::deserialize as from_ts; - /// #[derive(Deserialize)] - /// struct S { - /// #[serde(deserialize_with = "from_ts")] - /// time: DateTime - /// } - /// - /// # fn example() -> Result { - /// let my_s: S = serde_json::from_str(r#"{ "time": 1431684000 }"#)?; - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn deserialize<'de, D>(d: D) -> Result, D::Error> - where - D: de::Deserializer<'de>, - { - Ok(d.deserialize_i64(SecondsTimestampVisitor)?) - } - - impl<'de> de::Visitor<'de> for SecondsTimestampVisitor { - type Value = DateTime; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a unix timestamp in seconds") - } - - /// Deserialize a timestamp in seconds since the epoch - fn visit_i64(self, value: i64) -> Result, E> - where - E: de::Error, - { - serde_from(Utc.timestamp_opt(value, 0), &value) - } - - /// Deserialize a timestamp in seconds since the epoch - fn visit_u64(self, value: u64) -> Result, E> - where - E: de::Error, - { - serde_from(Utc.timestamp_opt(value as i64, 0), &value) - } - } - } - - /// Ser/de to/from optional timestamps in seconds - /// - /// Intended for use with `serde`'s `with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{TimeZone, DateTime, Utc}; - /// use chrono::serde::ts_seconds_option; - /// #[derive(Deserialize, Serialize)] - /// struct S { - /// #[serde(with = "ts_seconds_option")] - /// time: Option> - /// } - /// - /// # fn example() -> Result { - /// let time = Some(Utc.ymd(2015, 5, 15).and_hms(10, 0, 0)); - /// let my_s = S { - /// time: time.clone(), - /// }; - /// - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1431684000}"#); - /// let my_s: S = serde_json::from_str(&as_string)?; - /// assert_eq!(my_s.time, time); - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub mod ts_seconds_option { - use core::fmt; - use serdelib::{de, ser}; - - use {DateTime, Utc}; - - use super::SecondsTimestampVisitor; - - /// Serialize a UTC datetime into an integer number of seconds since the epoch or none - /// - /// Intended for use with `serde`s `serialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{TimeZone, DateTime, Utc}; - /// use chrono::serde::ts_seconds_option::serialize as to_tsopt; - /// #[derive(Serialize)] - /// struct S { - /// #[serde(serialize_with = "to_tsopt")] - /// time: Option> - /// } - /// - /// # fn example() -> Result { - /// let my_s = S { - /// time: Some(Utc.ymd(2015, 5, 15).and_hms(10, 0, 0)), - /// }; - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1431684000}"#); - /// # Ok(as_string) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn serialize(opt: &Option>, serializer: S) -> Result - where - S: ser::Serializer, - { - match *opt { - Some(ref dt) => serializer.serialize_some(&dt.timestamp()), - None => serializer.serialize_none(), - } - } - - /// Deserialize a `DateTime` from a seconds timestamp or none - /// - /// Intended for use with `serde`s `deserialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate chrono; - /// # use chrono::{DateTime, Utc}; - /// use chrono::serde::ts_seconds_option::deserialize as from_tsopt; - /// #[derive(Deserialize)] - /// struct S { - /// #[serde(deserialize_with = "from_tsopt")] - /// time: Option> - /// } - /// - /// # fn example() -> Result { - /// let my_s: S = serde_json::from_str(r#"{ "time": 1431684000 }"#)?; - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn deserialize<'de, D>(d: D) -> Result>, D::Error> - where - D: de::Deserializer<'de>, - { - Ok(d.deserialize_option(OptionSecondsTimestampVisitor)?) - } - - struct OptionSecondsTimestampVisitor; - - impl<'de> de::Visitor<'de> for OptionSecondsTimestampVisitor { - type Value = Option>; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a unix timestamp in seconds or none") - } - - /// Deserialize a timestamp in seconds since the epoch - fn visit_some(self, d: D) -> Result>, D::Error> - where - D: de::Deserializer<'de>, - { - d.deserialize_i64(SecondsTimestampVisitor).map(Some) - } - - /// Deserialize a timestamp in seconds since the epoch - fn visit_none(self) -> Result>, E> - where - E: de::Error, - { - Ok(None) - } - - /// Deserialize a timestamp in seconds since the epoch - fn visit_unit(self) -> Result>, E> - where - E: de::Error, - { - Ok(None) - } - } - } - - impl ser::Serialize for DateTime { - /// Serialize into a rfc3339 time string - /// - /// See [the `serde` module](./serde/index.html) for alternate - /// serializations. - fn serialize(&self, serializer: S) -> Result - where - S: ser::Serializer, - { - struct FormatWrapped<'a, D: 'a> { - inner: &'a D, - } - - impl<'a, D: fmt::Debug> fmt::Display for FormatWrapped<'a, D> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.inner.fmt(f) - } - } - - // Debug formatting is correct RFC3339, and it allows Zulu. - serializer.collect_str(&FormatWrapped { inner: &self }) - } - } - - struct DateTimeVisitor; - - impl<'de> de::Visitor<'de> for DateTimeVisitor { - type Value = DateTime; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - write!(formatter, "a formatted date and time string or a unix timestamp") - } - - fn visit_str(self, value: &str) -> Result, E> - where - E: de::Error, - { - value.parse().map_err(|err: ::format::ParseError| E::custom(err)) - } - } - - /// Deserialize a value that optionally includes a timezone offset in its - /// string representation - /// - /// The value to be deserialized must be an rfc3339 string. - /// - /// See [the `serde` module](./serde/index.html) for alternate - /// deserialization formats. - impl<'de> de::Deserialize<'de> for DateTime { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - deserializer.deserialize_str(DateTimeVisitor) - } - } - - /// Deserialize into a UTC value - /// - /// The value to be deserialized must be an rfc3339 string. - /// - /// See [the `serde` module](./serde/index.html) for alternate - /// deserialization formats. - impl<'de> de::Deserialize<'de> for DateTime { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - deserializer.deserialize_str(DateTimeVisitor).map(|dt| dt.with_timezone(&Utc)) - } - } - - /// Deserialize a value that includes no timezone in its string - /// representation - /// - /// The value to be deserialized must be an rfc3339 string. - /// - /// See [the `serde` module](./serde/index.html) for alternate - /// serialization formats. - #[cfg(feature = "clock")] - impl<'de> de::Deserialize<'de> for DateTime { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - deserializer.deserialize_str(DateTimeVisitor).map(|dt| dt.with_timezone(&Local)) - } - } - - #[cfg(test)] - extern crate bincode; - #[cfg(test)] - extern crate serde_json; - - #[test] - fn test_serde_serialize() { - super::test_encodable_json(self::serde_json::to_string, self::serde_json::to_string); - } - - #[cfg(feature = "clock")] - #[test] - fn test_serde_deserialize() { - super::test_decodable_json( - |input| self::serde_json::from_str(&input), - |input| self::serde_json::from_str(&input), - |input| self::serde_json::from_str(&input), - ); - } - - #[test] - fn test_serde_bincode() { - // Bincode is relevant to test separately from JSON because - // it is not self-describing. - use self::bincode::{deserialize, serialize, Infinite}; - - let dt = Utc.ymd(2014, 7, 24).and_hms(12, 34, 6); - let encoded = serialize(&dt, Infinite).unwrap(); - let decoded: DateTime = deserialize(&encoded).unwrap(); - assert_eq!(dt, decoded); - assert_eq!(dt.offset(), decoded.offset()); - } -} - -#[cfg(test)] -mod tests { - use super::DateTime; - use naive::{NaiveDate, NaiveTime}; - #[cfg(feature = "clock")] - use offset::Local; - use offset::{FixedOffset, TimeZone, Utc}; - use oldtime::Duration; - use std::time::{SystemTime, UNIX_EPOCH}; - #[cfg(feature = "clock")] - use Datelike; - - #[test] - #[allow(non_snake_case)] - fn test_datetime_offset() { - let Est = FixedOffset::west(5 * 60 * 60); - let Edt = FixedOffset::west(4 * 60 * 60); - let Kst = FixedOffset::east(9 * 60 * 60); - - assert_eq!(format!("{}", Utc.ymd(2014, 5, 6).and_hms(7, 8, 9)), "2014-05-06 07:08:09 UTC"); - assert_eq!( - format!("{}", Edt.ymd(2014, 5, 6).and_hms(7, 8, 9)), - "2014-05-06 07:08:09 -04:00" - ); - assert_eq!( - format!("{}", Kst.ymd(2014, 5, 6).and_hms(7, 8, 9)), - "2014-05-06 07:08:09 +09:00" - ); - assert_eq!(format!("{:?}", Utc.ymd(2014, 5, 6).and_hms(7, 8, 9)), "2014-05-06T07:08:09Z"); - assert_eq!( - format!("{:?}", Edt.ymd(2014, 5, 6).and_hms(7, 8, 9)), - "2014-05-06T07:08:09-04:00" - ); - assert_eq!( - format!("{:?}", Kst.ymd(2014, 5, 6).and_hms(7, 8, 9)), - "2014-05-06T07:08:09+09:00" - ); - - // edge cases - assert_eq!(format!("{:?}", Utc.ymd(2014, 5, 6).and_hms(0, 0, 0)), "2014-05-06T00:00:00Z"); - assert_eq!( - format!("{:?}", Edt.ymd(2014, 5, 6).and_hms(0, 0, 0)), - "2014-05-06T00:00:00-04:00" - ); - assert_eq!( - format!("{:?}", Kst.ymd(2014, 5, 6).and_hms(0, 0, 0)), - "2014-05-06T00:00:00+09:00" - ); - assert_eq!( - format!("{:?}", Utc.ymd(2014, 5, 6).and_hms(23, 59, 59)), - "2014-05-06T23:59:59Z" - ); - assert_eq!( - format!("{:?}", Edt.ymd(2014, 5, 6).and_hms(23, 59, 59)), - "2014-05-06T23:59:59-04:00" - ); - assert_eq!( - format!("{:?}", Kst.ymd(2014, 5, 6).and_hms(23, 59, 59)), - "2014-05-06T23:59:59+09:00" - ); - - let dt = Utc.ymd(2014, 5, 6).and_hms(7, 8, 9); - assert_eq!(dt, Edt.ymd(2014, 5, 6).and_hms(3, 8, 9)); - assert_eq!(dt + Duration::seconds(3600 + 60 + 1), Utc.ymd(2014, 5, 6).and_hms(8, 9, 10)); - assert_eq!( - dt.signed_duration_since(Edt.ymd(2014, 5, 6).and_hms(10, 11, 12)), - Duration::seconds(-7 * 3600 - 3 * 60 - 3) - ); - - assert_eq!(*Utc.ymd(2014, 5, 6).and_hms(7, 8, 9).offset(), Utc); - assert_eq!(*Edt.ymd(2014, 5, 6).and_hms(7, 8, 9).offset(), Edt); - assert!(*Edt.ymd(2014, 5, 6).and_hms(7, 8, 9).offset() != Est); - } - - #[test] - fn test_datetime_date_and_time() { - let tz = FixedOffset::east(5 * 60 * 60); - let d = tz.ymd(2014, 5, 6).and_hms(7, 8, 9); - assert_eq!(d.time(), NaiveTime::from_hms(7, 8, 9)); - assert_eq!(d.date(), tz.ymd(2014, 5, 6)); - assert_eq!(d.date().naive_local(), NaiveDate::from_ymd(2014, 5, 6)); - assert_eq!(d.date().and_time(d.time()), Some(d)); - - let tz = FixedOffset::east(4 * 60 * 60); - let d = tz.ymd(2016, 5, 4).and_hms(3, 2, 1); - assert_eq!(d.time(), NaiveTime::from_hms(3, 2, 1)); - assert_eq!(d.date(), tz.ymd(2016, 5, 4)); - assert_eq!(d.date().naive_local(), NaiveDate::from_ymd(2016, 5, 4)); - assert_eq!(d.date().and_time(d.time()), Some(d)); - - let tz = FixedOffset::west(13 * 60 * 60); - let d = tz.ymd(2017, 8, 9).and_hms(12, 34, 56); - assert_eq!(d.time(), NaiveTime::from_hms(12, 34, 56)); - assert_eq!(d.date(), tz.ymd(2017, 8, 9)); - assert_eq!(d.date().naive_local(), NaiveDate::from_ymd(2017, 8, 9)); - assert_eq!(d.date().and_time(d.time()), Some(d)); - - let utc_d = Utc.ymd(2017, 8, 9).and_hms(12, 34, 56); - assert!(utc_d < d); - } - - #[test] - #[cfg(feature = "clock")] - fn test_datetime_with_timezone() { - let local_now = Local::now(); - let utc_now = local_now.with_timezone(&Utc); - let local_now2 = utc_now.with_timezone(&Local); - assert_eq!(local_now, local_now2); - } - - #[test] - #[allow(non_snake_case)] - fn test_datetime_rfc2822_and_rfc3339() { - let EDT = FixedOffset::east(5 * 60 * 60); - assert_eq!( - Utc.ymd(2015, 2, 18).and_hms(23, 16, 9).to_rfc2822(), - "Wed, 18 Feb 2015 23:16:09 +0000" - ); - assert_eq!( - Utc.ymd(2015, 2, 18).and_hms(23, 16, 9).to_rfc3339(), - "2015-02-18T23:16:09+00:00" - ); - assert_eq!( - EDT.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150).to_rfc2822(), - "Wed, 18 Feb 2015 23:16:09 +0500" - ); - assert_eq!( - EDT.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150).to_rfc3339(), - "2015-02-18T23:16:09.150+05:00" - ); - assert_eq!( - EDT.ymd(2015, 2, 18).and_hms_micro(23, 59, 59, 1_234_567).to_rfc2822(), - "Wed, 18 Feb 2015 23:59:60 +0500" - ); - assert_eq!( - EDT.ymd(2015, 2, 18).and_hms_micro(23, 59, 59, 1_234_567).to_rfc3339(), - "2015-02-18T23:59:60.234567+05:00" - ); - - assert_eq!( - DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 +0000"), - Ok(FixedOffset::east(0).ymd(2015, 2, 18).and_hms(23, 16, 9)) - ); - assert_eq!( - DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 -0000"), - Ok(FixedOffset::east(0).ymd(2015, 2, 18).and_hms(23, 16, 9)) - ); - assert_eq!( - DateTime::parse_from_rfc3339("2015-02-18T23:16:09Z"), - Ok(FixedOffset::east(0).ymd(2015, 2, 18).and_hms(23, 16, 9)) - ); - assert_eq!( - DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:60 +0500"), - Ok(EDT.ymd(2015, 2, 18).and_hms_milli(23, 59, 59, 1_000)) - ); - assert_eq!( - DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00"), - Ok(EDT.ymd(2015, 2, 18).and_hms_micro(23, 59, 59, 1_234_567)) - ); - } - - #[test] - fn test_rfc3339_opts() { - use SecondsFormat::*; - let pst = FixedOffset::east(8 * 60 * 60); - let dt = pst.ymd(2018, 1, 11).and_hms_nano(10, 5, 13, 084_660_000); - assert_eq!(dt.to_rfc3339_opts(Secs, false), "2018-01-11T10:05:13+08:00"); - assert_eq!(dt.to_rfc3339_opts(Secs, true), "2018-01-11T10:05:13+08:00"); - assert_eq!(dt.to_rfc3339_opts(Millis, false), "2018-01-11T10:05:13.084+08:00"); - assert_eq!(dt.to_rfc3339_opts(Micros, false), "2018-01-11T10:05:13.084660+08:00"); - assert_eq!(dt.to_rfc3339_opts(Nanos, false), "2018-01-11T10:05:13.084660000+08:00"); - assert_eq!(dt.to_rfc3339_opts(AutoSi, false), "2018-01-11T10:05:13.084660+08:00"); - - let ut = DateTime::::from_utc(dt.naive_utc(), Utc); - assert_eq!(ut.to_rfc3339_opts(Secs, false), "2018-01-11T02:05:13+00:00"); - assert_eq!(ut.to_rfc3339_opts(Secs, true), "2018-01-11T02:05:13Z"); - assert_eq!(ut.to_rfc3339_opts(Millis, false), "2018-01-11T02:05:13.084+00:00"); - assert_eq!(ut.to_rfc3339_opts(Millis, true), "2018-01-11T02:05:13.084Z"); - assert_eq!(ut.to_rfc3339_opts(Micros, true), "2018-01-11T02:05:13.084660Z"); - assert_eq!(ut.to_rfc3339_opts(Nanos, true), "2018-01-11T02:05:13.084660000Z"); - assert_eq!(ut.to_rfc3339_opts(AutoSi, true), "2018-01-11T02:05:13.084660Z"); - } - - #[test] - #[should_panic] - fn test_rfc3339_opts_nonexhaustive() { - use SecondsFormat; - let dt = Utc.ymd(1999, 10, 9).and_hms(1, 2, 3); - dt.to_rfc3339_opts(SecondsFormat::__NonExhaustive, true); - } - - #[test] - fn test_datetime_from_str() { - assert_eq!( - "2015-02-18T23:16:9.15Z".parse::>(), - Ok(FixedOffset::east(0).ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150)) - ); - assert_eq!( - "2015-02-18T23:16:9.15Z".parse::>(), - Ok(Utc.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150)) - ); - assert_eq!( - "2015-02-18T23:16:9.15 UTC".parse::>(), - Ok(Utc.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150)) - ); - assert_eq!( - "2015-02-18T23:16:9.15UTC".parse::>(), - Ok(Utc.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150)) - ); - - assert_eq!( - "2015-2-18T23:16:9.15Z".parse::>(), - Ok(FixedOffset::east(0).ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150)) - ); - assert_eq!( - "2015-2-18T13:16:9.15-10:00".parse::>(), - Ok(FixedOffset::west(10 * 3600).ymd(2015, 2, 18).and_hms_milli(13, 16, 9, 150)) - ); - assert!("2015-2-18T23:16:9.15".parse::>().is_err()); - - assert_eq!( - "2015-2-18T23:16:9.15Z".parse::>(), - Ok(Utc.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150)) - ); - assert_eq!( - "2015-2-18T13:16:9.15-10:00".parse::>(), - Ok(Utc.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150)) - ); - assert!("2015-2-18T23:16:9.15".parse::>().is_err()); - - // no test for `DateTime`, we cannot verify that much. - } - - #[test] - fn test_datetime_parse_from_str() { - let ymdhms = |y, m, d, h, n, s, off| FixedOffset::east(off).ymd(y, m, d).and_hms(h, n, s); - assert_eq!( - DateTime::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), - Ok(ymdhms(2014, 5, 7, 12, 34, 56, 570 * 60)) - ); // ignore offset - assert!(DateTime::parse_from_str("20140507000000", "%Y%m%d%H%M%S").is_err()); // no offset - assert!(DateTime::parse_from_str( - "Fri, 09 Aug 2013 23:54:35 GMT", - "%a, %d %b %Y %H:%M:%S GMT" - ) - .is_err()); - assert_eq!( - Utc.datetime_from_str("Fri, 09 Aug 2013 23:54:35 GMT", "%a, %d %b %Y %H:%M:%S GMT"), - Ok(Utc.ymd(2013, 8, 9).and_hms(23, 54, 35)) - ); - } - - #[test] - fn test_to_string_round_trip() { - let dt = Utc.ymd(2000, 1, 1).and_hms(0, 0, 0); - let _dt: DateTime = dt.to_string().parse().unwrap(); - - let ndt_fixed = dt.with_timezone(&FixedOffset::east(3600)); - let _dt: DateTime = ndt_fixed.to_string().parse().unwrap(); - - let ndt_fixed = dt.with_timezone(&FixedOffset::east(0)); - let _dt: DateTime = ndt_fixed.to_string().parse().unwrap(); - } - - #[test] - #[cfg(feature = "clock")] - fn test_to_string_round_trip_with_local() { - let ndt = Local::now(); - let _dt: DateTime = ndt.to_string().parse().unwrap(); - } - - #[test] - #[cfg(feature = "clock")] - fn test_datetime_format_with_local() { - // if we are not around the year boundary, local and UTC date should have the same year - let dt = Local::now().with_month(5).unwrap(); - assert_eq!(dt.format("%Y").to_string(), dt.with_timezone(&Utc).format("%Y").to_string()); - } - - #[test] - #[cfg(feature = "clock")] - fn test_datetime_is_copy() { - // UTC is known to be `Copy`. - let a = Utc::now(); - let b = a; - assert_eq!(a, b); - } - - #[test] - #[cfg(feature = "clock")] - fn test_datetime_is_send() { - use std::thread; - - // UTC is known to be `Send`. - let a = Utc::now(); - thread::spawn(move || { - let _ = a; - }) - .join() - .unwrap(); - } - - #[test] - fn test_subsecond_part() { - let datetime = Utc.ymd(2014, 7, 8).and_hms_nano(9, 10, 11, 1234567); - - assert_eq!(1, datetime.timestamp_subsec_millis()); - assert_eq!(1234, datetime.timestamp_subsec_micros()); - assert_eq!(1234567, datetime.timestamp_subsec_nanos()); - } - - #[test] - #[cfg(not(target_os = "windows"))] - fn test_from_system_time() { - use std::time::Duration; - - let epoch = Utc.ymd(1970, 1, 1).and_hms(0, 0, 0); - let nanos = 999_999_999; - - // SystemTime -> DateTime - assert_eq!(DateTime::::from(UNIX_EPOCH), epoch); - assert_eq!( - DateTime::::from(UNIX_EPOCH + Duration::new(999_999_999, nanos)), - Utc.ymd(2001, 9, 9).and_hms_nano(1, 46, 39, nanos) - ); - assert_eq!( - DateTime::::from(UNIX_EPOCH - Duration::new(999_999_999, nanos)), - Utc.ymd(1938, 4, 24).and_hms_nano(22, 13, 20, 1) - ); - - // DateTime -> SystemTime - assert_eq!(SystemTime::from(epoch), UNIX_EPOCH); - assert_eq!( - SystemTime::from(Utc.ymd(2001, 9, 9).and_hms_nano(1, 46, 39, nanos)), - UNIX_EPOCH + Duration::new(999_999_999, nanos) - ); - assert_eq!( - SystemTime::from(Utc.ymd(1938, 4, 24).and_hms_nano(22, 13, 20, 1)), - UNIX_EPOCH - Duration::new(999_999_999, 999_999_999) - ); - - // DateTime -> SystemTime (via `with_timezone`) - #[cfg(feature = "clock")] - { - assert_eq!(SystemTime::from(epoch.with_timezone(&Local)), UNIX_EPOCH); - } - assert_eq!(SystemTime::from(epoch.with_timezone(&FixedOffset::east(32400))), UNIX_EPOCH); - assert_eq!(SystemTime::from(epoch.with_timezone(&FixedOffset::west(28800))), UNIX_EPOCH); - } - - #[test] - #[cfg(target_os = "windows")] - fn test_from_system_time() { - use std::time::Duration; - - let nanos = 999_999_000; - - let epoch = Utc.ymd(1970, 1, 1).and_hms(0, 0, 0); - - // SystemTime -> DateTime - assert_eq!(DateTime::::from(UNIX_EPOCH), epoch); - assert_eq!( - DateTime::::from(UNIX_EPOCH + Duration::new(999_999_999, nanos)), - Utc.ymd(2001, 9, 9).and_hms_nano(1, 46, 39, nanos) - ); - assert_eq!( - DateTime::::from(UNIX_EPOCH - Duration::new(999_999_999, nanos)), - Utc.ymd(1938, 4, 24).and_hms_nano(22, 13, 20, 1_000) - ); - - // DateTime -> SystemTime - assert_eq!(SystemTime::from(epoch), UNIX_EPOCH); - assert_eq!( - SystemTime::from(Utc.ymd(2001, 9, 9).and_hms_nano(1, 46, 39, nanos)), - UNIX_EPOCH + Duration::new(999_999_999, nanos) - ); - assert_eq!( - SystemTime::from(Utc.ymd(1938, 4, 24).and_hms_nano(22, 13, 20, 1_000)), - UNIX_EPOCH - Duration::new(999_999_999, nanos) - ); - - // DateTime -> SystemTime (via `with_timezone`) - #[cfg(feature = "clock")] - { - assert_eq!(SystemTime::from(epoch.with_timezone(&Local)), UNIX_EPOCH); - } - assert_eq!(SystemTime::from(epoch.with_timezone(&FixedOffset::east(32400))), UNIX_EPOCH); - assert_eq!(SystemTime::from(epoch.with_timezone(&FixedOffset::west(28800))), UNIX_EPOCH); - } - - #[test] - fn test_datetime_format_alignment() { - let datetime = Utc.ymd(2007, 01, 02); - - // Item::Literal - let percent = datetime.format("%%"); - assert_eq!(" %", format!("{:>3}", percent)); - assert_eq!("% ", format!("{:<3}", percent)); - assert_eq!(" % ", format!("{:^3}", percent)); - - // Item::Numeric - let year = datetime.format("%Y"); - assert_eq!(" 2007", format!("{:>6}", year)); - assert_eq!("2007 ", format!("{:<6}", year)); - assert_eq!(" 2007 ", format!("{:^6}", year)); - - // Item::Fixed - let tz = datetime.format("%Z"); - assert_eq!(" UTC", format!("{:>5}", tz)); - assert_eq!("UTC ", format!("{:<5}", tz)); - assert_eq!(" UTC ", format!("{:^5}", tz)); - - // [Item::Numeric, Item::Space, Item::Literal, Item::Space, Item::Numeric] - let ymd = datetime.format("%Y %B %d"); - let ymd_formatted = "2007 January 02"; - assert_eq!(format!(" {}", ymd_formatted), format!("{:>17}", ymd)); - assert_eq!(format!("{} ", ymd_formatted), format!("{:<17}", ymd)); - assert_eq!(format!(" {} ", ymd_formatted), format!("{:^17}", ymd)); - } -} diff --git a/third_party/rust/chrono/src/datetime/mod.rs b/third_party/rust/chrono/src/datetime/mod.rs new file mode 100644 index 00000000000..ccb6c953b24 --- /dev/null +++ b/third_party/rust/chrono/src/datetime/mod.rs @@ -0,0 +1,1940 @@ +// This is a part of Chrono. +// See README.md and LICENSE.txt for details. + +//! ISO 8601 date and time with time zone. + +#[cfg(all(feature = "alloc", not(feature = "std"), not(test)))] +use alloc::string::String; +use core::borrow::Borrow; +use core::cmp::Ordering; +use core::fmt::Write; +use core::ops::{Add, AddAssign, Sub, SubAssign}; +use core::time::Duration; +use core::{fmt, hash, str}; +#[cfg(feature = "std")] +use std::time::{SystemTime, UNIX_EPOCH}; + +#[allow(deprecated)] +use crate::Date; +#[cfg(all(feature = "unstable-locales", feature = "alloc"))] +use crate::format::Locale; +#[cfg(feature = "alloc")] +use crate::format::{DelayedFormat, SecondsFormat, write_rfc2822, write_rfc3339}; +use crate::format::{ + Fixed, Item, ParseError, ParseResult, Parsed, StrftimeItems, TOO_LONG, parse, + parse_and_remainder, parse_rfc3339, +}; +use crate::naive::{Days, IsoWeek, NaiveDate, NaiveDateTime, NaiveTime}; +#[cfg(feature = "clock")] +use crate::offset::Local; +use crate::offset::{FixedOffset, LocalResult, Offset, TimeZone, Utc}; +use crate::{Datelike, Months, TimeDelta, Timelike, Weekday}; +use crate::{expect, try_opt}; + +#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] +use rkyv::{Archive, Deserialize, Serialize}; + +/// documented at re-export site +#[cfg(feature = "serde")] +pub(super) mod serde; + +#[cfg(test)] +mod tests; + +/// ISO 8601 combined date and time with time zone. +/// +/// There are some constructors implemented here (the `from_*` methods), but +/// the general-purpose constructors are all via the methods on the +/// [`TimeZone`](./offset/trait.TimeZone.html) implementations. +#[derive(Clone)] +#[cfg_attr( + any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), + derive(Archive, Deserialize, Serialize), + archive(compare(PartialEq, PartialOrd)) +)] +#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] +pub struct DateTime { + datetime: NaiveDateTime, + offset: Tz::Offset, +} + +/// The minimum possible `DateTime`. +#[deprecated(since = "0.4.20", note = "Use DateTime::MIN_UTC instead")] +pub const MIN_DATETIME: DateTime = DateTime::::MIN_UTC; +/// The maximum possible `DateTime`. +#[deprecated(since = "0.4.20", note = "Use DateTime::MAX_UTC instead")] +pub const MAX_DATETIME: DateTime = DateTime::::MAX_UTC; + +impl DateTime { + /// Makes a new `DateTime` from its components: a `NaiveDateTime` in UTC and an `Offset`. + /// + /// This is a low-level method, intended for use cases such as deserializing a `DateTime` or + /// passing it through FFI. + /// + /// For regular use you will probably want to use a method such as + /// [`TimeZone::from_local_datetime`] or [`NaiveDateTime::and_local_timezone`] instead. + /// + /// # Example + /// + /// ``` + /// # #[cfg(feature = "clock")] { + /// use chrono::{DateTime, Local}; + /// + /// let dt = Local::now(); + /// // Get components + /// let naive_utc = dt.naive_utc(); + /// let offset = dt.offset().clone(); + /// // Serialize, pass through FFI... and recreate the `DateTime`: + /// let dt_new = DateTime::::from_naive_utc_and_offset(naive_utc, offset); + /// assert_eq!(dt, dt_new); + /// # } + /// ``` + #[inline] + #[must_use] + pub const fn from_naive_utc_and_offset( + datetime: NaiveDateTime, + offset: Tz::Offset, + ) -> DateTime { + DateTime { datetime, offset } + } + + /// Makes a new `DateTime` from its components: a `NaiveDateTime` in UTC and an `Offset`. + #[inline] + #[must_use] + #[deprecated( + since = "0.4.27", + note = "Use TimeZone::from_utc_datetime() or DateTime::from_naive_utc_and_offset instead" + )] + pub fn from_utc(datetime: NaiveDateTime, offset: Tz::Offset) -> DateTime { + DateTime { datetime, offset } + } + + /// Makes a new `DateTime` from a `NaiveDateTime` in *local* time and an `Offset`. + /// + /// # Panics + /// + /// Panics if the local datetime can't be converted to UTC because it would be out of range. + /// + /// This can happen if `datetime` is near the end of the representable range of `NaiveDateTime`, + /// and the offset from UTC pushes it beyond that. + #[inline] + #[must_use] + #[deprecated( + since = "0.4.27", + note = "Use TimeZone::from_local_datetime() or NaiveDateTime::and_local_timezone instead" + )] + pub fn from_local(datetime: NaiveDateTime, offset: Tz::Offset) -> DateTime { + let datetime_utc = datetime - offset.fix(); + + DateTime { datetime: datetime_utc, offset } + } + + /// Retrieves the date component with an associated timezone. + /// + /// Unless you are immediately planning on turning this into a `DateTime` + /// with the same timezone you should use the [`date_naive`](DateTime::date_naive) method. + /// + /// [`NaiveDate`] is a more well-defined type, and has more traits implemented on it, + /// so should be preferred to [`Date`] any time you truly want to operate on dates. + /// + /// # Panics + /// + /// [`DateTime`] internally stores the date and time in UTC with a [`NaiveDateTime`]. This + /// method will panic if the offset from UTC would push the local date outside of the + /// representable range of a [`Date`]. + #[inline] + #[deprecated(since = "0.4.23", note = "Use `date_naive()` instead")] + #[allow(deprecated)] + #[must_use] + pub fn date(&self) -> Date { + Date::from_utc(self.naive_local().date(), self.offset.clone()) + } + + /// Retrieves the date component. + /// + /// # Panics + /// + /// [`DateTime`] internally stores the date and time in UTC with a [`NaiveDateTime`]. This + /// method will panic if the offset from UTC would push the local date outside of the + /// representable range of a [`NaiveDate`]. + /// + /// # Example + /// + /// ``` + /// use chrono::prelude::*; + /// + /// let date: DateTime = Utc.with_ymd_and_hms(2020, 1, 1, 0, 0, 0).unwrap(); + /// let other: DateTime = + /// FixedOffset::east_opt(23).unwrap().with_ymd_and_hms(2020, 1, 1, 0, 0, 0).unwrap(); + /// assert_eq!(date.date_naive(), other.date_naive()); + /// ``` + #[inline] + #[must_use] + pub fn date_naive(&self) -> NaiveDate { + self.naive_local().date() + } + + /// Retrieves the time component. + #[inline] + #[must_use] + pub fn time(&self) -> NaiveTime { + self.datetime.time() + self.offset.fix() + } + + /// Returns the number of non-leap seconds since January 1, 1970 0:00:00 UTC + /// (aka "UNIX timestamp"). + /// + /// The reverse operation of creating a [`DateTime`] from a timestamp can be performed + /// using [`from_timestamp`](DateTime::from_timestamp) or [`TimeZone::timestamp_opt`]. + /// + /// ``` + /// use chrono::{DateTime, TimeZone, Utc}; + /// + /// let dt: DateTime = Utc.with_ymd_and_hms(2015, 5, 15, 0, 0, 0).unwrap(); + /// assert_eq!(dt.timestamp(), 1431648000); + /// + /// assert_eq!(DateTime::from_timestamp(dt.timestamp(), dt.timestamp_subsec_nanos()).unwrap(), dt); + /// ``` + #[inline] + #[must_use] + pub const fn timestamp(&self) -> i64 { + let gregorian_day = self.datetime.date().num_days_from_ce() as i64; + let seconds_from_midnight = self.datetime.time().num_seconds_from_midnight() as i64; + (gregorian_day - UNIX_EPOCH_DAY) * 86_400 + seconds_from_midnight + } + + /// Returns the number of non-leap-milliseconds since January 1, 1970 UTC. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, Utc}; + /// + /// let dt = NaiveDate::from_ymd_opt(1970, 1, 1) + /// .unwrap() + /// .and_hms_milli_opt(0, 0, 1, 444) + /// .unwrap() + /// .and_local_timezone(Utc) + /// .unwrap(); + /// assert_eq!(dt.timestamp_millis(), 1_444); + /// + /// let dt = NaiveDate::from_ymd_opt(2001, 9, 9) + /// .unwrap() + /// .and_hms_milli_opt(1, 46, 40, 555) + /// .unwrap() + /// .and_local_timezone(Utc) + /// .unwrap(); + /// assert_eq!(dt.timestamp_millis(), 1_000_000_000_555); + /// ``` + #[inline] + #[must_use] + pub const fn timestamp_millis(&self) -> i64 { + let as_ms = self.timestamp() * 1000; + as_ms + self.timestamp_subsec_millis() as i64 + } + + /// Returns the number of non-leap-microseconds since January 1, 1970 UTC. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, Utc}; + /// + /// let dt = NaiveDate::from_ymd_opt(1970, 1, 1) + /// .unwrap() + /// .and_hms_micro_opt(0, 0, 1, 444) + /// .unwrap() + /// .and_local_timezone(Utc) + /// .unwrap(); + /// assert_eq!(dt.timestamp_micros(), 1_000_444); + /// + /// let dt = NaiveDate::from_ymd_opt(2001, 9, 9) + /// .unwrap() + /// .and_hms_micro_opt(1, 46, 40, 555) + /// .unwrap() + /// .and_local_timezone(Utc) + /// .unwrap(); + /// assert_eq!(dt.timestamp_micros(), 1_000_000_000_000_555); + /// ``` + #[inline] + #[must_use] + pub const fn timestamp_micros(&self) -> i64 { + let as_us = self.timestamp() * 1_000_000; + as_us + self.timestamp_subsec_micros() as i64 + } + + /// Returns the number of non-leap-nanoseconds since January 1, 1970 UTC. + /// + /// # Panics + /// + /// An `i64` with nanosecond precision can span a range of ~584 years. This function panics on + /// an out of range `DateTime`. + /// + /// The dates that can be represented as nanoseconds are between 1677-09-21T00:12:43.145224192 + /// and 2262-04-11T23:47:16.854775807. + #[deprecated(since = "0.4.31", note = "use `timestamp_nanos_opt()` instead")] + #[inline] + #[must_use] + pub const fn timestamp_nanos(&self) -> i64 { + expect( + self.timestamp_nanos_opt(), + "value can not be represented in a timestamp with nanosecond precision.", + ) + } + + /// Returns the number of non-leap-nanoseconds since January 1, 1970 UTC. + /// + /// # Errors + /// + /// An `i64` with nanosecond precision can span a range of ~584 years. This function returns + /// `None` on an out of range `DateTime`. + /// + /// The dates that can be represented as nanoseconds are between 1677-09-21T00:12:43.145224192 + /// and 2262-04-11T23:47:16.854775807. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, Utc}; + /// + /// let dt = NaiveDate::from_ymd_opt(1970, 1, 1) + /// .unwrap() + /// .and_hms_nano_opt(0, 0, 1, 444) + /// .unwrap() + /// .and_local_timezone(Utc) + /// .unwrap(); + /// assert_eq!(dt.timestamp_nanos_opt(), Some(1_000_000_444)); + /// + /// let dt = NaiveDate::from_ymd_opt(2001, 9, 9) + /// .unwrap() + /// .and_hms_nano_opt(1, 46, 40, 555) + /// .unwrap() + /// .and_local_timezone(Utc) + /// .unwrap(); + /// assert_eq!(dt.timestamp_nanos_opt(), Some(1_000_000_000_000_000_555)); + /// + /// let dt = NaiveDate::from_ymd_opt(1677, 9, 21) + /// .unwrap() + /// .and_hms_nano_opt(0, 12, 43, 145_224_192) + /// .unwrap() + /// .and_local_timezone(Utc) + /// .unwrap(); + /// assert_eq!(dt.timestamp_nanos_opt(), Some(-9_223_372_036_854_775_808)); + /// + /// let dt = NaiveDate::from_ymd_opt(2262, 4, 11) + /// .unwrap() + /// .and_hms_nano_opt(23, 47, 16, 854_775_807) + /// .unwrap() + /// .and_local_timezone(Utc) + /// .unwrap(); + /// assert_eq!(dt.timestamp_nanos_opt(), Some(9_223_372_036_854_775_807)); + /// + /// let dt = NaiveDate::from_ymd_opt(1677, 9, 21) + /// .unwrap() + /// .and_hms_nano_opt(0, 12, 43, 145_224_191) + /// .unwrap() + /// .and_local_timezone(Utc) + /// .unwrap(); + /// assert_eq!(dt.timestamp_nanos_opt(), None); + /// + /// let dt = NaiveDate::from_ymd_opt(2262, 4, 11) + /// .unwrap() + /// .and_hms_nano_opt(23, 47, 16, 854_775_808) + /// .unwrap() + /// .and_local_timezone(Utc) + /// .unwrap(); + /// assert_eq!(dt.timestamp_nanos_opt(), None); + /// ``` + #[inline] + #[must_use] + pub const fn timestamp_nanos_opt(&self) -> Option { + let mut timestamp = self.timestamp(); + let mut subsec_nanos = self.timestamp_subsec_nanos() as i64; + // `(timestamp * 1_000_000_000) + subsec_nanos` may create a temporary that underflows while + // the final value can be represented as an `i64`. + // As workaround we converting the negative case to: + // `((timestamp + 1) * 1_000_000_000) + (ns - 1_000_000_000)`` + // + // Also see . + if timestamp < 0 { + subsec_nanos -= 1_000_000_000; + timestamp += 1; + } + try_opt!(timestamp.checked_mul(1_000_000_000)).checked_add(subsec_nanos) + } + + /// Returns the number of milliseconds since the last second boundary. + /// + /// In event of a leap second this may exceed 999. + #[inline] + #[must_use] + pub const fn timestamp_subsec_millis(&self) -> u32 { + self.timestamp_subsec_nanos() / 1_000_000 + } + + /// Returns the number of microseconds since the last second boundary. + /// + /// In event of a leap second this may exceed 999,999. + #[inline] + #[must_use] + pub const fn timestamp_subsec_micros(&self) -> u32 { + self.timestamp_subsec_nanos() / 1_000 + } + + /// Returns the number of nanoseconds since the last second boundary + /// + /// In event of a leap second this may exceed 999,999,999. + #[inline] + #[must_use] + pub const fn timestamp_subsec_nanos(&self) -> u32 { + self.datetime.time().nanosecond() + } + + /// Retrieves an associated offset from UTC. + #[inline] + #[must_use] + pub const fn offset(&self) -> &Tz::Offset { + &self.offset + } + + /// Retrieves an associated time zone. + #[inline] + #[must_use] + pub fn timezone(&self) -> Tz { + TimeZone::from_offset(&self.offset) + } + + /// Changes the associated time zone. + /// The returned `DateTime` references the same instant of time from the perspective of the + /// provided time zone. + #[inline] + #[must_use] + pub fn with_timezone(&self, tz: &Tz2) -> DateTime { + tz.from_utc_datetime(&self.datetime) + } + + /// Fix the offset from UTC to its current value, dropping the associated timezone information. + /// This it useful for converting a generic `DateTime` to `DateTime`. + #[inline] + #[must_use] + pub fn fixed_offset(&self) -> DateTime { + self.with_timezone(&self.offset().fix()) + } + + /// Turn this `DateTime` into a `DateTime`, dropping the offset and associated timezone + /// information. + #[inline] + #[must_use] + pub const fn to_utc(&self) -> DateTime { + DateTime { datetime: self.datetime, offset: Utc } + } + + /// Adds given `TimeDelta` to the current date and time. + /// + /// # Errors + /// + /// Returns `None` if the resulting date would be out of range. + #[inline] + #[must_use] + pub fn checked_add_signed(self, rhs: TimeDelta) -> Option> { + let datetime = self.datetime.checked_add_signed(rhs)?; + let tz = self.timezone(); + Some(tz.from_utc_datetime(&datetime)) + } + + /// Adds given `Months` to the current date and time. + /// + /// Uses the last day of the month if the day does not exist in the resulting month. + /// + /// See [`NaiveDate::checked_add_months`] for more details on behavior. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The local time at the resulting date does not exist or is ambiguous, for example during a + /// daylight saving time transition. + /// - The resulting UTC datetime would be out of range. + /// - The resulting local datetime would be out of range (unless `months` is zero). + #[must_use] + pub fn checked_add_months(self, months: Months) -> Option> { + // `NaiveDate::checked_add_months` has a fast path for `Months(0)` that does not validate + // the resulting date, with which we can return `Some` even for an out of range local + // datetime. + self.overflowing_naive_local() + .checked_add_months(months)? + .and_local_timezone(Tz::from_offset(&self.offset)) + .single() + } + + /// Subtracts given `TimeDelta` from the current date and time. + /// + /// # Errors + /// + /// Returns `None` if the resulting date would be out of range. + #[inline] + #[must_use] + pub fn checked_sub_signed(self, rhs: TimeDelta) -> Option> { + let datetime = self.datetime.checked_sub_signed(rhs)?; + let tz = self.timezone(); + Some(tz.from_utc_datetime(&datetime)) + } + + /// Subtracts given `Months` from the current date and time. + /// + /// Uses the last day of the month if the day does not exist in the resulting month. + /// + /// See [`NaiveDate::checked_sub_months`] for more details on behavior. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The local time at the resulting date does not exist or is ambiguous, for example during a + /// daylight saving time transition. + /// - The resulting UTC datetime would be out of range. + /// - The resulting local datetime would be out of range (unless `months` is zero). + #[must_use] + pub fn checked_sub_months(self, months: Months) -> Option> { + // `NaiveDate::checked_sub_months` has a fast path for `Months(0)` that does not validate + // the resulting date, with which we can return `Some` even for an out of range local + // datetime. + self.overflowing_naive_local() + .checked_sub_months(months)? + .and_local_timezone(Tz::from_offset(&self.offset)) + .single() + } + + /// Add a duration in [`Days`] to the date part of the `DateTime`. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The local time at the resulting date does not exist or is ambiguous, for example during a + /// daylight saving time transition. + /// - The resulting UTC datetime would be out of range. + /// - The resulting local datetime would be out of range (unless `days` is zero). + #[must_use] + pub fn checked_add_days(self, days: Days) -> Option { + if days == Days::new(0) { + return Some(self); + } + // `NaiveDate::add_days` has a fast path if the result remains within the same year, that + // does not validate the resulting date. This allows us to return `Some` even for an out of + // range local datetime when adding `Days(0)`. + self.overflowing_naive_local() + .checked_add_days(days) + .and_then(|dt| self.timezone().from_local_datetime(&dt).single()) + .filter(|dt| dt <= &DateTime::::MAX_UTC) + } + + /// Subtract a duration in [`Days`] from the date part of the `DateTime`. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The local time at the resulting date does not exist or is ambiguous, for example during a + /// daylight saving time transition. + /// - The resulting UTC datetime would be out of range. + /// - The resulting local datetime would be out of range (unless `days` is zero). + #[must_use] + pub fn checked_sub_days(self, days: Days) -> Option { + // `NaiveDate::add_days` has a fast path if the result remains within the same year, that + // does not validate the resulting date. This allows us to return `Some` even for an out of + // range local datetime when adding `Days(0)`. + self.overflowing_naive_local() + .checked_sub_days(days) + .and_then(|dt| self.timezone().from_local_datetime(&dt).single()) + .filter(|dt| dt >= &DateTime::::MIN_UTC) + } + + /// Subtracts another `DateTime` from the current date and time. + /// This does not overflow or underflow at all. + #[inline] + #[must_use] + pub fn signed_duration_since( + self, + rhs: impl Borrow>, + ) -> TimeDelta { + self.datetime.signed_duration_since(rhs.borrow().datetime) + } + + /// Returns a view to the naive UTC datetime. + #[inline] + #[must_use] + pub const fn naive_utc(&self) -> NaiveDateTime { + self.datetime + } + + /// Returns a view to the naive local datetime. + /// + /// # Panics + /// + /// [`DateTime`] internally stores the date and time in UTC with a [`NaiveDateTime`]. This + /// method will panic if the offset from UTC would push the local datetime outside of the + /// representable range of a [`NaiveDateTime`]. + #[inline] + #[must_use] + pub fn naive_local(&self) -> NaiveDateTime { + self.datetime + .checked_add_offset(self.offset.fix()) + .expect("Local time out of range for `NaiveDateTime`") + } + + /// Returns the naive local datetime. + /// + /// This makes use of the buffer space outside of the representable range of values of + /// `NaiveDateTime`. The result can be used as intermediate value, but should never be exposed + /// outside chrono. + #[inline] + #[must_use] + pub(crate) fn overflowing_naive_local(&self) -> NaiveDateTime { + self.datetime.overflowing_add_offset(self.offset.fix()) + } + + /// Retrieve the elapsed years from now to the given [`DateTime`]. + /// + /// # Errors + /// + /// Returns `None` if `base > self`. + #[must_use] + pub fn years_since(&self, base: Self) -> Option { + let mut years = self.year() - base.year(); + let earlier_time = + (self.month(), self.day(), self.time()) < (base.month(), base.day(), base.time()); + + years -= match earlier_time { + true => 1, + false => 0, + }; + + match years >= 0 { + true => Some(years as u32), + false => None, + } + } + + /// Returns an RFC 2822 date and time string such as `Tue, 1 Jul 2003 10:52:37 +0200`. + /// + /// # Panics + /// + /// Panics if the date can not be represented in this format: the year may not be negative and + /// can not have more than 4 digits. + #[cfg(feature = "alloc")] + #[must_use] + pub fn to_rfc2822(&self) -> String { + let mut result = String::with_capacity(32); + write_rfc2822(&mut result, self.overflowing_naive_local(), self.offset.fix()) + .expect("writing rfc2822 datetime to string should never fail"); + result + } + + /// Returns an RFC 3339 and ISO 8601 date and time string such as `1996-12-19T16:39:57-08:00`. + #[cfg(feature = "alloc")] + #[must_use] + pub fn to_rfc3339(&self) -> String { + // For some reason a string with a capacity less than 32 is ca 20% slower when benchmarking. + let mut result = String::with_capacity(32); + let naive = self.overflowing_naive_local(); + let offset = self.offset.fix(); + write_rfc3339(&mut result, naive, offset, SecondsFormat::AutoSi, false) + .expect("writing rfc3339 datetime to string should never fail"); + result + } + + /// Return an RFC 3339 and ISO 8601 date and time string with subseconds + /// formatted as per `SecondsFormat`. + /// + /// If `use_z` is true and the timezone is UTC (offset 0), uses `Z` as + /// per [`Fixed::TimezoneOffsetColonZ`]. If `use_z` is false, uses + /// [`Fixed::TimezoneOffsetColon`] + /// + /// # Examples + /// + /// ```rust + /// # use chrono::{FixedOffset, SecondsFormat, TimeZone, NaiveDate}; + /// let dt = NaiveDate::from_ymd_opt(2018, 1, 26) + /// .unwrap() + /// .and_hms_micro_opt(18, 30, 9, 453_829) + /// .unwrap() + /// .and_utc(); + /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Millis, false), "2018-01-26T18:30:09.453+00:00"); + /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Millis, true), "2018-01-26T18:30:09.453Z"); + /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Secs, true), "2018-01-26T18:30:09Z"); + /// + /// let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap(); + /// let dt = pst + /// .from_local_datetime( + /// &NaiveDate::from_ymd_opt(2018, 1, 26) + /// .unwrap() + /// .and_hms_micro_opt(10, 30, 9, 453_829) + /// .unwrap(), + /// ) + /// .unwrap(); + /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Secs, true), "2018-01-26T10:30:09+08:00"); + /// ``` + #[cfg(feature = "alloc")] + #[must_use] + pub fn to_rfc3339_opts(&self, secform: SecondsFormat, use_z: bool) -> String { + let mut result = String::with_capacity(38); + write_rfc3339(&mut result, self.naive_local(), self.offset.fix(), secform, use_z) + .expect("writing rfc3339 datetime to string should never fail"); + result + } + + /// Set the time to a new fixed time on the existing date. + /// + /// # Errors + /// + /// Returns `LocalResult::None` if the datetime is at the edge of the representable range for a + /// `DateTime`, and `with_time` would push the value in UTC out of range. + /// + /// # Example + /// + /// ``` + /// # #[cfg(feature = "clock")] { + /// use chrono::{Local, NaiveTime}; + /// + /// let noon = NaiveTime::from_hms_opt(12, 0, 0).unwrap(); + /// let today_noon = Local::now().with_time(noon); + /// let today_midnight = Local::now().with_time(NaiveTime::MIN); + /// + /// assert_eq!(today_noon.single().unwrap().time(), noon); + /// assert_eq!(today_midnight.single().unwrap().time(), NaiveTime::MIN); + /// # } + /// ``` + #[must_use] + pub fn with_time(&self, time: NaiveTime) -> LocalResult { + self.timezone().from_local_datetime(&self.overflowing_naive_local().date().and_time(time)) + } + + /// The minimum possible `DateTime`. + pub const MIN_UTC: DateTime = DateTime { datetime: NaiveDateTime::MIN, offset: Utc }; + /// The maximum possible `DateTime`. + pub const MAX_UTC: DateTime = DateTime { datetime: NaiveDateTime::MAX, offset: Utc }; +} + +impl DateTime { + /// Makes a new `DateTime` from the number of non-leap seconds + /// since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp") + /// and the number of nanoseconds since the last whole non-leap second. + /// + /// This is guaranteed to round-trip with regard to [`timestamp`](DateTime::timestamp) and + /// [`timestamp_subsec_nanos`](DateTime::timestamp_subsec_nanos). + /// + /// If you need to create a `DateTime` with a [`TimeZone`] different from [`Utc`], use + /// [`TimeZone::timestamp_opt`] or [`DateTime::with_timezone`]. + /// + /// The nanosecond part can exceed 1,000,000,000 in order to represent a + /// [leap second](NaiveTime#leap-second-handling), but only when `secs % 60 == 59`. + /// (The true "UNIX timestamp" cannot represent a leap second unambiguously.) + /// + /// # Errors + /// + /// Returns `None` on out-of-range number of seconds and/or + /// invalid nanosecond, otherwise returns `Some(DateTime {...})`. + /// + /// # Example + /// + /// ``` + /// use chrono::DateTime; + /// + /// let dt = DateTime::from_timestamp(1431648000, 0).expect("invalid timestamp"); + /// + /// assert_eq!(dt.to_string(), "2015-05-15 00:00:00 UTC"); + /// assert_eq!(DateTime::from_timestamp(dt.timestamp(), dt.timestamp_subsec_nanos()).unwrap(), dt); + /// ``` + #[inline] + #[must_use] + pub const fn from_timestamp(secs: i64, nsecs: u32) -> Option { + let days = secs.div_euclid(86_400) + UNIX_EPOCH_DAY; + let secs = secs.rem_euclid(86_400); + if days < i32::MIN as i64 || days > i32::MAX as i64 { + return None; + } + let date = try_opt!(NaiveDate::from_num_days_from_ce_opt(days as i32)); + let time = try_opt!(NaiveTime::from_num_seconds_from_midnight_opt(secs as u32, nsecs)); + Some(date.and_time(time).and_utc()) + } + + /// Makes a new `DateTime` from the number of non-leap milliseconds + /// since January 1, 1970 0:00:00.000 UTC (aka "UNIX timestamp"). + /// + /// This is guaranteed to round-trip with [`timestamp_millis`](DateTime::timestamp_millis). + /// + /// If you need to create a `DateTime` with a [`TimeZone`] different from [`Utc`], use + /// [`TimeZone::timestamp_millis_opt`] or [`DateTime::with_timezone`]. + /// + /// # Errors + /// + /// Returns `None` on out-of-range number of milliseconds, otherwise returns `Some(DateTime {...})`. + /// + /// # Example + /// + /// ``` + /// use chrono::DateTime; + /// + /// let dt = DateTime::from_timestamp_millis(947638923004).expect("invalid timestamp"); + /// + /// assert_eq!(dt.to_string(), "2000-01-12 01:02:03.004 UTC"); + /// assert_eq!(DateTime::from_timestamp_millis(dt.timestamp_millis()).unwrap(), dt); + /// ``` + #[inline] + #[must_use] + pub const fn from_timestamp_millis(millis: i64) -> Option { + let secs = millis.div_euclid(1000); + let nsecs = millis.rem_euclid(1000) as u32 * 1_000_000; + Self::from_timestamp(secs, nsecs) + } + + /// Creates a new `DateTime` from the number of non-leap microseconds + /// since January 1, 1970 0:00:00.000 UTC (aka "UNIX timestamp"). + /// + /// This is guaranteed to round-trip with [`timestamp_micros`](DateTime::timestamp_micros). + /// + /// If you need to create a `DateTime` with a [`TimeZone`] different from [`Utc`], use + /// [`TimeZone::timestamp_micros`] or [`DateTime::with_timezone`]. + /// + /// # Errors + /// + /// Returns `None` if the number of microseconds would be out of range for a `NaiveDateTime` + /// (more than ca. 262,000 years away from common era) + /// + /// # Example + /// + /// ``` + /// use chrono::DateTime; + /// + /// let timestamp_micros: i64 = 1662921288000000; // Sun, 11 Sep 2022 18:34:48 UTC + /// let dt = DateTime::from_timestamp_micros(timestamp_micros); + /// assert!(dt.is_some()); + /// assert_eq!(timestamp_micros, dt.expect("invalid timestamp").timestamp_micros()); + /// + /// // Negative timestamps (before the UNIX epoch) are supported as well. + /// let timestamp_micros: i64 = -2208936075000000; // Mon, 1 Jan 1900 14:38:45 UTC + /// let dt = DateTime::from_timestamp_micros(timestamp_micros); + /// assert!(dt.is_some()); + /// assert_eq!(timestamp_micros, dt.expect("invalid timestamp").timestamp_micros()); + /// ``` + #[inline] + #[must_use] + pub const fn from_timestamp_micros(micros: i64) -> Option { + let secs = micros.div_euclid(1_000_000); + let nsecs = micros.rem_euclid(1_000_000) as u32 * 1000; + Self::from_timestamp(secs, nsecs) + } + + /// Creates a new [`DateTime`] from the number of non-leap nanoseconds + /// since January 1, 1970 0:00:00.000 UTC (aka "UNIX timestamp"). + /// + /// This is guaranteed to round-trip with [`timestamp_nanos`](DateTime::timestamp_nanos). + /// + /// If you need to create a `DateTime` with a [`TimeZone`] different from [`Utc`], use + /// [`TimeZone::timestamp_nanos`] or [`DateTime::with_timezone`]. + /// + /// The UNIX epoch starts on midnight, January 1, 1970, UTC. + /// + /// An `i64` with nanosecond precision can span a range of ~584 years. Because all values can + /// be represented as a `DateTime` this method never fails. + /// + /// # Example + /// + /// ``` + /// use chrono::DateTime; + /// + /// let timestamp_nanos: i64 = 1662921288_000_000_000; // Sun, 11 Sep 2022 18:34:48 UTC + /// let dt = DateTime::from_timestamp_nanos(timestamp_nanos); + /// assert_eq!(timestamp_nanos, dt.timestamp_nanos_opt().unwrap()); + /// + /// // Negative timestamps (before the UNIX epoch) are supported as well. + /// let timestamp_nanos: i64 = -2208936075_000_000_000; // Mon, 1 Jan 1900 14:38:45 UTC + /// let dt = DateTime::from_timestamp_nanos(timestamp_nanos); + /// assert_eq!(timestamp_nanos, dt.timestamp_nanos_opt().unwrap()); + /// ``` + #[inline] + #[must_use] + pub const fn from_timestamp_nanos(nanos: i64) -> Self { + let secs = nanos.div_euclid(1_000_000_000); + let nsecs = nanos.rem_euclid(1_000_000_000) as u32; + expect(Self::from_timestamp(secs, nsecs), "timestamp in nanos is always in range") + } + + /// The Unix Epoch, 1970-01-01 00:00:00 UTC. + pub const UNIX_EPOCH: Self = Self { datetime: NaiveDateTime::UNIX_EPOCH, offset: Utc }; +} + +impl Default for DateTime { + fn default() -> Self { + Utc.from_utc_datetime(&NaiveDateTime::default()) + } +} + +#[cfg(feature = "clock")] +impl Default for DateTime { + fn default() -> Self { + Local.from_utc_datetime(&NaiveDateTime::default()) + } +} + +impl Default for DateTime { + fn default() -> Self { + FixedOffset::west_opt(0).unwrap().from_utc_datetime(&NaiveDateTime::default()) + } +} + +/// Convert a `DateTime` instance into a `DateTime` instance. +impl From> for DateTime { + /// Convert this `DateTime` instance into a `DateTime` instance. + /// + /// Conversion is done via [`DateTime::with_timezone`]. Note that the converted value returned by + /// this will be created with a fixed timezone offset of 0. + fn from(src: DateTime) -> Self { + src.with_timezone(&FixedOffset::east_opt(0).unwrap()) + } +} + +/// Convert a `DateTime` instance into a `DateTime` instance. +#[cfg(feature = "clock")] +impl From> for DateTime { + /// Convert this `DateTime` instance into a `DateTime` instance. + /// + /// Conversion is performed via [`DateTime::with_timezone`], accounting for the difference in timezones. + fn from(src: DateTime) -> Self { + src.with_timezone(&Local) + } +} + +/// Convert a `DateTime` instance into a `DateTime` instance. +impl From> for DateTime { + /// Convert this `DateTime` instance into a `DateTime` instance. + /// + /// Conversion is performed via [`DateTime::with_timezone`], accounting for the timezone + /// difference. + fn from(src: DateTime) -> Self { + src.with_timezone(&Utc) + } +} + +/// Convert a `DateTime` instance into a `DateTime` instance. +#[cfg(feature = "clock")] +impl From> for DateTime { + /// Convert this `DateTime` instance into a `DateTime` instance. + /// + /// Conversion is performed via [`DateTime::with_timezone`]. Returns the equivalent value in local + /// time. + fn from(src: DateTime) -> Self { + src.with_timezone(&Local) + } +} + +/// Convert a `DateTime` instance into a `DateTime` instance. +#[cfg(feature = "clock")] +impl From> for DateTime { + /// Convert this `DateTime` instance into a `DateTime` instance. + /// + /// Conversion is performed via [`DateTime::with_timezone`], accounting for the difference in + /// timezones. + fn from(src: DateTime) -> Self { + src.with_timezone(&Utc) + } +} + +/// Convert a `DateTime` instance into a `DateTime` instance. +#[cfg(feature = "clock")] +impl From> for DateTime { + /// Convert this `DateTime` instance into a `DateTime` instance. + /// + /// Conversion is performed via [`DateTime::with_timezone`]. + fn from(src: DateTime) -> Self { + src.with_timezone(&src.offset().fix()) + } +} + +/// Maps the local datetime to other datetime with given conversion function. +fn map_local(dt: &DateTime, mut f: F) -> Option> +where + F: FnMut(NaiveDateTime) -> Option, +{ + f(dt.overflowing_naive_local()) + .and_then(|datetime| dt.timezone().from_local_datetime(&datetime).single()) + .filter(|dt| dt >= &DateTime::::MIN_UTC && dt <= &DateTime::::MAX_UTC) +} + +impl DateTime { + /// Parses an RFC 2822 date-and-time string into a `DateTime` value. + /// + /// This parses valid RFC 2822 datetime strings (such as `Tue, 1 Jul 2003 10:52:37 +0200`) + /// and returns a new [`DateTime`] instance with the parsed timezone as the [`FixedOffset`]. + /// + /// RFC 2822 is the internet message standard that specifies the representation of times in HTTP + /// and email headers. It is the 2001 revision of RFC 822, and is itself revised as RFC 5322 in + /// 2008. + /// + /// # Support for the obsolete date format + /// + /// - A 2-digit year is interpreted to be a year in 1950-2049. + /// - The standard allows comments and whitespace between many of the tokens. See [4.3] and + /// [Appendix A.5] + /// - Single letter 'military' time zone names are parsed as a `-0000` offset. + /// They were defined with the wrong sign in RFC 822 and corrected in RFC 2822. But because + /// the meaning is now ambiguous, the standard says they should be be considered as `-0000` + /// unless there is out-of-band information confirming their meaning. + /// The exception is `Z`, which remains identical to `+0000`. + /// + /// [4.3]: https://www.rfc-editor.org/rfc/rfc2822#section-4.3 + /// [Appendix A.5]: https://www.rfc-editor.org/rfc/rfc2822#appendix-A.5 + /// + /// # Example + /// + /// ``` + /// # use chrono::{DateTime, FixedOffset, TimeZone}; + /// assert_eq!( + /// DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 GMT").unwrap(), + /// FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap() + /// ); + /// ``` + pub fn parse_from_rfc2822(s: &str) -> ParseResult> { + const ITEMS: &[Item<'static>] = &[Item::Fixed(Fixed::RFC2822)]; + let mut parsed = Parsed::new(); + parse(&mut parsed, s, ITEMS.iter())?; + parsed.to_datetime() + } + + /// Parses an RFC 3339 date-and-time string into a `DateTime` value. + /// + /// Parses all valid RFC 3339 values (as well as the subset of valid ISO 8601 values that are + /// also valid RFC 3339 date-and-time values) and returns a new [`DateTime`] with a + /// [`FixedOffset`] corresponding to the parsed timezone. While RFC 3339 values come in a wide + /// variety of shapes and sizes, `1996-12-19T16:39:57-08:00` is an example of the most commonly + /// encountered variety of RFC 3339 formats. + /// + /// Why isn't this named `parse_from_iso8601`? That's because ISO 8601 allows representing + /// values in a wide range of formats, only some of which represent actual date-and-time + /// instances (rather than periods, ranges, dates, or times). Some valid ISO 8601 values are + /// also simultaneously valid RFC 3339 values, but not all RFC 3339 values are valid ISO 8601 + /// values (or the other way around). + pub fn parse_from_rfc3339(s: &str) -> ParseResult> { + let mut parsed = Parsed::new(); + let (s, _) = parse_rfc3339(&mut parsed, s)?; + if !s.is_empty() { + return Err(TOO_LONG); + } + parsed.to_datetime() + } + + /// Parses a string from a user-specified format into a `DateTime` value. + /// + /// Note that this method *requires a timezone* in the input string. See + /// [`NaiveDateTime::parse_from_str`](./naive/struct.NaiveDateTime.html#method.parse_from_str) + /// for a version that does not require a timezone in the to-be-parsed str. The returned + /// [`DateTime`] value will have a [`FixedOffset`] reflecting the parsed timezone. + /// + /// See the [`format::strftime` module](crate::format::strftime) for supported format + /// sequences. + /// + /// # Example + /// + /// ```rust + /// use chrono::{DateTime, FixedOffset, NaiveDate, TimeZone}; + /// + /// let dt = DateTime::parse_from_str("1983 Apr 13 12:09:14.274 +0000", "%Y %b %d %H:%M:%S%.3f %z"); + /// assert_eq!( + /// dt, + /// Ok(FixedOffset::east_opt(0) + /// .unwrap() + /// .from_local_datetime( + /// &NaiveDate::from_ymd_opt(1983, 4, 13) + /// .unwrap() + /// .and_hms_milli_opt(12, 9, 14, 274) + /// .unwrap() + /// ) + /// .unwrap()) + /// ); + /// ``` + pub fn parse_from_str(s: &str, fmt: &str) -> ParseResult> { + let mut parsed = Parsed::new(); + parse(&mut parsed, s, StrftimeItems::new(fmt))?; + parsed.to_datetime() + } + + /// Parses a string from a user-specified format into a `DateTime` value, and a + /// slice with the remaining portion of the string. + /// + /// Note that this method *requires a timezone* in the input string. See + /// [`NaiveDateTime::parse_and_remainder`] for a version that does not + /// require a timezone in `s`. The returned [`DateTime`] value will have a [`FixedOffset`] + /// reflecting the parsed timezone. + /// + /// See the [`format::strftime` module](./format/strftime/index.html) for supported format + /// sequences. + /// + /// Similar to [`parse_from_str`](#method.parse_from_str). + /// + /// # Example + /// + /// ```rust + /// # use chrono::{DateTime, FixedOffset, TimeZone}; + /// let (datetime, remainder) = DateTime::parse_and_remainder( + /// "2015-02-18 23:16:09 +0200 trailing text", + /// "%Y-%m-%d %H:%M:%S %z", + /// ) + /// .unwrap(); + /// assert_eq!( + /// datetime, + /// FixedOffset::east_opt(2 * 3600).unwrap().with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap() + /// ); + /// assert_eq!(remainder, " trailing text"); + /// ``` + pub fn parse_and_remainder<'a>( + s: &'a str, + fmt: &str, + ) -> ParseResult<(DateTime, &'a str)> { + let mut parsed = Parsed::new(); + let remainder = parse_and_remainder(&mut parsed, s, StrftimeItems::new(fmt))?; + parsed.to_datetime().map(|d| (d, remainder)) + } +} + +impl DateTime +where + Tz::Offset: fmt::Display, +{ + /// Formats the combined date and time with the specified formatting items. + #[cfg(feature = "alloc")] + #[inline] + #[must_use] + pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat + where + I: Iterator + Clone, + B: Borrow>, + { + let local = self.overflowing_naive_local(); + DelayedFormat::new_with_offset(Some(local.date()), Some(local.time()), &self.offset, items) + } + + /// Formats the combined date and time per the specified format string. + /// + /// See the [`crate::format::strftime`] module for the supported escape sequences. + /// + /// # Example + /// ```rust + /// use chrono::prelude::*; + /// + /// let date_time: DateTime = Utc.with_ymd_and_hms(2017, 04, 02, 12, 50, 32).unwrap(); + /// let formatted = format!("{}", date_time.format("%d/%m/%Y %H:%M")); + /// assert_eq!(formatted, "02/04/2017 12:50"); + /// ``` + #[cfg(feature = "alloc")] + #[inline] + #[must_use] + pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { + self.format_with_items(StrftimeItems::new(fmt)) + } + + /// Formats the combined date and time with the specified formatting items and locale. + #[cfg(all(feature = "unstable-locales", feature = "alloc"))] + #[inline] + #[must_use] + pub fn format_localized_with_items<'a, I, B>( + &self, + items: I, + locale: Locale, + ) -> DelayedFormat + where + I: Iterator + Clone, + B: Borrow>, + { + let local = self.overflowing_naive_local(); + DelayedFormat::new_with_offset_and_locale( + Some(local.date()), + Some(local.time()), + &self.offset, + items, + locale, + ) + } + + /// Formats the combined date and time per the specified format string and + /// locale. + /// + /// See the [`crate::format::strftime`] module on the supported escape + /// sequences. + #[cfg(all(feature = "unstable-locales", feature = "alloc"))] + #[inline] + #[must_use] + pub fn format_localized<'a>( + &self, + fmt: &'a str, + locale: Locale, + ) -> DelayedFormat> { + self.format_localized_with_items(StrftimeItems::new_with_locale(fmt, locale), locale) + } +} + +impl Datelike for DateTime { + #[inline] + fn year(&self) -> i32 { + self.overflowing_naive_local().year() + } + #[inline] + fn month(&self) -> u32 { + self.overflowing_naive_local().month() + } + #[inline] + fn month0(&self) -> u32 { + self.overflowing_naive_local().month0() + } + #[inline] + fn day(&self) -> u32 { + self.overflowing_naive_local().day() + } + #[inline] + fn day0(&self) -> u32 { + self.overflowing_naive_local().day0() + } + #[inline] + fn ordinal(&self) -> u32 { + self.overflowing_naive_local().ordinal() + } + #[inline] + fn ordinal0(&self) -> u32 { + self.overflowing_naive_local().ordinal0() + } + #[inline] + fn weekday(&self) -> Weekday { + self.overflowing_naive_local().weekday() + } + #[inline] + fn iso_week(&self) -> IsoWeek { + self.overflowing_naive_local().iso_week() + } + + #[inline] + /// Makes a new `DateTime` with the year number changed, while keeping the same month and day. + /// + /// See also the [`NaiveDate::with_year`] method. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist (February 29 in a non-leap year). + /// - The local time at the resulting date does not exist or is ambiguous, for example during a + /// daylight saving time transition. + /// - The resulting UTC datetime would be out of range. + /// - The resulting local datetime would be out of range (unless the year remains the same). + fn with_year(&self, year: i32) -> Option> { + map_local(self, |dt| match dt.year() == year { + true => Some(dt), + false => dt.with_year(year), + }) + } + + /// Makes a new `DateTime` with the month number (starting from 1) changed. + /// + /// Don't combine multiple `Datelike::with_*` methods. The intermediate value may not exist. + /// + /// See also the [`NaiveDate::with_month`] method. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist (for example `month(4)` when day of the month is 31). + /// - The value for `month` is invalid. + /// - The local time at the resulting date does not exist or is ambiguous, for example during a + /// daylight saving time transition. + #[inline] + fn with_month(&self, month: u32) -> Option> { + map_local(self, |datetime| datetime.with_month(month)) + } + + /// Makes a new `DateTime` with the month number (starting from 0) changed. + /// + /// See also the [`NaiveDate::with_month0`] method. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist (for example `month0(3)` when day of the month is 31). + /// - The value for `month0` is invalid. + /// - The local time at the resulting date does not exist or is ambiguous, for example during a + /// daylight saving time transition. + #[inline] + fn with_month0(&self, month0: u32) -> Option> { + map_local(self, |datetime| datetime.with_month0(month0)) + } + + /// Makes a new `DateTime` with the day of month (starting from 1) changed. + /// + /// See also the [`NaiveDate::with_day`] method. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist (for example `day(31)` in April). + /// - The value for `day` is invalid. + /// - The local time at the resulting date does not exist or is ambiguous, for example during a + /// daylight saving time transition. + #[inline] + fn with_day(&self, day: u32) -> Option> { + map_local(self, |datetime| datetime.with_day(day)) + } + + /// Makes a new `DateTime` with the day of month (starting from 0) changed. + /// + /// See also the [`NaiveDate::with_day0`] method. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist (for example `day(30)` in April). + /// - The value for `day0` is invalid. + /// - The local time at the resulting date does not exist or is ambiguous, for example during a + /// daylight saving time transition. + #[inline] + fn with_day0(&self, day0: u32) -> Option> { + map_local(self, |datetime| datetime.with_day0(day0)) + } + + /// Makes a new `DateTime` with the day of year (starting from 1) changed. + /// + /// See also the [`NaiveDate::with_ordinal`] method. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist (`with_ordinal(366)` in a non-leap year). + /// - The value for `ordinal` is invalid. + /// - The local time at the resulting date does not exist or is ambiguous, for example during a + /// daylight saving time transition. + #[inline] + fn with_ordinal(&self, ordinal: u32) -> Option> { + map_local(self, |datetime| datetime.with_ordinal(ordinal)) + } + + /// Makes a new `DateTime` with the day of year (starting from 0) changed. + /// + /// See also the [`NaiveDate::with_ordinal0`] method. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist (`with_ordinal0(365)` in a non-leap year). + /// - The value for `ordinal0` is invalid. + /// - The local time at the resulting date does not exist or is ambiguous, for example during a + /// daylight saving time transition. + #[inline] + fn with_ordinal0(&self, ordinal0: u32) -> Option> { + map_local(self, |datetime| datetime.with_ordinal0(ordinal0)) + } +} + +impl Timelike for DateTime { + #[inline] + fn hour(&self) -> u32 { + self.overflowing_naive_local().hour() + } + #[inline] + fn minute(&self) -> u32 { + self.overflowing_naive_local().minute() + } + #[inline] + fn second(&self) -> u32 { + self.overflowing_naive_local().second() + } + #[inline] + fn nanosecond(&self) -> u32 { + self.overflowing_naive_local().nanosecond() + } + + /// Makes a new `DateTime` with the hour number changed. + /// + /// See also the [`NaiveTime::with_hour`] method. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The value for `hour` is invalid. + /// - The local time at the resulting date does not exist or is ambiguous, for example during a + /// daylight saving time transition. + #[inline] + fn with_hour(&self, hour: u32) -> Option> { + map_local(self, |datetime| datetime.with_hour(hour)) + } + + /// Makes a new `DateTime` with the minute number changed. + /// + /// See also the [`NaiveTime::with_minute`] method. + /// + /// # Errors + /// + /// - The value for `minute` is invalid. + /// - The local time at the resulting date does not exist or is ambiguous, for example during a + /// daylight saving time transition. + #[inline] + fn with_minute(&self, min: u32) -> Option> { + map_local(self, |datetime| datetime.with_minute(min)) + } + + /// Makes a new `DateTime` with the second number changed. + /// + /// As with the [`second`](#method.second) method, + /// the input range is restricted to 0 through 59. + /// + /// See also the [`NaiveTime::with_second`] method. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The value for `second` is invalid. + /// - The local time at the resulting date does not exist or is ambiguous, for example during a + /// daylight saving time transition. + #[inline] + fn with_second(&self, sec: u32) -> Option> { + map_local(self, |datetime| datetime.with_second(sec)) + } + + /// Makes a new `DateTime` with nanoseconds since the whole non-leap second changed. + /// + /// Returns `None` when the resulting `NaiveDateTime` would be invalid. + /// As with the [`NaiveDateTime::nanosecond`] method, + /// the input range can exceed 1,000,000,000 for leap seconds. + /// + /// See also the [`NaiveTime::with_nanosecond`] method. + /// + /// # Errors + /// + /// Returns `None` if `nanosecond >= 2,000,000,000`. + #[inline] + fn with_nanosecond(&self, nano: u32) -> Option> { + map_local(self, |datetime| datetime.with_nanosecond(nano)) + } +} + +// We don't store a field with the `Tz` type, so it doesn't need to influence whether `DateTime` can +// be `Copy`. Implement it manually if the two types we do have are `Copy`. +impl Copy for DateTime +where + ::Offset: Copy, + NaiveDateTime: Copy, +{ +} + +impl PartialEq> for DateTime { + fn eq(&self, other: &DateTime) -> bool { + self.datetime == other.datetime + } +} + +impl Eq for DateTime {} + +impl PartialOrd> for DateTime { + /// Compare two DateTimes based on their true time, ignoring time zones + /// + /// # Example + /// + /// ``` + /// use chrono::prelude::*; + /// + /// let earlier = Utc + /// .with_ymd_and_hms(2015, 5, 15, 2, 0, 0) + /// .unwrap() + /// .with_timezone(&FixedOffset::west_opt(1 * 3600).unwrap()); + /// let later = Utc + /// .with_ymd_and_hms(2015, 5, 15, 3, 0, 0) + /// .unwrap() + /// .with_timezone(&FixedOffset::west_opt(5 * 3600).unwrap()); + /// + /// assert_eq!(earlier.to_string(), "2015-05-15 01:00:00 -01:00"); + /// assert_eq!(later.to_string(), "2015-05-14 22:00:00 -05:00"); + /// + /// assert!(later > earlier); + /// ``` + fn partial_cmp(&self, other: &DateTime) -> Option { + self.datetime.partial_cmp(&other.datetime) + } +} + +impl Ord for DateTime { + fn cmp(&self, other: &DateTime) -> Ordering { + self.datetime.cmp(&other.datetime) + } +} + +impl hash::Hash for DateTime { + fn hash(&self, state: &mut H) { + self.datetime.hash(state) + } +} + +/// Add `TimeDelta` to `DateTime`. +/// +/// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap +/// second ever**, except when the `NaiveDateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`DateTime::checked_add_signed`] to get an `Option` instead. +impl Add for DateTime { + type Output = DateTime; + + #[inline] + fn add(self, rhs: TimeDelta) -> DateTime { + self.checked_add_signed(rhs).expect("`DateTime + TimeDelta` overflowed") + } +} + +/// Add `std::time::Duration` to `DateTime`. +/// +/// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap +/// second ever**, except when the `NaiveDateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`DateTime::checked_add_signed`] to get an `Option` instead. +impl Add for DateTime { + type Output = DateTime; + + #[inline] + fn add(self, rhs: Duration) -> DateTime { + let rhs = TimeDelta::from_std(rhs) + .expect("overflow converting from core::time::Duration to TimeDelta"); + self.checked_add_signed(rhs).expect("`DateTime + TimeDelta` overflowed") + } +} + +/// Add-assign `chrono::Duration` to `DateTime`. +/// +/// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap +/// second ever**, except when the `NaiveDateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`DateTime::checked_add_signed`] to get an `Option` instead. +impl AddAssign for DateTime { + #[inline] + fn add_assign(&mut self, rhs: TimeDelta) { + let datetime = + self.datetime.checked_add_signed(rhs).expect("`DateTime + TimeDelta` overflowed"); + let tz = self.timezone(); + *self = tz.from_utc_datetime(&datetime); + } +} + +/// Add-assign `std::time::Duration` to `DateTime`. +/// +/// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap +/// second ever**, except when the `NaiveDateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`DateTime::checked_add_signed`] to get an `Option` instead. +impl AddAssign for DateTime { + #[inline] + fn add_assign(&mut self, rhs: Duration) { + let rhs = TimeDelta::from_std(rhs) + .expect("overflow converting from core::time::Duration to TimeDelta"); + *self += rhs; + } +} + +/// Add `FixedOffset` to the datetime value of `DateTime` (offset remains unchanged). +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +impl Add for DateTime { + type Output = DateTime; + + #[inline] + fn add(mut self, rhs: FixedOffset) -> DateTime { + self.datetime = + self.naive_utc().checked_add_offset(rhs).expect("`DateTime + FixedOffset` overflowed"); + self + } +} + +/// Add `Months` to `DateTime`. +/// +/// The result will be clamped to valid days in the resulting month, see `checked_add_months` for +/// details. +/// +/// # Panics +/// +/// Panics if: +/// - The resulting date would be out of range. +/// - The local time at the resulting date does not exist or is ambiguous, for example during a +/// daylight saving time transition. +/// +/// Strongly consider using [`DateTime::checked_add_months`] to get an `Option` instead. +impl Add for DateTime { + type Output = DateTime; + + fn add(self, rhs: Months) -> Self::Output { + self.checked_add_months(rhs).expect("`DateTime + Months` out of range") + } +} + +/// Subtract `TimeDelta` from `DateTime`. +/// +/// This is the same as the addition with a negated `TimeDelta`. +/// +/// As a part of Chrono's [leap second handling] the subtraction assumes that **there is no leap +/// second ever**, except when the `DateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`DateTime::checked_sub_signed`] to get an `Option` instead. +impl Sub for DateTime { + type Output = DateTime; + + #[inline] + fn sub(self, rhs: TimeDelta) -> DateTime { + self.checked_sub_signed(rhs).expect("`DateTime - TimeDelta` overflowed") + } +} + +/// Subtract `std::time::Duration` from `DateTime`. +/// +/// As a part of Chrono's [leap second handling] the subtraction assumes that **there is no leap +/// second ever**, except when the `DateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`DateTime::checked_sub_signed`] to get an `Option` instead. +impl Sub for DateTime { + type Output = DateTime; + + #[inline] + fn sub(self, rhs: Duration) -> DateTime { + let rhs = TimeDelta::from_std(rhs) + .expect("overflow converting from core::time::Duration to TimeDelta"); + self.checked_sub_signed(rhs).expect("`DateTime - TimeDelta` overflowed") + } +} + +/// Subtract-assign `TimeDelta` from `DateTime`. +/// +/// This is the same as the addition with a negated `TimeDelta`. +/// +/// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap +/// second ever**, except when the `DateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`DateTime::checked_sub_signed`] to get an `Option` instead. +impl SubAssign for DateTime { + #[inline] + fn sub_assign(&mut self, rhs: TimeDelta) { + let datetime = + self.datetime.checked_sub_signed(rhs).expect("`DateTime - TimeDelta` overflowed"); + let tz = self.timezone(); + *self = tz.from_utc_datetime(&datetime) + } +} + +/// Subtract-assign `std::time::Duration` from `DateTime`. +/// +/// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap +/// second ever**, except when the `DateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`DateTime::checked_sub_signed`] to get an `Option` instead. +impl SubAssign for DateTime { + #[inline] + fn sub_assign(&mut self, rhs: Duration) { + let rhs = TimeDelta::from_std(rhs) + .expect("overflow converting from core::time::Duration to TimeDelta"); + *self -= rhs; + } +} + +/// Subtract `FixedOffset` from the datetime value of `DateTime` (offset remains unchanged). +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +impl Sub for DateTime { + type Output = DateTime; + + #[inline] + fn sub(mut self, rhs: FixedOffset) -> DateTime { + self.datetime = + self.naive_utc().checked_sub_offset(rhs).expect("`DateTime - FixedOffset` overflowed"); + self + } +} + +/// Subtract `Months` from `DateTime`. +/// +/// The result will be clamped to valid days in the resulting month, see +/// [`DateTime::checked_sub_months`] for details. +/// +/// # Panics +/// +/// Panics if: +/// - The resulting date would be out of range. +/// - The local time at the resulting date does not exist or is ambiguous, for example during a +/// daylight saving time transition. +/// +/// Strongly consider using [`DateTime::checked_sub_months`] to get an `Option` instead. +impl Sub for DateTime { + type Output = DateTime; + + fn sub(self, rhs: Months) -> Self::Output { + self.checked_sub_months(rhs).expect("`DateTime - Months` out of range") + } +} + +impl Sub> for DateTime { + type Output = TimeDelta; + + #[inline] + fn sub(self, rhs: DateTime) -> TimeDelta { + self.signed_duration_since(rhs) + } +} + +impl Sub<&DateTime> for DateTime { + type Output = TimeDelta; + + #[inline] + fn sub(self, rhs: &DateTime) -> TimeDelta { + self.signed_duration_since(rhs) + } +} + +/// Add `Days` to `NaiveDateTime`. +/// +/// # Panics +/// +/// Panics if: +/// - The resulting date would be out of range. +/// - The local time at the resulting date does not exist or is ambiguous, for example during a +/// daylight saving time transition. +/// +/// Strongly consider using `DateTime::checked_add_days` to get an `Option` instead. +impl Add for DateTime { + type Output = DateTime; + + fn add(self, days: Days) -> Self::Output { + self.checked_add_days(days).expect("`DateTime + Days` out of range") + } +} + +/// Subtract `Days` from `DateTime`. +/// +/// # Panics +/// +/// Panics if: +/// - The resulting date would be out of range. +/// - The local time at the resulting date does not exist or is ambiguous, for example during a +/// daylight saving time transition. +/// +/// Strongly consider using `DateTime::checked_sub_days` to get an `Option` instead. +impl Sub for DateTime { + type Output = DateTime; + + fn sub(self, days: Days) -> Self::Output { + self.checked_sub_days(days).expect("`DateTime - Days` out of range") + } +} + +impl fmt::Debug for DateTime { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.overflowing_naive_local().fmt(f)?; + self.offset.fmt(f) + } +} + +// `fmt::Debug` is hand implemented for the `rkyv::Archive` variant of `DateTime` because +// deriving a trait recursively does not propagate trait defined associated types with their own +// constraints: +// In our case `<::Offset as Archive>::Archived` +// cannot be formatted using `{:?}` because it doesn't implement `Debug`. +// See below for further discussion: +// * https://github.com/rust-lang/rust/issues/26925 +// * https://github.com/rkyv/rkyv/issues/333 +// * https://github.com/dtolnay/syn/issues/370 +#[cfg(feature = "rkyv-validation")] +impl fmt::Debug for ArchivedDateTime +where + Tz: Archive, + ::Archived: fmt::Debug, + <::Offset as Archive>::Archived: fmt::Debug, + ::Offset: fmt::Debug + Archive, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("ArchivedDateTime") + .field("datetime", &self.datetime) + .field("offset", &self.offset) + .finish() + } +} + +impl fmt::Display for DateTime +where + Tz::Offset: fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.overflowing_naive_local().fmt(f)?; + f.write_char(' ')?; + self.offset.fmt(f) + } +} + +/// Accepts a relaxed form of RFC3339. +/// A space or a 'T' are accepted as the separator between the date and time +/// parts. +/// +/// All of these examples are equivalent: +/// ``` +/// # use chrono::{DateTime, Utc}; +/// "2012-12-12T12:12:12Z".parse::>()?; +/// "2012-12-12 12:12:12Z".parse::>()?; +/// "2012-12-12 12:12:12+0000".parse::>()?; +/// "2012-12-12 12:12:12+00:00".parse::>()?; +/// # Ok::<(), chrono::ParseError>(()) +/// ``` +impl str::FromStr for DateTime { + type Err = ParseError; + + fn from_str(s: &str) -> ParseResult> { + s.parse::>().map(|dt| dt.with_timezone(&Utc)) + } +} + +/// Accepts a relaxed form of RFC3339. +/// A space or a 'T' are accepted as the separator between the date and time +/// parts. +/// +/// All of these examples are equivalent: +/// ``` +/// # use chrono::{DateTime, Local}; +/// "2012-12-12T12:12:12Z".parse::>()?; +/// "2012-12-12 12:12:12Z".parse::>()?; +/// "2012-12-12 12:12:12+0000".parse::>()?; +/// "2012-12-12 12:12:12+00:00".parse::>()?; +/// # Ok::<(), chrono::ParseError>(()) +/// ``` +#[cfg(feature = "clock")] +impl str::FromStr for DateTime { + type Err = ParseError; + + fn from_str(s: &str) -> ParseResult> { + s.parse::>().map(|dt| dt.with_timezone(&Local)) + } +} + +#[cfg(feature = "std")] +impl From for DateTime { + fn from(t: SystemTime) -> DateTime { + let (sec, nsec) = match t.duration_since(UNIX_EPOCH) { + Ok(dur) => (dur.as_secs() as i64, dur.subsec_nanos()), + Err(e) => { + // unlikely but should be handled + let dur = e.duration(); + let (sec, nsec) = (dur.as_secs() as i64, dur.subsec_nanos()); + if nsec == 0 { (-sec, 0) } else { (-sec - 1, 1_000_000_000 - nsec) } + } + }; + Utc.timestamp_opt(sec, nsec).unwrap() + } +} + +#[cfg(feature = "clock")] +impl From for DateTime { + fn from(t: SystemTime) -> DateTime { + DateTime::::from(t).with_timezone(&Local) + } +} + +#[cfg(feature = "std")] +impl From> for SystemTime { + fn from(dt: DateTime) -> SystemTime { + let sec = dt.timestamp(); + let nsec = dt.timestamp_subsec_nanos(); + if sec < 0 { + // unlikely but should be handled + UNIX_EPOCH - Duration::new(-sec as u64, 0) + Duration::new(0, nsec) + } else { + UNIX_EPOCH + Duration::new(sec as u64, nsec) + } + } +} + +#[cfg(all( + target_arch = "wasm32", + feature = "wasmbind", + not(any(target_os = "emscripten", target_os = "wasi")) +))] +impl From for DateTime { + fn from(date: js_sys::Date) -> DateTime { + DateTime::::from(&date) + } +} + +#[cfg(all( + target_arch = "wasm32", + feature = "wasmbind", + not(any(target_os = "emscripten", target_os = "wasi")) +))] +impl From<&js_sys::Date> for DateTime { + fn from(date: &js_sys::Date) -> DateTime { + Utc.timestamp_millis_opt(date.get_time() as i64).unwrap() + } +} + +#[cfg(all( + target_arch = "wasm32", + feature = "wasmbind", + not(any(target_os = "emscripten", target_os = "wasi")) +))] +impl From> for js_sys::Date { + /// Converts a `DateTime` to a JS `Date`. The resulting value may be lossy, + /// any values that have a millisecond timestamp value greater/less than ±8,640,000,000,000,000 + /// (April 20, 271821 BCE ~ September 13, 275760 CE) will become invalid dates in JS. + fn from(date: DateTime) -> js_sys::Date { + let js_millis = wasm_bindgen::JsValue::from_f64(date.timestamp_millis() as f64); + js_sys::Date::new(&js_millis) + } +} + +// Note that implementation of Arbitrary cannot be simply derived for DateTime, due to +// the nontrivial bound ::Offset: Arbitrary. +#[cfg(all(feature = "arbitrary", feature = "std"))] +impl<'a, Tz> arbitrary::Arbitrary<'a> for DateTime +where + Tz: TimeZone, + ::Offset: arbitrary::Arbitrary<'a>, +{ + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result> { + let datetime = NaiveDateTime::arbitrary(u)?; + let offset = ::Offset::arbitrary(u)?; + Ok(DateTime::from_naive_utc_and_offset(datetime, offset)) + } +} + +/// Number of days between Januari 1, 1970 and December 31, 1 BCE which we define to be day 0. +/// 4 full leap year cycles until December 31, 1600 4 * 146097 = 584388 +/// 1 day until January 1, 1601 1 +/// 369 years until Januari 1, 1970 369 * 365 = 134685 +/// of which floor(369 / 4) are leap years floor(369 / 4) = 92 +/// except for 1700, 1800 and 1900 -3 + +/// -------- +/// 719163 +const UNIX_EPOCH_DAY: i64 = 719_163; diff --git a/third_party/rust/chrono/src/datetime/serde.rs b/third_party/rust/chrono/src/datetime/serde.rs new file mode 100644 index 00000000000..1492fe3f0bf --- /dev/null +++ b/third_party/rust/chrono/src/datetime/serde.rs @@ -0,0 +1,1359 @@ +use core::fmt; +use serde::{de, ser}; + +use super::DateTime; +use crate::format::{SecondsFormat, write_rfc3339}; +#[cfg(feature = "clock")] +use crate::offset::Local; +use crate::offset::{FixedOffset, Offset, TimeZone, Utc}; + +#[doc(hidden)] +#[derive(Debug)] +pub struct SecondsTimestampVisitor; + +#[doc(hidden)] +#[derive(Debug)] +pub struct NanoSecondsTimestampVisitor; + +#[doc(hidden)] +#[derive(Debug)] +pub struct MicroSecondsTimestampVisitor; + +#[doc(hidden)] +#[derive(Debug)] +pub struct MilliSecondsTimestampVisitor; + +/// Serialize to an RFC 3339 formatted string +/// +/// As an extension to RFC 3339 this can serialize `DateTime`s outside the range of 0-9999 years +/// using an ISO 8601 syntax (which prepends an `-` or `+`). +/// +/// See [the `serde` module](crate::serde) for alternate serializations. +impl ser::Serialize for DateTime { + fn serialize(&self, serializer: S) -> Result + where + S: ser::Serializer, + { + struct FormatIso8601<'a, Tz: TimeZone> { + inner: &'a DateTime, + } + + impl fmt::Display for FormatIso8601<'_, Tz> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let naive = self.inner.naive_local(); + let offset = self.inner.offset.fix(); + write_rfc3339(f, naive, offset, SecondsFormat::AutoSi, true) + } + } + + serializer.collect_str(&FormatIso8601 { inner: self }) + } +} + +struct DateTimeVisitor; + +impl de::Visitor<'_> for DateTimeVisitor { + type Value = DateTime; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("an RFC 3339 formatted date and time string") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + value.parse().map_err(E::custom) + } +} + +/// Deserialize an RFC 3339 formatted string into a `DateTime` +/// +/// As an extension to RFC 3339 this can deserialize to `DateTime`s outside the range of 0-9999 +/// years using an ISO 8601 syntax (which prepends an `-` or `+`). +/// +/// See [the `serde` module](crate::serde) for alternate deserialization formats. +impl<'de> de::Deserialize<'de> for DateTime { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + deserializer.deserialize_str(DateTimeVisitor) + } +} + +/// Deserialize an RFC 3339 formatted string into a `DateTime` +/// +/// If the value contains an offset from UTC that is not zero, the value will be converted to UTC. +/// +/// As an extension to RFC 3339 this can deserialize to `DateTime`s outside the range of 0-9999 +/// years using an ISO 8601 syntax (which prepends an `-` or `+`). +/// +/// See [the `serde` module](crate::serde) for alternate deserialization formats. +impl<'de> de::Deserialize<'de> for DateTime { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + deserializer.deserialize_str(DateTimeVisitor).map(|dt| dt.with_timezone(&Utc)) + } +} + +/// Deserialize an RFC 3339 formatted string into a `DateTime` +/// +/// The value will remain the same instant in UTC, but the offset will be recalculated to match +/// that of the `Local` platform time zone. +/// +/// As an extension to RFC 3339 this can deserialize to `DateTime`s outside the range of 0-9999 +/// years using an ISO 8601 syntax (which prepends an `-` or `+`). +/// +/// See [the `serde` module](crate::serde) for alternate deserialization formats. +#[cfg(feature = "clock")] +impl<'de> de::Deserialize<'de> for DateTime { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + deserializer.deserialize_str(DateTimeVisitor).map(|dt| dt.with_timezone(&Local)) + } +} + +/// Ser/de to/from timestamps in nanoseconds +/// +/// Intended for use with `serde`'s `with` attribute. +/// +/// # Example: +/// +/// ```rust +/// # use chrono::{DateTime, Utc, NaiveDate}; +/// # use serde_derive::{Deserialize, Serialize}; +/// use chrono::serde::ts_nanoseconds; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_nanoseconds")] +/// time: DateTime, +/// } +/// +/// let time = NaiveDate::from_ymd_opt(2018, 5, 17) +/// .unwrap() +/// .and_hms_nano_opt(02, 04, 59, 918355733) +/// .unwrap() +/// .and_utc(); +/// let my_s = S { time: time.clone() }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok::<(), serde_json::Error>(()) +/// ``` +pub mod ts_nanoseconds { + use core::fmt; + use serde::{de, ser}; + + use crate::serde::invalid_ts; + use crate::{DateTime, Utc}; + + use super::NanoSecondsTimestampVisitor; + + /// Serialize a UTC datetime into an integer number of nanoseconds since the epoch + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Errors + /// + /// An `i64` with nanosecond precision can span a range of ~584 years. This function returns an + /// error on an out of range `DateTime`. + /// + /// The dates that can be represented as nanoseconds are between 1677-09-21T00:12:44.0 and + /// 2262-04-11T23:47:16.854775804. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{DateTime, Utc, NaiveDate}; + /// # use serde_derive::Serialize; + /// use chrono::serde::ts_nanoseconds::serialize as to_nano_ts; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_nano_ts")] + /// time: DateTime, + /// } + /// + /// let my_s = S { + /// time: NaiveDate::from_ymd_opt(2018, 5, 17) + /// .unwrap() + /// .and_hms_nano_opt(02, 04, 59, 918355733) + /// .unwrap() + /// .and_utc(), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn serialize(dt: &DateTime, serializer: S) -> Result + where + S: ser::Serializer, + { + serializer.serialize_i64(dt.timestamp_nanos_opt().ok_or(ser::Error::custom( + "value out of range for a timestamp with nanosecond precision", + ))?) + } + + /// Deserialize a [`DateTime`] from a nanosecond timestamp + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{DateTime, TimeZone, Utc}; + /// # use serde_derive::Deserialize; + /// use chrono::serde::ts_nanoseconds::deserialize as from_nano_ts; + /// #[derive(Debug, PartialEq, Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_nano_ts")] + /// time: DateTime, + /// } + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355733 }"#)?; + /// assert_eq!(my_s, S { time: Utc.timestamp_opt(1526522699, 918355733).unwrap() }); + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": -1 }"#)?; + /// assert_eq!(my_s, S { time: Utc.timestamp_opt(-1, 999_999_999).unwrap() }); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result, D::Error> + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(NanoSecondsTimestampVisitor) + } + + impl de::Visitor<'_> for NanoSecondsTimestampVisitor { + type Value = DateTime; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp in nanoseconds") + } + + /// Deserialize a timestamp in nanoseconds since the epoch + fn visit_i64(self, value: i64) -> Result + where + E: de::Error, + { + DateTime::from_timestamp( + value.div_euclid(1_000_000_000), + (value.rem_euclid(1_000_000_000)) as u32, + ) + .ok_or_else(|| invalid_ts(value)) + } + + /// Deserialize a timestamp in nanoseconds since the epoch + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + DateTime::from_timestamp((value / 1_000_000_000) as i64, (value % 1_000_000_000) as u32) + .ok_or_else(|| invalid_ts(value)) + } + } +} + +/// Ser/de to/from optional timestamps in nanoseconds +/// +/// Intended for use with `serde`'s `with` attribute. +/// +/// # Example: +/// +/// ```rust +/// # use chrono::{DateTime, Utc, NaiveDate}; +/// # use serde_derive::{Deserialize, Serialize}; +/// use chrono::serde::ts_nanoseconds_option; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_nanoseconds_option")] +/// time: Option>, +/// } +/// +/// let time = Some( +/// NaiveDate::from_ymd_opt(2018, 5, 17) +/// .unwrap() +/// .and_hms_nano_opt(02, 04, 59, 918355733) +/// .unwrap() +/// .and_utc(), +/// ); +/// let my_s = S { time: time.clone() }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok::<(), serde_json::Error>(()) +/// ``` +pub mod ts_nanoseconds_option { + use core::fmt; + use serde::{de, ser}; + + use crate::{DateTime, Utc}; + + use super::NanoSecondsTimestampVisitor; + + /// Serialize a UTC datetime into an integer number of nanoseconds since the epoch or none + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Errors + /// + /// An `i64` with nanosecond precision can span a range of ~584 years. This function returns an + /// error on an out of range `DateTime`. + /// + /// The dates that can be represented as nanoseconds are between 1677-09-21T00:12:44.0 and + /// 2262-04-11T23:47:16.854775804. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{DateTime, Utc, NaiveDate}; + /// # use serde_derive::Serialize; + /// use chrono::serde::ts_nanoseconds_option::serialize as to_nano_tsopt; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_nano_tsopt")] + /// time: Option>, + /// } + /// + /// let my_s = S { + /// time: Some( + /// NaiveDate::from_ymd_opt(2018, 5, 17) + /// .unwrap() + /// .and_hms_nano_opt(02, 04, 59, 918355733) + /// .unwrap() + /// .and_utc(), + /// ), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn serialize(opt: &Option>, serializer: S) -> Result + where + S: ser::Serializer, + { + match *opt { + Some(ref dt) => serializer.serialize_some(&dt.timestamp_nanos_opt().ok_or( + ser::Error::custom("value out of range for a timestamp with nanosecond precision"), + )?), + None => serializer.serialize_none(), + } + } + + /// Deserialize a `DateTime` from a nanosecond timestamp or none + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{DateTime, TimeZone, Utc}; + /// # use serde_derive::Deserialize; + /// use chrono::serde::ts_nanoseconds_option::deserialize as from_nano_tsopt; + /// #[derive(Debug, PartialEq, Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_nano_tsopt")] + /// time: Option>, + /// } + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355733 }"#)?; + /// assert_eq!(my_s, S { time: Utc.timestamp_opt(1526522699, 918355733).single() }); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result>, D::Error> + where + D: de::Deserializer<'de>, + { + d.deserialize_option(OptionNanoSecondsTimestampVisitor) + } + + struct OptionNanoSecondsTimestampVisitor; + + impl<'de> de::Visitor<'de> for OptionNanoSecondsTimestampVisitor { + type Value = Option>; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp in nanoseconds or none") + } + + /// Deserialize a timestamp in nanoseconds since the epoch + fn visit_some(self, d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(NanoSecondsTimestampVisitor).map(Some) + } + + /// Deserialize a timestamp in nanoseconds since the epoch + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(None) + } + + /// Deserialize a timestamp in nanoseconds since the epoch + fn visit_unit(self) -> Result + where + E: de::Error, + { + Ok(None) + } + } +} + +/// Ser/de to/from timestamps in microseconds +/// +/// Intended for use with `serde`'s `with` attribute. +/// +/// # Example: +/// +/// ```rust +/// # use chrono::{DateTime, Utc, NaiveDate}; +/// # use serde_derive::{Deserialize, Serialize}; +/// use chrono::serde::ts_microseconds; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_microseconds")] +/// time: DateTime, +/// } +/// +/// let time = NaiveDate::from_ymd_opt(2018, 5, 17) +/// .unwrap() +/// .and_hms_micro_opt(02, 04, 59, 918355) +/// .unwrap() +/// .and_utc(); +/// let my_s = S { time: time.clone() }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":1526522699918355}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok::<(), serde_json::Error>(()) +/// ``` +pub mod ts_microseconds { + use core::fmt; + use serde::{de, ser}; + + use crate::serde::invalid_ts; + use crate::{DateTime, Utc}; + + use super::MicroSecondsTimestampVisitor; + + /// Serialize a UTC datetime into an integer number of microseconds since the epoch + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{DateTime, Utc, NaiveDate}; + /// # use serde_derive::Serialize; + /// use chrono::serde::ts_microseconds::serialize as to_micro_ts; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_micro_ts")] + /// time: DateTime, + /// } + /// + /// let my_s = S { + /// time: NaiveDate::from_ymd_opt(2018, 5, 17) + /// .unwrap() + /// .and_hms_micro_opt(02, 04, 59, 918355) + /// .unwrap() + /// .and_utc(), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn serialize(dt: &DateTime, serializer: S) -> Result + where + S: ser::Serializer, + { + serializer.serialize_i64(dt.timestamp_micros()) + } + + /// Deserialize a `DateTime` from a microsecond timestamp + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{DateTime, TimeZone, Utc}; + /// # use serde_derive::Deserialize; + /// use chrono::serde::ts_microseconds::deserialize as from_micro_ts; + /// #[derive(Debug, PartialEq, Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_micro_ts")] + /// time: DateTime, + /// } + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355 }"#)?; + /// assert_eq!(my_s, S { time: Utc.timestamp_opt(1526522699, 918355000).unwrap() }); + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": -1 }"#)?; + /// assert_eq!(my_s, S { time: Utc.timestamp_opt(-1, 999_999_000).unwrap() }); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result, D::Error> + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(MicroSecondsTimestampVisitor) + } + + impl de::Visitor<'_> for MicroSecondsTimestampVisitor { + type Value = DateTime; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp in microseconds") + } + + /// Deserialize a timestamp in milliseconds since the epoch + fn visit_i64(self, value: i64) -> Result + where + E: de::Error, + { + DateTime::from_timestamp( + value.div_euclid(1_000_000), + (value.rem_euclid(1_000_000) * 1000) as u32, + ) + .ok_or_else(|| invalid_ts(value)) + } + + /// Deserialize a timestamp in milliseconds since the epoch + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + DateTime::from_timestamp( + (value / 1_000_000) as i64, + ((value % 1_000_000) * 1_000) as u32, + ) + .ok_or_else(|| invalid_ts(value)) + } + } +} + +/// Ser/de to/from optional timestamps in microseconds +/// +/// Intended for use with `serde`'s `with` attribute. +/// +/// # Example: +/// +/// ```rust +/// # use chrono::{DateTime, Utc, NaiveDate}; +/// # use serde_derive::{Deserialize, Serialize}; +/// use chrono::serde::ts_microseconds_option; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_microseconds_option")] +/// time: Option>, +/// } +/// +/// let time = Some( +/// NaiveDate::from_ymd_opt(2018, 5, 17) +/// .unwrap() +/// .and_hms_micro_opt(02, 04, 59, 918355) +/// .unwrap() +/// .and_utc(), +/// ); +/// let my_s = S { time: time.clone() }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":1526522699918355}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok::<(), serde_json::Error>(()) +/// ``` +pub mod ts_microseconds_option { + use core::fmt; + use serde::{de, ser}; + + use super::MicroSecondsTimestampVisitor; + use crate::{DateTime, Utc}; + + /// Serialize a UTC datetime into an integer number of microseconds since the epoch or none + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{DateTime, Utc, NaiveDate}; + /// # use serde_derive::Serialize; + /// use chrono::serde::ts_microseconds_option::serialize as to_micro_tsopt; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_micro_tsopt")] + /// time: Option>, + /// } + /// + /// let my_s = S { + /// time: Some( + /// NaiveDate::from_ymd_opt(2018, 5, 17) + /// .unwrap() + /// .and_hms_micro_opt(02, 04, 59, 918355) + /// .unwrap() + /// .and_utc(), + /// ), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn serialize(opt: &Option>, serializer: S) -> Result + where + S: ser::Serializer, + { + match *opt { + Some(ref dt) => serializer.serialize_some(&dt.timestamp_micros()), + None => serializer.serialize_none(), + } + } + + /// Deserialize a `DateTime` from a microsecond timestamp or none + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{DateTime, TimeZone, Utc}; + /// # use serde_derive::Deserialize; + /// use chrono::serde::ts_microseconds_option::deserialize as from_micro_tsopt; + /// #[derive(Debug, PartialEq, Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_micro_tsopt")] + /// time: Option>, + /// } + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355 }"#)?; + /// assert_eq!(my_s, S { time: Utc.timestamp_opt(1526522699, 918355000).single() }); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result>, D::Error> + where + D: de::Deserializer<'de>, + { + d.deserialize_option(OptionMicroSecondsTimestampVisitor) + } + + struct OptionMicroSecondsTimestampVisitor; + + impl<'de> de::Visitor<'de> for OptionMicroSecondsTimestampVisitor { + type Value = Option>; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp in microseconds or none") + } + + /// Deserialize a timestamp in microseconds since the epoch + fn visit_some(self, d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(MicroSecondsTimestampVisitor).map(Some) + } + + /// Deserialize a timestamp in microseconds since the epoch + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(None) + } + + /// Deserialize a timestamp in microseconds since the epoch + fn visit_unit(self) -> Result + where + E: de::Error, + { + Ok(None) + } + } +} + +/// Ser/de to/from timestamps in milliseconds +/// +/// Intended for use with `serde`s `with` attribute. +/// +/// # Example +/// +/// ```rust +/// # use chrono::{DateTime, Utc, NaiveDate}; +/// # use serde_derive::{Deserialize, Serialize}; +/// use chrono::serde::ts_milliseconds; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_milliseconds")] +/// time: DateTime, +/// } +/// +/// let time = NaiveDate::from_ymd_opt(2018, 5, 17) +/// .unwrap() +/// .and_hms_milli_opt(02, 04, 59, 918) +/// .unwrap() +/// .and_utc(); +/// let my_s = S { time: time.clone() }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":1526522699918}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok::<(), serde_json::Error>(()) +/// ``` +pub mod ts_milliseconds { + use core::fmt; + use serde::{de, ser}; + + use crate::serde::invalid_ts; + use crate::{DateTime, Utc}; + + use super::MilliSecondsTimestampVisitor; + + /// Serialize a UTC datetime into an integer number of milliseconds since the epoch + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{DateTime, Utc, NaiveDate}; + /// # use serde_derive::Serialize; + /// use chrono::serde::ts_milliseconds::serialize as to_milli_ts; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_milli_ts")] + /// time: DateTime, + /// } + /// + /// let my_s = S { + /// time: NaiveDate::from_ymd_opt(2018, 5, 17) + /// .unwrap() + /// .and_hms_milli_opt(02, 04, 59, 918) + /// .unwrap() + /// .and_utc(), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1526522699918}"#); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn serialize(dt: &DateTime, serializer: S) -> Result + where + S: ser::Serializer, + { + serializer.serialize_i64(dt.timestamp_millis()) + } + + /// Deserialize a `DateTime` from a millisecond timestamp + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{DateTime, TimeZone, Utc}; + /// # use serde_derive::Deserialize; + /// use chrono::serde::ts_milliseconds::deserialize as from_milli_ts; + /// #[derive(Debug, PartialEq, Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_milli_ts")] + /// time: DateTime, + /// } + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918 }"#)?; + /// assert_eq!(my_s, S { time: Utc.timestamp_opt(1526522699, 918000000).unwrap() }); + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": -1 }"#)?; + /// assert_eq!(my_s, S { time: Utc.timestamp_opt(-1, 999_000_000).unwrap() }); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result, D::Error> + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(MilliSecondsTimestampVisitor).map(|dt| dt.with_timezone(&Utc)) + } + + impl de::Visitor<'_> for MilliSecondsTimestampVisitor { + type Value = DateTime; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp in milliseconds") + } + + /// Deserialize a timestamp in milliseconds since the epoch + fn visit_i64(self, value: i64) -> Result + where + E: de::Error, + { + DateTime::from_timestamp_millis(value).ok_or_else(|| invalid_ts(value)) + } + + /// Deserialize a timestamp in milliseconds since the epoch + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + DateTime::from_timestamp((value / 1000) as i64, ((value % 1000) * 1_000_000) as u32) + .ok_or_else(|| invalid_ts(value)) + } + } +} + +/// Ser/de to/from optional timestamps in milliseconds +/// +/// Intended for use with `serde`s `with` attribute. +/// +/// # Example +/// +/// ```rust +/// # use chrono::{DateTime, Utc, NaiveDate}; +/// # use serde_derive::{Deserialize, Serialize}; +/// use chrono::serde::ts_milliseconds_option; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_milliseconds_option")] +/// time: Option>, +/// } +/// +/// let time = Some( +/// NaiveDate::from_ymd_opt(2018, 5, 17) +/// .unwrap() +/// .and_hms_milli_opt(02, 04, 59, 918) +/// .unwrap() +/// .and_utc(), +/// ); +/// let my_s = S { time: time.clone() }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":1526522699918}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok::<(), serde_json::Error>(()) +/// ``` +pub mod ts_milliseconds_option { + use core::fmt; + use serde::{de, ser}; + + use super::MilliSecondsTimestampVisitor; + use crate::{DateTime, Utc}; + + /// Serialize a UTC datetime into an integer number of milliseconds since the epoch or none + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{DateTime, Utc, NaiveDate}; + /// # use serde_derive::Serialize; + /// use chrono::serde::ts_milliseconds_option::serialize as to_milli_tsopt; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_milli_tsopt")] + /// time: Option>, + /// } + /// + /// let my_s = S { + /// time: Some( + /// NaiveDate::from_ymd_opt(2018, 5, 17) + /// .unwrap() + /// .and_hms_milli_opt(02, 04, 59, 918) + /// .unwrap() + /// .and_utc(), + /// ), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1526522699918}"#); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn serialize(opt: &Option>, serializer: S) -> Result + where + S: ser::Serializer, + { + match *opt { + Some(ref dt) => serializer.serialize_some(&dt.timestamp_millis()), + None => serializer.serialize_none(), + } + } + + /// Deserialize a `DateTime` from a millisecond timestamp or none + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{TimeZone, DateTime, Utc}; + /// # use serde_derive::Deserialize; + /// use chrono::serde::ts_milliseconds_option::deserialize as from_milli_tsopt; + /// + /// #[derive(Deserialize, PartialEq, Debug)] + /// #[serde(untagged)] + /// enum E { + /// V(T), + /// } + /// + /// #[derive(Deserialize, PartialEq, Debug)] + /// struct S { + /// #[serde(default, deserialize_with = "from_milli_tsopt")] + /// time: Option>, + /// } + /// + /// let my_s: E = serde_json::from_str(r#"{ "time": 1526522699918 }"#)?; + /// assert_eq!(my_s, E::V(S { time: Some(Utc.timestamp_opt(1526522699, 918000000).unwrap()) })); + /// let s: E = serde_json::from_str(r#"{ "time": null }"#)?; + /// assert_eq!(s, E::V(S { time: None })); + /// let t: E = serde_json::from_str(r#"{}"#)?; + /// assert_eq!(t, E::V(S { time: None })); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result>, D::Error> + where + D: de::Deserializer<'de>, + { + d.deserialize_option(OptionMilliSecondsTimestampVisitor) + .map(|opt| opt.map(|dt| dt.with_timezone(&Utc))) + } + + struct OptionMilliSecondsTimestampVisitor; + + impl<'de> de::Visitor<'de> for OptionMilliSecondsTimestampVisitor { + type Value = Option>; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp in milliseconds or none") + } + + /// Deserialize a timestamp in milliseconds since the epoch + fn visit_some(self, d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(MilliSecondsTimestampVisitor).map(Some) + } + + /// Deserialize a timestamp in milliseconds since the epoch + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(None) + } + + /// Deserialize a timestamp in milliseconds since the epoch + fn visit_unit(self) -> Result + where + E: de::Error, + { + Ok(None) + } + } +} + +/// Ser/de to/from timestamps in seconds +/// +/// Intended for use with `serde`'s `with` attribute. +/// +/// # Example: +/// +/// ```rust +/// # use chrono::{TimeZone, DateTime, Utc}; +/// # use serde_derive::{Deserialize, Serialize}; +/// use chrono::serde::ts_seconds; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_seconds")] +/// time: DateTime, +/// } +/// +/// let time = Utc.with_ymd_and_hms(2015, 5, 15, 10, 0, 0).unwrap(); +/// let my_s = S { time: time.clone() }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":1431684000}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok::<(), serde_json::Error>(()) +/// ``` +pub mod ts_seconds { + use core::fmt; + use serde::{de, ser}; + + use crate::serde::invalid_ts; + use crate::{DateTime, Utc}; + + use super::SecondsTimestampVisitor; + + /// Serialize a UTC datetime into an integer number of seconds since the epoch + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{TimeZone, DateTime, Utc}; + /// # use serde_derive::Serialize; + /// use chrono::serde::ts_seconds::serialize as to_ts; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_ts")] + /// time: DateTime, + /// } + /// + /// let my_s = S { time: Utc.with_ymd_and_hms(2015, 5, 15, 10, 0, 0).unwrap() }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1431684000}"#); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn serialize(dt: &DateTime, serializer: S) -> Result + where + S: ser::Serializer, + { + serializer.serialize_i64(dt.timestamp()) + } + + /// Deserialize a `DateTime` from a seconds timestamp + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{DateTime, TimeZone, Utc}; + /// # use serde_derive::Deserialize; + /// use chrono::serde::ts_seconds::deserialize as from_ts; + /// #[derive(Debug, PartialEq, Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_ts")] + /// time: DateTime, + /// } + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": 1431684000 }"#)?; + /// assert_eq!(my_s, S { time: Utc.timestamp_opt(1431684000, 0).unwrap() }); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result, D::Error> + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(SecondsTimestampVisitor) + } + + impl de::Visitor<'_> for SecondsTimestampVisitor { + type Value = DateTime; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp in seconds") + } + + /// Deserialize a timestamp in seconds since the epoch + fn visit_i64(self, value: i64) -> Result + where + E: de::Error, + { + DateTime::from_timestamp(value, 0).ok_or_else(|| invalid_ts(value)) + } + + /// Deserialize a timestamp in seconds since the epoch + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + if value > i64::MAX as u64 { + Err(invalid_ts(value)) + } else { + DateTime::from_timestamp(value as i64, 0).ok_or_else(|| invalid_ts(value)) + } + } + } +} + +/// Ser/de to/from optional timestamps in seconds +/// +/// Intended for use with `serde`'s `with` attribute. +/// +/// # Example: +/// +/// ```rust +/// # use chrono::{TimeZone, DateTime, Utc}; +/// # use serde_derive::{Deserialize, Serialize}; +/// use chrono::serde::ts_seconds_option; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_seconds_option")] +/// time: Option>, +/// } +/// +/// let time = Some(Utc.with_ymd_and_hms(2015, 5, 15, 10, 0, 0).unwrap()); +/// let my_s = S { time: time.clone() }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":1431684000}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok::<(), serde_json::Error>(()) +/// ``` +pub mod ts_seconds_option { + use core::fmt; + use serde::{de, ser}; + + use super::SecondsTimestampVisitor; + use crate::{DateTime, Utc}; + + /// Serialize a UTC datetime into an integer number of seconds since the epoch or none + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{TimeZone, DateTime, Utc}; + /// # use serde_derive::Serialize; + /// use chrono::serde::ts_seconds_option::serialize as to_tsopt; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_tsopt")] + /// time: Option>, + /// } + /// + /// let my_s = S { time: Some(Utc.with_ymd_and_hms(2015, 5, 15, 10, 0, 0).unwrap()) }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1431684000}"#); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn serialize(opt: &Option>, serializer: S) -> Result + where + S: ser::Serializer, + { + match *opt { + Some(ref dt) => serializer.serialize_some(&dt.timestamp()), + None => serializer.serialize_none(), + } + } + + /// Deserialize a `DateTime` from a seconds timestamp or none + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{DateTime, TimeZone, Utc}; + /// # use serde_derive::Deserialize; + /// use chrono::serde::ts_seconds_option::deserialize as from_tsopt; + /// #[derive(Debug, PartialEq, Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_tsopt")] + /// time: Option>, + /// } + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": 1431684000 }"#)?; + /// assert_eq!(my_s, S { time: Utc.timestamp_opt(1431684000, 0).single() }); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result>, D::Error> + where + D: de::Deserializer<'de>, + { + d.deserialize_option(OptionSecondsTimestampVisitor) + } + + struct OptionSecondsTimestampVisitor; + + impl<'de> de::Visitor<'de> for OptionSecondsTimestampVisitor { + type Value = Option>; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp in seconds or none") + } + + /// Deserialize a timestamp in seconds since the epoch + fn visit_some(self, d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(SecondsTimestampVisitor).map(Some) + } + + /// Deserialize a timestamp in seconds since the epoch + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(None) + } + + /// Deserialize a timestamp in seconds since the epoch + fn visit_unit(self) -> Result + where + E: de::Error, + { + Ok(None) + } + } +} + +#[cfg(test)] +mod tests { + #[cfg(feature = "clock")] + use crate::Local; + use crate::{DateTime, FixedOffset, TimeZone, Utc}; + use core::fmt; + + #[test] + fn test_serde_serialize() { + assert_eq!( + serde_json::to_string(&Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap()).ok(), + Some(r#""2014-07-24T12:34:06Z""#.to_owned()) + ); + assert_eq!( + serde_json::to_string( + &FixedOffset::east_opt(3660) + .unwrap() + .with_ymd_and_hms(2014, 7, 24, 12, 34, 6) + .unwrap() + ) + .ok(), + Some(r#""2014-07-24T12:34:06+01:01""#.to_owned()) + ); + assert_eq!( + serde_json::to_string( + &FixedOffset::east_opt(3650) + .unwrap() + .with_ymd_and_hms(2014, 7, 24, 12, 34, 6) + .unwrap() + ) + .ok(), + // An offset with seconds is not allowed by RFC 3339, so we round it to the nearest minute. + // In this case `+01:00:50` becomes `+01:01` + Some(r#""2014-07-24T12:34:06+01:01""#.to_owned()) + ); + } + + #[test] + fn test_serde_deserialize() { + // should check against the offset as well (the normal DateTime comparison will ignore them) + fn norm(dt: &Option>) -> Option<(&DateTime, &Tz::Offset)> { + dt.as_ref().map(|dt| (dt, dt.offset())) + } + + let dt: Option> = serde_json::from_str(r#""2014-07-24T12:34:06Z""#).ok(); + assert_eq!(norm(&dt), norm(&Some(Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap()))); + let dt: Option> = serde_json::from_str(r#""2014-07-24T13:57:06+01:23""#).ok(); + assert_eq!(norm(&dt), norm(&Some(Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap()))); + + let dt: Option> = + serde_json::from_str(r#""2014-07-24T12:34:06Z""#).ok(); + assert_eq!( + norm(&dt), + norm(&Some( + FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap() + )) + ); + let dt: Option> = + serde_json::from_str(r#""2014-07-24T13:57:06+01:23""#).ok(); + assert_eq!( + norm(&dt), + norm(&Some( + FixedOffset::east_opt(60 * 60 + 23 * 60) + .unwrap() + .with_ymd_and_hms(2014, 7, 24, 13, 57, 6) + .unwrap() + )) + ); + + // we don't know the exact local offset but we can check that + // the conversion didn't change the instant itself + #[cfg(feature = "clock")] + { + let dt: DateTime = + serde_json::from_str(r#""2014-07-24T12:34:06Z""#).expect("local should parse"); + assert_eq!(dt, Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap()); + + let dt: DateTime = serde_json::from_str(r#""2014-07-24T13:57:06+01:23""#) + .expect("local should parse with offset"); + assert_eq!(dt, Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap()); + } + + assert!(serde_json::from_str::>(r#""2014-07-32T12:34:06Z""#).is_err()); + assert!( + serde_json::from_str::>(r#""2014-07-32T12:34:06Z""#).is_err() + ); + } + + #[test] + fn test_serde_bincode() { + // Bincode is relevant to test separately from JSON because + // it is not self-describing. + use bincode::{deserialize, serialize}; + + let dt = Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap(); + let encoded = serialize(&dt).unwrap(); + let decoded: DateTime = deserialize(&encoded).unwrap(); + assert_eq!(dt, decoded); + assert_eq!(dt.offset(), decoded.offset()); + } + + #[test] + fn test_serde_no_offset_debug() { + use crate::{MappedLocalTime, NaiveDate, NaiveDateTime, Offset}; + use core::fmt::Debug; + + #[derive(Clone)] + struct TestTimeZone; + impl Debug for TestTimeZone { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "TEST") + } + } + impl TimeZone for TestTimeZone { + type Offset = TestTimeZone; + fn from_offset(_state: &TestTimeZone) -> TestTimeZone { + TestTimeZone + } + fn offset_from_local_date(&self, _local: &NaiveDate) -> MappedLocalTime { + MappedLocalTime::Single(TestTimeZone) + } + fn offset_from_local_datetime( + &self, + _local: &NaiveDateTime, + ) -> MappedLocalTime { + MappedLocalTime::Single(TestTimeZone) + } + fn offset_from_utc_date(&self, _utc: &NaiveDate) -> TestTimeZone { + TestTimeZone + } + fn offset_from_utc_datetime(&self, _utc: &NaiveDateTime) -> TestTimeZone { + TestTimeZone + } + } + impl Offset for TestTimeZone { + fn fix(&self) -> FixedOffset { + FixedOffset::east_opt(15 * 60 * 60).unwrap() + } + } + + let tz = TestTimeZone; + assert_eq!(format!("{:?}", &tz), "TEST"); + + let dt = tz.with_ymd_and_hms(2023, 4, 24, 21, 10, 33).unwrap(); + let encoded = serde_json::to_string(&dt).unwrap(); + dbg!(&encoded); + let decoded: DateTime = serde_json::from_str(&encoded).unwrap(); + assert_eq!(dt, decoded); + assert_eq!(dt.offset().fix(), *decoded.offset()); + } +} diff --git a/third_party/rust/chrono/src/datetime/tests.rs b/third_party/rust/chrono/src/datetime/tests.rs new file mode 100644 index 00000000000..b7f71069285 --- /dev/null +++ b/third_party/rust/chrono/src/datetime/tests.rs @@ -0,0 +1,1902 @@ +use super::DateTime; +use crate::naive::{NaiveDate, NaiveTime}; +#[cfg(feature = "clock")] +use crate::offset::Local; +use crate::offset::{FixedOffset, Offset, TimeZone, Utc}; +use crate::{Datelike, Days, MappedLocalTime, Months, NaiveDateTime, TimeDelta, Timelike, Weekday}; + +#[derive(Clone)] +struct DstTester; + +impl DstTester { + fn winter_offset() -> FixedOffset { + FixedOffset::east_opt(8 * 60 * 60).unwrap() + } + fn summer_offset() -> FixedOffset { + FixedOffset::east_opt(9 * 60 * 60).unwrap() + } + + const TO_WINTER_MONTH_DAY: (u32, u32) = (4, 15); + const TO_SUMMER_MONTH_DAY: (u32, u32) = (9, 15); + + fn transition_start_local() -> NaiveTime { + NaiveTime::from_hms_opt(2, 0, 0).unwrap() + } +} + +impl TimeZone for DstTester { + type Offset = FixedOffset; + + fn from_offset(_: &Self::Offset) -> Self { + DstTester + } + + fn offset_from_local_date(&self, _: &NaiveDate) -> crate::MappedLocalTime { + unimplemented!() + } + + fn offset_from_local_datetime( + &self, + local: &NaiveDateTime, + ) -> crate::MappedLocalTime { + let local_to_winter_transition_start = NaiveDate::from_ymd_opt( + local.year(), + DstTester::TO_WINTER_MONTH_DAY.0, + DstTester::TO_WINTER_MONTH_DAY.1, + ) + .unwrap() + .and_time(DstTester::transition_start_local()); + + let local_to_winter_transition_end = NaiveDate::from_ymd_opt( + local.year(), + DstTester::TO_WINTER_MONTH_DAY.0, + DstTester::TO_WINTER_MONTH_DAY.1, + ) + .unwrap() + .and_time(DstTester::transition_start_local() - TimeDelta::try_hours(1).unwrap()); + + let local_to_summer_transition_start = NaiveDate::from_ymd_opt( + local.year(), + DstTester::TO_SUMMER_MONTH_DAY.0, + DstTester::TO_SUMMER_MONTH_DAY.1, + ) + .unwrap() + .and_time(DstTester::transition_start_local()); + + let local_to_summer_transition_end = NaiveDate::from_ymd_opt( + local.year(), + DstTester::TO_SUMMER_MONTH_DAY.0, + DstTester::TO_SUMMER_MONTH_DAY.1, + ) + .unwrap() + .and_time(DstTester::transition_start_local() + TimeDelta::try_hours(1).unwrap()); + + if *local < local_to_winter_transition_end || *local >= local_to_summer_transition_end { + MappedLocalTime::Single(DstTester::summer_offset()) + } else if *local >= local_to_winter_transition_start + && *local < local_to_summer_transition_start + { + MappedLocalTime::Single(DstTester::winter_offset()) + } else if *local >= local_to_winter_transition_end + && *local < local_to_winter_transition_start + { + MappedLocalTime::Ambiguous(DstTester::winter_offset(), DstTester::summer_offset()) + } else if *local >= local_to_summer_transition_start + && *local < local_to_summer_transition_end + { + MappedLocalTime::None + } else { + panic!("Unexpected local time {}", local) + } + } + + fn offset_from_utc_date(&self, _: &NaiveDate) -> Self::Offset { + unimplemented!() + } + + fn offset_from_utc_datetime(&self, utc: &NaiveDateTime) -> Self::Offset { + let utc_to_winter_transition = NaiveDate::from_ymd_opt( + utc.year(), + DstTester::TO_WINTER_MONTH_DAY.0, + DstTester::TO_WINTER_MONTH_DAY.1, + ) + .unwrap() + .and_time(DstTester::transition_start_local()) + - DstTester::summer_offset(); + + let utc_to_summer_transition = NaiveDate::from_ymd_opt( + utc.year(), + DstTester::TO_SUMMER_MONTH_DAY.0, + DstTester::TO_SUMMER_MONTH_DAY.1, + ) + .unwrap() + .and_time(DstTester::transition_start_local()) + - DstTester::winter_offset(); + + if *utc < utc_to_winter_transition || *utc >= utc_to_summer_transition { + DstTester::summer_offset() + } else if *utc >= utc_to_winter_transition && *utc < utc_to_summer_transition { + DstTester::winter_offset() + } else { + panic!("Unexpected utc time {}", utc) + } + } +} + +#[test] +fn test_datetime_from_timestamp_millis() { + let valid_map = [ + (1662921288000, "2022-09-11 18:34:48.000000000"), + (1662921288123, "2022-09-11 18:34:48.123000000"), + (1662921287890, "2022-09-11 18:34:47.890000000"), + (-2208936075000, "1900-01-01 14:38:45.000000000"), + (0, "1970-01-01 00:00:00.000000000"), + (119731017000, "1973-10-17 18:36:57.000000000"), + (1234567890000, "2009-02-13 23:31:30.000000000"), + (2034061609000, "2034-06-16 09:06:49.000000000"), + ]; + + for (timestamp_millis, _formatted) in valid_map.iter().copied() { + let datetime = DateTime::from_timestamp_millis(timestamp_millis).unwrap(); + assert_eq!(timestamp_millis, datetime.timestamp_millis()); + #[cfg(feature = "alloc")] + assert_eq!(datetime.format("%F %T%.9f").to_string(), _formatted); + } + + let invalid = [i64::MAX, i64::MIN]; + + for timestamp_millis in invalid.iter().copied() { + let datetime = DateTime::from_timestamp_millis(timestamp_millis); + assert!(datetime.is_none()); + } + + // Test that the result of `from_timestamp_millis` compares equal to + // that of `from_timestamp_opt`. + let secs_test = [0, 1, 2, 1000, 1234, 12345678, -1, -2, -1000, -12345678]; + for secs in secs_test.iter().cloned() { + assert_eq!(DateTime::from_timestamp_millis(secs * 1000), DateTime::from_timestamp(secs, 0)); + } +} + +#[test] +fn test_datetime_from_timestamp_micros() { + let valid_map = [ + (1662921288000000, "2022-09-11 18:34:48.000000000"), + (1662921288123456, "2022-09-11 18:34:48.123456000"), + (1662921287890000, "2022-09-11 18:34:47.890000000"), + (-2208936075000000, "1900-01-01 14:38:45.000000000"), + (0, "1970-01-01 00:00:00.000000000"), + (119731017000000, "1973-10-17 18:36:57.000000000"), + (1234567890000000, "2009-02-13 23:31:30.000000000"), + (2034061609000000, "2034-06-16 09:06:49.000000000"), + ]; + + for (timestamp_micros, _formatted) in valid_map.iter().copied() { + let datetime = DateTime::from_timestamp_micros(timestamp_micros).unwrap(); + assert_eq!(timestamp_micros, datetime.timestamp_micros()); + #[cfg(feature = "alloc")] + assert_eq!(datetime.format("%F %T%.9f").to_string(), _formatted); + } + + let invalid = [i64::MAX, i64::MIN]; + + for timestamp_micros in invalid.iter().copied() { + let datetime = DateTime::from_timestamp_micros(timestamp_micros); + assert!(datetime.is_none()); + } + + // Test that the result of `TimeZone::timestamp_micros` compares equal to + // that of `TimeZone::timestamp_opt`. + let secs_test = [0, 1, 2, 1000, 1234, 12345678, -1, -2, -1000, -12345678]; + for secs in secs_test.iter().copied() { + assert_eq!( + DateTime::from_timestamp_micros(secs * 1_000_000), + DateTime::from_timestamp(secs, 0) + ); + } +} + +#[test] +fn test_datetime_from_timestamp_nanos() { + let valid_map = [ + (1662921288000000000, "2022-09-11 18:34:48.000000000"), + (1662921288123456000, "2022-09-11 18:34:48.123456000"), + (1662921288123456789, "2022-09-11 18:34:48.123456789"), + (1662921287890000000, "2022-09-11 18:34:47.890000000"), + (-2208936075000000000, "1900-01-01 14:38:45.000000000"), + (-5337182663000000000, "1800-11-15 01:15:37.000000000"), + (0, "1970-01-01 00:00:00.000000000"), + (119731017000000000, "1973-10-17 18:36:57.000000000"), + (1234567890000000000, "2009-02-13 23:31:30.000000000"), + (2034061609000000000, "2034-06-16 09:06:49.000000000"), + ]; + + for (timestamp_nanos, _formatted) in valid_map.iter().copied() { + let datetime = DateTime::from_timestamp_nanos(timestamp_nanos); + assert_eq!(timestamp_nanos, datetime.timestamp_nanos_opt().unwrap()); + #[cfg(feature = "alloc")] + assert_eq!(datetime.format("%F %T%.9f").to_string(), _formatted); + } + + const A_BILLION: i64 = 1_000_000_000; + // Maximum datetime in nanoseconds + let maximum = "2262-04-11T23:47:16.854775804UTC"; + let parsed: DateTime = maximum.parse().unwrap(); + let nanos = parsed.timestamp_nanos_opt().unwrap(); + assert_eq!( + Some(DateTime::from_timestamp_nanos(nanos)), + DateTime::from_timestamp(nanos / A_BILLION, (nanos % A_BILLION) as u32) + ); + // Minimum datetime in nanoseconds + let minimum = "1677-09-21T00:12:44.000000000UTC"; + let parsed: DateTime = minimum.parse().unwrap(); + let nanos = parsed.timestamp_nanos_opt().unwrap(); + assert_eq!( + Some(DateTime::from_timestamp_nanos(nanos)), + DateTime::from_timestamp(nanos / A_BILLION, (nanos % A_BILLION) as u32) + ); + + // Test that the result of `TimeZone::timestamp_nanos` compares equal to + // that of `TimeZone::timestamp_opt`. + let secs_test = [0, 1, 2, 1000, 1234, 12345678, -1, -2, -1000, -12345678]; + for secs in secs_test.iter().copied() { + assert_eq!( + Some(DateTime::from_timestamp_nanos(secs * 1_000_000_000)), + DateTime::from_timestamp(secs, 0) + ); + } +} + +#[test] +fn test_datetime_from_timestamp() { + let from_timestamp = |secs| DateTime::from_timestamp(secs, 0); + let ymdhms = |y, m, d, h, n, s| { + NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap().and_utc() + }; + assert_eq!(from_timestamp(-1), Some(ymdhms(1969, 12, 31, 23, 59, 59))); + assert_eq!(from_timestamp(0), Some(ymdhms(1970, 1, 1, 0, 0, 0))); + assert_eq!(from_timestamp(1), Some(ymdhms(1970, 1, 1, 0, 0, 1))); + assert_eq!(from_timestamp(1_000_000_000), Some(ymdhms(2001, 9, 9, 1, 46, 40))); + assert_eq!(from_timestamp(0x7fffffff), Some(ymdhms(2038, 1, 19, 3, 14, 7))); + assert_eq!(from_timestamp(i64::MIN), None); + assert_eq!(from_timestamp(i64::MAX), None); +} + +#[test] +fn test_datetime_timestamp() { + let to_timestamp = |y, m, d, h, n, s| { + NaiveDate::from_ymd_opt(y, m, d) + .unwrap() + .and_hms_opt(h, n, s) + .unwrap() + .and_utc() + .timestamp() + }; + assert_eq!(to_timestamp(1969, 12, 31, 23, 59, 59), -1); + assert_eq!(to_timestamp(1970, 1, 1, 0, 0, 0), 0); + assert_eq!(to_timestamp(1970, 1, 1, 0, 0, 1), 1); + assert_eq!(to_timestamp(2001, 9, 9, 1, 46, 40), 1_000_000_000); + assert_eq!(to_timestamp(2038, 1, 19, 3, 14, 7), 0x7fffffff); +} + +#[test] +fn test_nanosecond_range() { + const A_BILLION: i64 = 1_000_000_000; + let maximum = "2262-04-11T23:47:16.854775804UTC"; + let parsed: DateTime = maximum.parse().unwrap(); + let nanos = parsed.timestamp_nanos_opt().unwrap(); + assert_eq!( + parsed, + DateTime::::from_timestamp(nanos / A_BILLION, (nanos % A_BILLION) as u32).unwrap() + ); + + let minimum = "1677-09-21T00:12:44.000000000UTC"; + let parsed: DateTime = minimum.parse().unwrap(); + let nanos = parsed.timestamp_nanos_opt().unwrap(); + assert_eq!( + parsed, + DateTime::::from_timestamp(nanos / A_BILLION, (nanos % A_BILLION) as u32).unwrap() + ); + + // Just beyond range + let maximum = "2262-04-11T23:47:16.854775804UTC"; + let parsed: DateTime = maximum.parse().unwrap(); + let beyond_max = parsed + TimeDelta::try_milliseconds(300).unwrap(); + assert!(beyond_max.timestamp_nanos_opt().is_none()); + + // Far beyond range + let maximum = "2262-04-11T23:47:16.854775804UTC"; + let parsed: DateTime = maximum.parse().unwrap(); + let beyond_max = parsed + Days::new(365); + assert!(beyond_max.timestamp_nanos_opt().is_none()); +} + +#[test] +fn test_datetime_add_days() { + let est = FixedOffset::west_opt(5 * 60 * 60).unwrap(); + let kst = FixedOffset::east_opt(9 * 60 * 60).unwrap(); + + assert_eq!( + format!("{}", est.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap() + Days::new(5)), + "2014-05-11 07:08:09 -05:00" + ); + assert_eq!( + format!("{}", kst.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap() + Days::new(5)), + "2014-05-11 07:08:09 +09:00" + ); + + assert_eq!( + format!("{}", est.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap() + Days::new(35)), + "2014-06-10 07:08:09 -05:00" + ); + assert_eq!( + format!("{}", kst.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap() + Days::new(35)), + "2014-06-10 07:08:09 +09:00" + ); + + assert_eq!( + format!("{}", DstTester.with_ymd_and_hms(2014, 4, 6, 7, 8, 9).unwrap() + Days::new(5)), + "2014-04-11 07:08:09 +09:00" + ); + assert_eq!( + format!("{}", DstTester.with_ymd_and_hms(2014, 4, 6, 7, 8, 9).unwrap() + Days::new(10)), + "2014-04-16 07:08:09 +08:00" + ); + + assert_eq!( + format!("{}", DstTester.with_ymd_and_hms(2014, 9, 6, 7, 8, 9).unwrap() + Days::new(5)), + "2014-09-11 07:08:09 +08:00" + ); + assert_eq!( + format!("{}", DstTester.with_ymd_and_hms(2014, 9, 6, 7, 8, 9).unwrap() + Days::new(10)), + "2014-09-16 07:08:09 +09:00" + ); +} + +#[test] +fn test_datetime_sub_days() { + let est = FixedOffset::west_opt(5 * 60 * 60).unwrap(); + let kst = FixedOffset::east_opt(9 * 60 * 60).unwrap(); + + assert_eq!( + format!("{}", est.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap() - Days::new(5)), + "2014-05-01 07:08:09 -05:00" + ); + assert_eq!( + format!("{}", kst.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap() - Days::new(5)), + "2014-05-01 07:08:09 +09:00" + ); + + assert_eq!( + format!("{}", est.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap() - Days::new(35)), + "2014-04-01 07:08:09 -05:00" + ); + assert_eq!( + format!("{}", kst.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap() - Days::new(35)), + "2014-04-01 07:08:09 +09:00" + ); +} + +#[test] +fn test_datetime_add_months() { + let est = FixedOffset::west_opt(5 * 60 * 60).unwrap(); + let kst = FixedOffset::east_opt(9 * 60 * 60).unwrap(); + + assert_eq!( + format!("{}", est.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap() + Months::new(1)), + "2014-06-06 07:08:09 -05:00" + ); + assert_eq!( + format!("{}", kst.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap() + Months::new(1)), + "2014-06-06 07:08:09 +09:00" + ); + + assert_eq!( + format!("{}", est.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap() + Months::new(5)), + "2014-10-06 07:08:09 -05:00" + ); + assert_eq!( + format!("{}", kst.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap() + Months::new(5)), + "2014-10-06 07:08:09 +09:00" + ); +} + +#[test] +fn test_datetime_sub_months() { + let est = FixedOffset::west_opt(5 * 60 * 60).unwrap(); + let kst = FixedOffset::east_opt(9 * 60 * 60).unwrap(); + + assert_eq!( + format!("{}", est.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap() - Months::new(1)), + "2014-04-06 07:08:09 -05:00" + ); + assert_eq!( + format!("{}", kst.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap() - Months::new(1)), + "2014-04-06 07:08:09 +09:00" + ); + + assert_eq!( + format!("{}", est.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap() - Months::new(5)), + "2013-12-06 07:08:09 -05:00" + ); + assert_eq!( + format!("{}", kst.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap() - Months::new(5)), + "2013-12-06 07:08:09 +09:00" + ); +} + +// local helper function to easily create a DateTime +#[allow(clippy::too_many_arguments)] +fn ymdhms( + fixedoffset: &FixedOffset, + year: i32, + month: u32, + day: u32, + hour: u32, + min: u32, + sec: u32, +) -> DateTime { + fixedoffset.with_ymd_and_hms(year, month, day, hour, min, sec).unwrap() +} + +// local helper function to easily create a DateTime +#[allow(clippy::too_many_arguments)] +fn ymdhms_milli( + fixedoffset: &FixedOffset, + year: i32, + month: u32, + day: u32, + hour: u32, + min: u32, + sec: u32, + milli: u32, +) -> DateTime { + fixedoffset + .with_ymd_and_hms(year, month, day, hour, min, sec) + .unwrap() + .with_nanosecond(milli * 1_000_000) + .unwrap() +} + +// local helper function to easily create a DateTime +#[allow(clippy::too_many_arguments)] +#[cfg(feature = "alloc")] +fn ymdhms_micro( + fixedoffset: &FixedOffset, + year: i32, + month: u32, + day: u32, + hour: u32, + min: u32, + sec: u32, + micro: u32, +) -> DateTime { + fixedoffset + .with_ymd_and_hms(year, month, day, hour, min, sec) + .unwrap() + .with_nanosecond(micro * 1000) + .unwrap() +} + +// local helper function to easily create a DateTime +#[allow(clippy::too_many_arguments)] +#[cfg(feature = "alloc")] +fn ymdhms_nano( + fixedoffset: &FixedOffset, + year: i32, + month: u32, + day: u32, + hour: u32, + min: u32, + sec: u32, + nano: u32, +) -> DateTime { + fixedoffset + .with_ymd_and_hms(year, month, day, hour, min, sec) + .unwrap() + .with_nanosecond(nano) + .unwrap() +} + +// local helper function to easily create a DateTime +#[cfg(feature = "alloc")] +fn ymdhms_utc(year: i32, month: u32, day: u32, hour: u32, min: u32, sec: u32) -> DateTime { + Utc.with_ymd_and_hms(year, month, day, hour, min, sec).unwrap() +} + +// local helper function to easily create a DateTime +fn ymdhms_milli_utc( + year: i32, + month: u32, + day: u32, + hour: u32, + min: u32, + sec: u32, + milli: u32, +) -> DateTime { + Utc.with_ymd_and_hms(year, month, day, hour, min, sec) + .unwrap() + .with_nanosecond(milli * 1_000_000) + .unwrap() +} + +#[test] +fn test_datetime_offset() { + let est = FixedOffset::west_opt(5 * 60 * 60).unwrap(); + let edt = FixedOffset::west_opt(4 * 60 * 60).unwrap(); + let kst = FixedOffset::east_opt(9 * 60 * 60).unwrap(); + + assert_eq!( + format!("{}", Utc.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap()), + "2014-05-06 07:08:09 UTC" + ); + assert_eq!( + format!("{}", edt.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap()), + "2014-05-06 07:08:09 -04:00" + ); + assert_eq!( + format!("{}", kst.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap()), + "2014-05-06 07:08:09 +09:00" + ); + assert_eq!( + format!("{:?}", Utc.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap()), + "2014-05-06T07:08:09Z" + ); + assert_eq!( + format!("{:?}", edt.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap()), + "2014-05-06T07:08:09-04:00" + ); + assert_eq!( + format!("{:?}", kst.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap()), + "2014-05-06T07:08:09+09:00" + ); + + // edge cases + assert_eq!( + format!("{:?}", Utc.with_ymd_and_hms(2014, 5, 6, 0, 0, 0).unwrap()), + "2014-05-06T00:00:00Z" + ); + assert_eq!( + format!("{:?}", edt.with_ymd_and_hms(2014, 5, 6, 0, 0, 0).unwrap()), + "2014-05-06T00:00:00-04:00" + ); + assert_eq!( + format!("{:?}", kst.with_ymd_and_hms(2014, 5, 6, 0, 0, 0).unwrap()), + "2014-05-06T00:00:00+09:00" + ); + assert_eq!( + format!("{:?}", Utc.with_ymd_and_hms(2014, 5, 6, 23, 59, 59).unwrap()), + "2014-05-06T23:59:59Z" + ); + assert_eq!( + format!("{:?}", edt.with_ymd_and_hms(2014, 5, 6, 23, 59, 59).unwrap()), + "2014-05-06T23:59:59-04:00" + ); + assert_eq!( + format!("{:?}", kst.with_ymd_and_hms(2014, 5, 6, 23, 59, 59).unwrap()), + "2014-05-06T23:59:59+09:00" + ); + + let dt = Utc.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap(); + assert_eq!(dt, edt.with_ymd_and_hms(2014, 5, 6, 3, 8, 9).unwrap()); + assert_eq!( + dt + TimeDelta::try_seconds(3600 + 60 + 1).unwrap(), + Utc.with_ymd_and_hms(2014, 5, 6, 8, 9, 10).unwrap() + ); + assert_eq!( + dt.signed_duration_since(edt.with_ymd_and_hms(2014, 5, 6, 10, 11, 12).unwrap()), + TimeDelta::try_seconds(-7 * 3600 - 3 * 60 - 3).unwrap() + ); + + assert_eq!(*Utc.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap().offset(), Utc); + assert_eq!(*edt.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap().offset(), edt); + assert!(*edt.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap().offset() != est); +} + +#[test] +#[allow(clippy::needless_borrow, clippy::op_ref)] +fn signed_duration_since_autoref() { + let dt1 = Utc.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap(); + let dt2 = Utc.with_ymd_and_hms(2014, 3, 4, 5, 6, 7).unwrap(); + let diff1 = dt1.signed_duration_since(dt2); // Copy/consume + #[allow(clippy::needless_borrows_for_generic_args)] + let diff2 = dt2.signed_duration_since(&dt1); // Take by reference + assert_eq!(diff1, -diff2); + + let diff1 = dt1 - &dt2; // We can choose to subtract rhs by reference + let diff2 = dt2 - dt1; // Or consume rhs + assert_eq!(diff1, -diff2); +} + +#[test] +fn test_datetime_date_and_time() { + let tz = FixedOffset::east_opt(5 * 60 * 60).unwrap(); + let d = tz.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap(); + assert_eq!(d.time(), NaiveTime::from_hms_opt(7, 8, 9).unwrap()); + assert_eq!(d.date_naive(), NaiveDate::from_ymd_opt(2014, 5, 6).unwrap()); + + let tz = FixedOffset::east_opt(4 * 60 * 60).unwrap(); + let d = tz.with_ymd_and_hms(2016, 5, 4, 3, 2, 1).unwrap(); + assert_eq!(d.time(), NaiveTime::from_hms_opt(3, 2, 1).unwrap()); + assert_eq!(d.date_naive(), NaiveDate::from_ymd_opt(2016, 5, 4).unwrap()); + + let tz = FixedOffset::west_opt(13 * 60 * 60).unwrap(); + let d = tz.with_ymd_and_hms(2017, 8, 9, 12, 34, 56).unwrap(); + assert_eq!(d.time(), NaiveTime::from_hms_opt(12, 34, 56).unwrap()); + assert_eq!(d.date_naive(), NaiveDate::from_ymd_opt(2017, 8, 9).unwrap()); + + let utc_d = Utc.with_ymd_and_hms(2017, 8, 9, 12, 34, 56).unwrap(); + assert!(utc_d < d); +} + +#[test] +#[cfg(feature = "clock")] +fn test_datetime_with_timezone() { + let local_now = Local::now(); + let utc_now = local_now.with_timezone(&Utc); + let local_now2 = utc_now.with_timezone(&Local); + assert_eq!(local_now, local_now2); +} + +#[test] +#[cfg(feature = "alloc")] +fn test_datetime_rfc2822() { + let edt = FixedOffset::east_opt(5 * 60 * 60).unwrap(); + + // timezone 0 + assert_eq!( + Utc.with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap().to_rfc2822(), + "Wed, 18 Feb 2015 23:16:09 +0000" + ); + assert_eq!( + Utc.with_ymd_and_hms(2015, 2, 1, 23, 16, 9).unwrap().to_rfc2822(), + "Sun, 1 Feb 2015 23:16:09 +0000" + ); + // timezone +05 + assert_eq!( + edt.from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(23, 16, 9, 150) + .unwrap() + ) + .unwrap() + .to_rfc2822(), + "Wed, 18 Feb 2015 23:16:09 +0500" + ); + assert_eq!( + DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:60 +0500"), + Ok(edt + .from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(23, 59, 59, 1_000) + .unwrap() + ) + .unwrap()) + ); + assert!(DateTime::parse_from_rfc2822("31 DEC 262143 23:59 -2359").is_err()); + assert_eq!( + DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00"), + Ok(edt + .from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_micro_opt(23, 59, 59, 1_234_567) + .unwrap() + ) + .unwrap()) + ); + // seconds 60 + assert_eq!( + edt.from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_micro_opt(23, 59, 59, 1_234_567) + .unwrap() + ) + .unwrap() + .to_rfc2822(), + "Wed, 18 Feb 2015 23:59:60 +0500" + ); + + assert_eq!( + DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 +0000"), + Ok(FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap()) + ); + assert_eq!( + DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 -0000"), + Ok(FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap()) + ); + assert_eq!( + ymdhms_micro(&edt, 2015, 2, 18, 23, 59, 59, 1_234_567).to_rfc2822(), + "Wed, 18 Feb 2015 23:59:60 +0500" + ); + assert_eq!( + DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:58 +0500"), + Ok(ymdhms(&edt, 2015, 2, 18, 23, 59, 58)) + ); + assert_ne!( + DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:58 +0500"), + Ok(ymdhms_milli(&edt, 2015, 2, 18, 23, 59, 58, 500)) + ); + + // many varying whitespace intermixed + assert_eq!( + DateTime::parse_from_rfc2822( + "\t\t\tWed,\n\t\t18 \r\n\t\tFeb \u{3000} 2015\r\n\t\t\t23:59:58 \t+0500" + ), + Ok(ymdhms(&edt, 2015, 2, 18, 23, 59, 58)) + ); + // example from RFC 2822 Appendix A.5. + assert_eq!( + DateTime::parse_from_rfc2822( + "Thu,\n\t13\n Feb\n 1969\n 23:32\n -0330 (Newfoundland Time)" + ), + Ok( + ymdhms(&FixedOffset::east_opt(-3 * 60 * 60 - 30 * 60).unwrap(), 1969, 2, 13, 23, 32, 0,) + ) + ); + // example from RFC 2822 Appendix A.5. without trailing " (Newfoundland Time)" + assert_eq!( + DateTime::parse_from_rfc2822( + "Thu,\n\t13\n Feb\n 1969\n 23:32\n -0330" + ), + Ok( + ymdhms(&FixedOffset::east_opt(-3 * 60 * 60 - 30 * 60).unwrap(), 1969, 2, 13, 23, 32, 0,) + ) + ); + + // bad year + assert!(DateTime::parse_from_rfc2822("31 DEC 262143 23:59 -2359").is_err()); + // wrong format + assert!(DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 +00:00").is_err()); + // full name day of week + assert!(DateTime::parse_from_rfc2822("Wednesday, 18 Feb 2015 23:16:09 +0000").is_err()); + // full name day of week + assert!(DateTime::parse_from_rfc2822("Wednesday 18 Feb 2015 23:16:09 +0000").is_err()); + // wrong day of week separator '.' + assert!(DateTime::parse_from_rfc2822("Wed. 18 Feb 2015 23:16:09 +0000").is_err()); + // *trailing* space causes failure + assert!(DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 +0000 ").is_err()); +} + +#[test] +#[cfg(feature = "alloc")] +fn test_datetime_rfc3339() { + let edt5 = FixedOffset::east_opt(5 * 60 * 60).unwrap(); + let edt0 = FixedOffset::east_opt(0).unwrap(); + + // timezone 0 + assert_eq!( + Utc.with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap().to_rfc3339(), + "2015-02-18T23:16:09+00:00" + ); + // timezone +05 + assert_eq!( + edt5.from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(23, 16, 9, 150) + .unwrap() + ) + .unwrap() + .to_rfc3339(), + "2015-02-18T23:16:09.150+05:00" + ); + + assert_eq!(ymdhms_utc(2015, 2, 18, 23, 16, 9).to_rfc3339(), "2015-02-18T23:16:09+00:00"); + assert_eq!( + ymdhms_milli(&edt5, 2015, 2, 18, 23, 16, 9, 150).to_rfc3339(), + "2015-02-18T23:16:09.150+05:00" + ); + assert_eq!( + ymdhms_micro(&edt5, 2015, 2, 18, 23, 59, 59, 1_234_567).to_rfc3339(), + "2015-02-18T23:59:60.234567+05:00" + ); + assert_eq!( + DateTime::parse_from_rfc3339("2015-02-18T23:59:59.123+05:00"), + Ok(ymdhms_micro(&edt5, 2015, 2, 18, 23, 59, 59, 123_000)) + ); + assert_eq!( + DateTime::parse_from_rfc3339("2015-02-18T23:59:59.123456+05:00"), + Ok(ymdhms_micro(&edt5, 2015, 2, 18, 23, 59, 59, 123_456)) + ); + assert_eq!( + DateTime::parse_from_rfc3339("2015-02-18T23:59:59.123456789+05:00"), + Ok(ymdhms_nano(&edt5, 2015, 2, 18, 23, 59, 59, 123_456_789)) + ); + assert_eq!( + DateTime::parse_from_rfc3339("2015-02-18T23:16:09Z"), + Ok(ymdhms(&edt0, 2015, 2, 18, 23, 16, 9)) + ); + + assert_eq!( + ymdhms_micro(&edt5, 2015, 2, 18, 23, 59, 59, 1_234_567).to_rfc3339(), + "2015-02-18T23:59:60.234567+05:00" + ); + assert_eq!( + ymdhms_milli(&edt5, 2015, 2, 18, 23, 16, 9, 150).to_rfc3339(), + "2015-02-18T23:16:09.150+05:00" + ); + assert_eq!( + DateTime::parse_from_rfc3339("2015-02-18T00:00:00.234567+05:00"), + Ok(ymdhms_micro(&edt5, 2015, 2, 18, 0, 0, 0, 234_567)) + ); + assert_eq!( + DateTime::parse_from_rfc3339("2015-02-18T23:16:09Z"), + Ok(ymdhms(&edt0, 2015, 2, 18, 23, 16, 9)) + ); + assert_eq!( + DateTime::parse_from_rfc3339("2015-02-18 23:59:60.234567+05:00"), + Ok(ymdhms_micro(&edt5, 2015, 2, 18, 23, 59, 59, 1_234_567)) + ); + assert_eq!(ymdhms_utc(2015, 2, 18, 23, 16, 9).to_rfc3339(), "2015-02-18T23:16:09+00:00"); + + assert!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567 +05:00").is_err()); + assert!(DateTime::parse_from_rfc3339("2015-02-18T23:059:60.234567+05:00").is_err()); + assert!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00PST").is_err()); + assert!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567+PST").is_err()); + assert!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567PST").is_err()); + assert!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567+0500").is_err()); + assert!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00:00").is_err()); + assert!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567:+05:00").is_err()); + assert!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00 ").is_err()); + assert!(DateTime::parse_from_rfc3339(" 2015-02-18T23:59:60.234567+05:00").is_err()); + assert!(DateTime::parse_from_rfc3339("2015- 02-18T23:59:60.234567+05:00").is_err()); + assert!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567A+05:00").is_err()); +} + +#[test] +#[cfg(feature = "alloc")] +fn test_rfc3339_opts() { + use crate::SecondsFormat::*; + let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap(); + let dt = pst + .from_local_datetime( + &NaiveDate::from_ymd_opt(2018, 1, 11) + .unwrap() + .and_hms_nano_opt(10, 5, 13, 84_660_000) + .unwrap(), + ) + .unwrap(); + assert_eq!(dt.to_rfc3339_opts(Secs, false), "2018-01-11T10:05:13+08:00"); + assert_eq!(dt.to_rfc3339_opts(Secs, true), "2018-01-11T10:05:13+08:00"); + assert_eq!(dt.to_rfc3339_opts(Millis, false), "2018-01-11T10:05:13.084+08:00"); + assert_eq!(dt.to_rfc3339_opts(Micros, false), "2018-01-11T10:05:13.084660+08:00"); + assert_eq!(dt.to_rfc3339_opts(Nanos, false), "2018-01-11T10:05:13.084660000+08:00"); + assert_eq!(dt.to_rfc3339_opts(AutoSi, false), "2018-01-11T10:05:13.084660+08:00"); + + let ut = dt.naive_utc().and_utc(); + assert_eq!(ut.to_rfc3339_opts(Secs, false), "2018-01-11T02:05:13+00:00"); + assert_eq!(ut.to_rfc3339_opts(Secs, true), "2018-01-11T02:05:13Z"); + assert_eq!(ut.to_rfc3339_opts(Millis, false), "2018-01-11T02:05:13.084+00:00"); + assert_eq!(ut.to_rfc3339_opts(Millis, true), "2018-01-11T02:05:13.084Z"); + assert_eq!(ut.to_rfc3339_opts(Micros, true), "2018-01-11T02:05:13.084660Z"); + assert_eq!(ut.to_rfc3339_opts(Nanos, true), "2018-01-11T02:05:13.084660000Z"); + assert_eq!(ut.to_rfc3339_opts(AutoSi, true), "2018-01-11T02:05:13.084660Z"); +} + +#[test] +#[should_panic] +#[cfg(feature = "alloc")] +fn test_rfc3339_opts_nonexhaustive() { + use crate::SecondsFormat; + let dt = Utc.with_ymd_and_hms(1999, 10, 9, 1, 2, 3).unwrap(); + let _ = dt.to_rfc3339_opts(SecondsFormat::__NonExhaustive, true); +} + +#[test] +fn test_datetime_from_str() { + assert_eq!( + "2015-02-18T23:16:9.15Z".parse::>(), + Ok(FixedOffset::east_opt(0) + .unwrap() + .from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(23, 16, 9, 150) + .unwrap() + ) + .unwrap()) + ); + assert_eq!( + "2015-02-18T23:16:9.15Z".parse::>(), + Ok(Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(23, 16, 9, 150) + .unwrap() + ) + .unwrap()) + ); + assert_eq!( + "2015-02-18T23:16:9.15 UTC".parse::>(), + Ok(Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(23, 16, 9, 150) + .unwrap() + ) + .unwrap()) + ); + assert_eq!( + "2015-02-18T23:16:9.15UTC".parse::>(), + Ok(Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(23, 16, 9, 150) + .unwrap() + ) + .unwrap()) + ); + assert_eq!( + "2015-02-18T23:16:9.15Utc".parse::>(), + Ok(Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(23, 16, 9, 150) + .unwrap() + ) + .unwrap()) + ); + + assert_eq!( + "2015-2-18T23:16:9.15Z".parse::>(), + Ok(FixedOffset::east_opt(0) + .unwrap() + .from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(23, 16, 9, 150) + .unwrap() + ) + .unwrap()) + ); + assert_eq!( + "2015-2-18T13:16:9.15-10:00".parse::>(), + Ok(FixedOffset::west_opt(10 * 3600) + .unwrap() + .from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(13, 16, 9, 150) + .unwrap() + ) + .unwrap()) + ); + assert!("2015-2-18T23:16:9.15".parse::>().is_err()); + + assert_eq!( + "2015-2-18T23:16:9.15Z".parse::>(), + Ok(Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(23, 16, 9, 150) + .unwrap() + ) + .unwrap()) + ); + assert_eq!( + "2015-2-18T13:16:9.15-10:00".parse::>(), + Ok(Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(23, 16, 9, 150) + .unwrap() + ) + .unwrap()) + ); + assert!("2015-2-18T23:16:9.15".parse::>().is_err()); + assert!("2015-02-18T23:16:9.15øøø".parse::>().is_err()); + + // no test for `DateTime`, we cannot verify that much. +} + +#[test] +fn test_parse_datetime_utc() { + // valid cases + let valid = [ + "2001-02-03T04:05:06Z", + "2001-02-03T04:05:06+0000", + "2001-02-03T04:05:06-00:00", + "2001-02-03T04:05:06-01:00", + "2012-12-12 12:12:12Z", + "2012-12-12t12:12:12Z", + "2012-12-12T12:12:12Z", + "2012 -12-12T12:12:12Z", + "2012 -12-12T12:12:12Z", + "2012- 12-12T12:12:12Z", + "2012- 12-12T12:12:12Z", + "2012-12-12T 12:12:12Z", + "2012-12-12T12 :12:12Z", + "2012-12-12T12 :12:12Z", + "2012-12-12T12: 12:12Z", + "2012-12-12T12: 12:12Z", + "2012-12-12T12 : 12:12Z", + "2012-12-12T12:12:12Z ", + " 2012-12-12T12:12:12Z", + "2015-02-18T23:16:09.153Z", + "2015-2-18T23:16:09.153Z", + "+2015-2-18T23:16:09.153Z", + "-77-02-18T23:16:09Z", + "+82701-05-6T15:9:60.898989898989Z", + ]; + for &s in &valid { + eprintln!("test_parse_datetime_utc valid {:?}", s); + let d = match s.parse::>() { + Ok(d) => d, + Err(e) => panic!("parsing `{}` has failed: {}", s, e), + }; + let s_ = format!("{:?}", d); + // `s` and `s_` may differ, but `s.parse()` and `s_.parse()` must be same + let d_ = match s_.parse::>() { + Ok(d) => d, + Err(e) => { + panic!("`{}` is parsed into `{:?}`, but reparsing that has failed: {}", s, d, e) + } + }; + assert!( + d == d_, + "`{}` is parsed into `{:?}`, but reparsed result `{:?}` does not match", + s, + d, + d_ + ); + } + + // some invalid cases + // since `ParseErrorKind` is private, all we can do is to check if there was an error + let invalid = [ + "", // empty + "Z", // missing data + "15Z", // missing data + "15:8:9Z", // missing date + "15-8-9Z", // missing time or date + "Fri, 09 Aug 2013 23:54:35 GMT", // valid datetime, wrong format + "Sat Jun 30 23:59:60 2012", // valid datetime, wrong format + "1441497364.649", // valid datetime, wrong format + "+1441497364.649", // valid datetime, wrong format + "+1441497364", // valid datetime, wrong format + "+1441497364Z", // valid datetime, wrong format + "2014/02/03 04:05:06Z", // valid datetime, wrong format + "2001-02-03T04:05:0600:00", // valid datetime, timezone too close + "2015-15-15T15:15:15Z", // invalid datetime + "2012-12-12T12:12:12x", // invalid timezone + "2012-123-12T12:12:12Z", // invalid month + "2012-12-77T12:12:12Z", // invalid day + "2012-12-12T26:12:12Z", // invalid hour + "2012-12-12T12:61:12Z", // invalid minute + "2012-12-12T12:12:62Z", // invalid second + "2012-12-12 T12:12:12Z", // space after date + "2012-12-12T12:12:12ZZ", // trailing literal 'Z' + "+802701-12-12T12:12:12Z", // invalid year (out of bounds) + "+ 2012-12-12T12:12:12Z", // invalid space before year + " +82701 - 05 - 6 T 15 : 9 : 60.898989898989 Z", // valid datetime, wrong format + ]; + for &s in &invalid { + eprintln!("test_parse_datetime_utc invalid {:?}", s); + assert!(s.parse::>().is_err()); + } +} + +#[test] +fn test_parse_from_str() { + let edt = FixedOffset::east_opt(570 * 60).unwrap(); + let edt0 = FixedOffset::east_opt(0).unwrap(); + let wdt = FixedOffset::west_opt(10 * 3600).unwrap(); + assert_eq!( + DateTime::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), + Ok(ymdhms(&edt, 2014, 5, 7, 12, 34, 56)) + ); // ignore offset + assert!(DateTime::parse_from_str("20140507000000", "%Y%m%d%H%M%S").is_err()); // no offset + assert!( + DateTime::parse_from_str("Fri, 09 Aug 2013 23:54:35 GMT", "%a, %d %b %Y %H:%M:%S GMT") + .is_err() + ); + assert_eq!( + DateTime::parse_from_str("0", "%s").unwrap(), + DateTime::from_timestamp(0, 0).unwrap().fixed_offset() + ); + + assert_eq!( + "2015-02-18T23:16:9.15Z".parse::>(), + Ok(ymdhms_milli(&edt0, 2015, 2, 18, 23, 16, 9, 150)) + ); + assert_eq!( + "2015-02-18T23:16:9.15Z".parse::>(), + Ok(ymdhms_milli_utc(2015, 2, 18, 23, 16, 9, 150)), + ); + assert_eq!( + "2015-02-18T23:16:9.15 UTC".parse::>(), + Ok(ymdhms_milli_utc(2015, 2, 18, 23, 16, 9, 150)) + ); + assert_eq!( + "2015-02-18T23:16:9.15UTC".parse::>(), + Ok(ymdhms_milli_utc(2015, 2, 18, 23, 16, 9, 150)) + ); + + assert_eq!( + "2015-2-18T23:16:9.15Z".parse::>(), + Ok(ymdhms_milli(&edt0, 2015, 2, 18, 23, 16, 9, 150)) + ); + assert_eq!( + "2015-2-18T13:16:9.15-10:00".parse::>(), + Ok(ymdhms_milli(&wdt, 2015, 2, 18, 13, 16, 9, 150)) + ); + assert!("2015-2-18T23:16:9.15".parse::>().is_err()); + + assert_eq!( + "2015-2-18T23:16:9.15Z".parse::>(), + Ok(ymdhms_milli_utc(2015, 2, 18, 23, 16, 9, 150)) + ); + assert_eq!( + "2015-2-18T13:16:9.15-10:00".parse::>(), + Ok(ymdhms_milli_utc(2015, 2, 18, 23, 16, 9, 150)) + ); + assert!("2015-2-18T23:16:9.15".parse::>().is_err()); + + // no test for `DateTime`, we cannot verify that much. +} + +#[test] +fn test_datetime_parse_from_str() { + let dt = ymdhms(&FixedOffset::east_opt(-9 * 60 * 60).unwrap(), 2013, 8, 9, 23, 54, 35); + let parse = DateTime::parse_from_str; + + // timezone variations + + // + // %Z + // + // wrong timezone format + assert!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %Z").is_err()); + // bad timezone data? + assert!(parse("Aug 09 2013 23:54:35 PST", "%b %d %Y %H:%M:%S %Z").is_err()); + // bad timezone data + assert!(parse("Aug 09 2013 23:54:35 XXXXX", "%b %d %Y %H:%M:%S %Z").is_err()); + + // + // %z + // + assert_eq!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09 00", "%b %d %Y %H:%M:%S %z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00", "%b %d %Y %H:%M:%S %z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09 : 00", "%b %d %Y %H:%M:%S %z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 --0900", "%b %d %Y %H:%M:%S -%z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 +-0900", "%b %d %Y %H:%M:%S +%z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00 ", "%b %d %Y %H:%M:%S %z "), Ok(dt)); + // trailing newline after timezone + assert!(parse("Aug 09 2013 23:54:35 -09:00\n", "%b %d %Y %H:%M:%S %z").is_err()); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00\n", "%b %d %Y %H:%M:%S %z "), Ok(dt)); + // trailing colon + assert!(parse("Aug 09 2013 23:54:35 -09:00:", "%b %d %Y %H:%M:%S %z").is_err()); + // trailing colon with space + assert!(parse("Aug 09 2013 23:54:35 -09:00: ", "%b %d %Y %H:%M:%S %z ").is_err()); + // trailing colon, mismatch space + assert!(parse("Aug 09 2013 23:54:35 -09:00:", "%b %d %Y %H:%M:%S %z ").is_err()); + // wrong timezone data + assert!(parse("Aug 09 2013 23:54:35 -09", "%b %d %Y %H:%M:%S %z").is_err()); + assert_eq!(parse("Aug 09 2013 23:54:35 -09::00", "%b %d %Y %H:%M:%S %z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900::", "%b %d %Y %H:%M:%S %z::"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00:00", "%b %d %Y %H:%M:%S %z:00"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00:00 ", "%b %d %Y %H:%M:%S %z:00 "), Ok(dt)); + + // + // %:z + // + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00", "%b %d %Y %H:%M:%S %:z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %:z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09 00", "%b %d %Y %H:%M:%S %:z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09 : 00", "%b %d %Y %H:%M:%S %:z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09 : 00:", "%b %d %Y %H:%M:%S %:z:"), Ok(dt)); + // wrong timezone data + assert!(parse("Aug 09 2013 23:54:35 -09", "%b %d %Y %H:%M:%S %:z").is_err()); + assert_eq!(parse("Aug 09 2013 23:54:35 -09::00", "%b %d %Y %H:%M:%S %:z"), Ok(dt)); + // timezone data hs too many colons + assert!(parse("Aug 09 2013 23:54:35 -09:00:", "%b %d %Y %H:%M:%S %:z").is_err()); + // timezone data hs too many colons + assert!(parse("Aug 09 2013 23:54:35 -09:00::", "%b %d %Y %H:%M:%S %:z").is_err()); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00::", "%b %d %Y %H:%M:%S %:z::"), Ok(dt)); + + // + // %::z + // + assert_eq!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %::z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00", "%b %d %Y %H:%M:%S %::z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09 : 00", "%b %d %Y %H:%M:%S %::z"), Ok(dt)); + // mismatching colon expectations + assert!(parse("Aug 09 2013 23:54:35 -09:00:00", "%b %d %Y %H:%M:%S %::z").is_err()); + assert_eq!(parse("Aug 09 2013 23:54:35 -09::00", "%b %d %Y %H:%M:%S %::z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09::00", "%b %d %Y %H:%M:%S %:z"), Ok(dt)); + // wrong timezone data + assert!(parse("Aug 09 2013 23:54:35 -09", "%b %d %Y %H:%M:%S %::z").is_err()); + assert_eq!(parse("Aug 09 2013 23:54:35 -09001234", "%b %d %Y %H:%M:%S %::z1234"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:001234", "%b %d %Y %H:%M:%S %::z1234"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900 ", "%b %d %Y %H:%M:%S %::z "), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900\t\n", "%b %d %Y %H:%M:%S %::z\t\n"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900:", "%b %d %Y %H:%M:%S %::z:"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 :-0900:0", "%b %d %Y %H:%M:%S :%::z:0"), Ok(dt)); + // mismatching colons and spaces + assert!(parse("Aug 09 2013 23:54:35 :-0900: ", "%b %d %Y %H:%M:%S :%::z::").is_err()); + // mismatching colons expectations + assert!(parse("Aug 09 2013 23:54:35 -09:00:00", "%b %d %Y %H:%M:%S %::z").is_err()); + assert_eq!(parse("Aug 09 2013 -0900: 23:54:35", "%b %d %Y %::z: %H:%M:%S"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 :-0900:0 23:54:35", "%b %d %Y :%::z:0 %H:%M:%S"), Ok(dt)); + // mismatching colons expectations mid-string + assert!(parse("Aug 09 2013 :-0900: 23:54:35", "%b %d %Y :%::z %H:%M:%S").is_err()); + // mismatching colons expectations, before end + assert!(parse("Aug 09 2013 23:54:35 -09:00:00 ", "%b %d %Y %H:%M:%S %::z ").is_err()); + + // + // %:::z + // + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00", "%b %d %Y %H:%M:%S %:::z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %:::z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900 ", "%b %d %Y %H:%M:%S %:::z "), Ok(dt)); + // wrong timezone data + assert!(parse("Aug 09 2013 23:54:35 -09", "%b %d %Y %H:%M:%S %:::z").is_err()); + + // + // %::::z + // + // too many colons + assert!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %::::z").is_err()); + // too many colons + assert!(parse("Aug 09 2013 23:54:35 -09:00", "%b %d %Y %H:%M:%S %::::z").is_err()); + // too many colons + assert!(parse("Aug 09 2013 23:54:35 -09:00:", "%b %d %Y %H:%M:%S %::::z").is_err()); + // too many colons + assert!(parse("Aug 09 2013 23:54:35 -09:00:00", "%b %d %Y %H:%M:%S %::::z").is_err()); + + // + // %#z + // + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00", "%b %d %Y %H:%M:%S %#z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %#z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00 ", "%b %d %Y %H:%M:%S %#z "), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900 ", "%b %d %Y %H:%M:%S %#z "), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09", "%b %d %Y %H:%M:%S %#z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %#z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:", "%b %d %Y %H:%M:%S %#z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09: ", "%b %d %Y %H:%M:%S %#z "), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35+-09", "%b %d %Y %H:%M:%S+%#z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35--09", "%b %d %Y %H:%M:%S-%#z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 -09:00 23:54:35", "%b %d %Y %#z%H:%M:%S"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 -0900 23:54:35", "%b %d %Y %#z%H:%M:%S"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 -090023:54:35", "%b %d %Y %#z%H:%M:%S"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 -09:0023:54:35", "%b %d %Y %#z%H:%M:%S"), Ok(dt)); + // timezone with partial minutes adjacent hours + assert_ne!(parse("Aug 09 2013 -09023:54:35", "%b %d %Y %#z%H:%M:%S"), Ok(dt)); + // bad timezone data + assert!(parse("Aug 09 2013 23:54:35 -09:00:00", "%b %d %Y %H:%M:%S %#z").is_err()); + // bad timezone data (partial minutes) + assert!(parse("Aug 09 2013 23:54:35 -090", "%b %d %Y %H:%M:%S %#z").is_err()); + // bad timezone data (partial minutes) with trailing space + assert!(parse("Aug 09 2013 23:54:35 -090 ", "%b %d %Y %H:%M:%S %#z ").is_err()); + // bad timezone data (partial minutes) mid-string + assert!(parse("Aug 09 2013 -090 23:54:35", "%b %d %Y %#z %H:%M:%S").is_err()); + // bad timezone data + assert!(parse("Aug 09 2013 -09:00:00 23:54:35", "%b %d %Y %#z %H:%M:%S").is_err()); + // timezone data ambiguous with hours + assert!(parse("Aug 09 2013 -09:00:23:54:35", "%b %d %Y %#z%H:%M:%S").is_err()); +} + +#[test] +fn test_to_string_round_trip() { + let dt = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap(); + let _dt: DateTime = dt.to_string().parse().unwrap(); + + let ndt_fixed = dt.with_timezone(&FixedOffset::east_opt(3600).unwrap()); + let _dt: DateTime = ndt_fixed.to_string().parse().unwrap(); + + let ndt_fixed = dt.with_timezone(&FixedOffset::east_opt(0).unwrap()); + let _dt: DateTime = ndt_fixed.to_string().parse().unwrap(); +} + +#[test] +#[cfg(feature = "clock")] +fn test_to_string_round_trip_with_local() { + let ndt = Local::now(); + let _dt: DateTime = ndt.to_string().parse().unwrap(); +} + +#[test] +#[cfg(feature = "clock")] +fn test_datetime_format_with_local() { + // if we are not around the year boundary, local and UTC date should have the same year + let dt = Local::now().with_month(5).unwrap(); + assert_eq!(dt.format("%Y").to_string(), dt.with_timezone(&Utc).format("%Y").to_string()); +} + +#[test] +fn test_datetime_is_send_and_copy() { + #[derive(Clone)] + struct Tz { + _not_send: *const i32, + } + impl TimeZone for Tz { + type Offset = Off; + + fn from_offset(_: &Self::Offset) -> Self { + unimplemented!() + } + fn offset_from_local_date(&self, _: &NaiveDate) -> crate::MappedLocalTime { + unimplemented!() + } + fn offset_from_local_datetime( + &self, + _: &NaiveDateTime, + ) -> crate::MappedLocalTime { + unimplemented!() + } + fn offset_from_utc_date(&self, _: &NaiveDate) -> Self::Offset { + unimplemented!() + } + fn offset_from_utc_datetime(&self, _: &NaiveDateTime) -> Self::Offset { + unimplemented!() + } + } + + #[derive(Copy, Clone, Debug)] + struct Off(()); + impl Offset for Off { + fn fix(&self) -> FixedOffset { + unimplemented!() + } + } + + fn _assert_send_copy() {} + // `DateTime` is `Send + Copy` if the offset is. + _assert_send_copy::>(); +} + +#[test] +fn test_subsecond_part() { + let datetime = Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2014, 7, 8) + .unwrap() + .and_hms_nano_opt(9, 10, 11, 1234567) + .unwrap(), + ) + .unwrap(); + + assert_eq!(1, datetime.timestamp_subsec_millis()); + assert_eq!(1234, datetime.timestamp_subsec_micros()); + assert_eq!(1234567, datetime.timestamp_subsec_nanos()); +} + +// Some targets, such as `wasm32-wasi`, have a problematic definition of `SystemTime`, such as an +// `i32` (year 2035 problem), or an `u64` (no values before `UNIX-EPOCH`). +// See https://github.com/rust-lang/rust/issues/44394. +#[test] +#[cfg(all(feature = "std", not(all(target_arch = "wasm32", target_os = "wasi"))))] +fn test_from_system_time() { + use std::time::{Duration, SystemTime, UNIX_EPOCH}; + + let nanos = 999_999_000; + + let epoch = Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap(); + + // SystemTime -> DateTime + assert_eq!(DateTime::::from(UNIX_EPOCH), epoch); + assert_eq!( + DateTime::::from(UNIX_EPOCH + Duration::new(999_999_999, nanos)), + Utc.from_local_datetime( + &NaiveDate::from_ymd_opt(2001, 9, 9) + .unwrap() + .and_hms_nano_opt(1, 46, 39, nanos) + .unwrap() + ) + .unwrap() + ); + assert_eq!( + DateTime::::from(UNIX_EPOCH - Duration::new(999_999_999, nanos)), + Utc.from_local_datetime( + &NaiveDate::from_ymd_opt(1938, 4, 24) + .unwrap() + .and_hms_nano_opt(22, 13, 20, 1_000) + .unwrap() + ) + .unwrap() + ); + + // DateTime -> SystemTime + assert_eq!(SystemTime::from(epoch), UNIX_EPOCH); + assert_eq!( + SystemTime::from( + Utc.from_local_datetime( + &NaiveDate::from_ymd_opt(2001, 9, 9) + .unwrap() + .and_hms_nano_opt(1, 46, 39, nanos) + .unwrap() + ) + .unwrap() + ), + UNIX_EPOCH + Duration::new(999_999_999, nanos) + ); + assert_eq!( + SystemTime::from( + Utc.from_local_datetime( + &NaiveDate::from_ymd_opt(1938, 4, 24) + .unwrap() + .and_hms_nano_opt(22, 13, 20, 1_000) + .unwrap() + ) + .unwrap() + ), + UNIX_EPOCH - Duration::new(999_999_999, nanos) + ); + + // DateTime -> SystemTime (via `with_timezone`) + #[cfg(feature = "clock")] + { + assert_eq!(SystemTime::from(epoch.with_timezone(&Local)), UNIX_EPOCH); + } + assert_eq!( + SystemTime::from(epoch.with_timezone(&FixedOffset::east_opt(32400).unwrap())), + UNIX_EPOCH + ); + assert_eq!( + SystemTime::from(epoch.with_timezone(&FixedOffset::west_opt(28800).unwrap())), + UNIX_EPOCH + ); +} + +#[test] +#[allow(deprecated)] +fn test_datetime_from_local() { + // 2000-01-12T02:00:00Z + let naivedatetime_utc = + NaiveDate::from_ymd_opt(2000, 1, 12).unwrap().and_hms_opt(2, 0, 0).unwrap(); + let datetime_utc = DateTime::::from_utc(naivedatetime_utc, Utc); + + // 2000-01-12T10:00:00+8:00:00 + let timezone_east = FixedOffset::east_opt(8 * 60 * 60).unwrap(); + let naivedatetime_east = + NaiveDate::from_ymd_opt(2000, 1, 12).unwrap().and_hms_opt(10, 0, 0).unwrap(); + let datetime_east = DateTime::::from_local(naivedatetime_east, timezone_east); + + // 2000-01-11T19:00:00-7:00:00 + let timezone_west = FixedOffset::west_opt(7 * 60 * 60).unwrap(); + let naivedatetime_west = + NaiveDate::from_ymd_opt(2000, 1, 11).unwrap().and_hms_opt(19, 0, 0).unwrap(); + let datetime_west = DateTime::::from_local(naivedatetime_west, timezone_west); + + assert_eq!(datetime_east, datetime_utc.with_timezone(&timezone_east)); + assert_eq!(datetime_west, datetime_utc.with_timezone(&timezone_west)); +} + +#[test] +#[cfg(feature = "clock")] +fn test_datetime_before_windows_api_limits() { + // dt corresponds to `FILETIME = 147221225472` from issue 651. + // (https://github.com/chronotope/chrono/issues/651) + // This used to fail on Windows for timezones with an offset of -5:00 or greater. + // The API limits years to 1601..=30827. + let dt = NaiveDate::from_ymd_opt(1601, 1, 1).unwrap().and_hms_milli_opt(4, 5, 22, 122).unwrap(); + let local_dt = Local.from_utc_datetime(&dt); + dbg!(local_dt); +} + +#[test] +#[cfg(feature = "clock")] +fn test_years_elapsed() { + // A bit more than 1 year + let one_year_ago = Utc::now().date_naive() - Days::new(400); + // A bit more than 2 years + let two_year_ago = Utc::now().date_naive() - Days::new(750); + + assert_eq!(Utc::now().date_naive().years_since(one_year_ago), Some(1)); + assert_eq!(Utc::now().date_naive().years_since(two_year_ago), Some(2)); + + // If the given DateTime is later than now, the function will always return 0. + let future = Utc::now().date_naive() + Days(100); + assert_eq!(Utc::now().date_naive().years_since(future), None); +} + +#[test] +fn test_datetime_add_assign() { + let naivedatetime = NaiveDate::from_ymd_opt(2000, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); + let datetime = naivedatetime.and_utc(); + let mut datetime_add = datetime; + + datetime_add += TimeDelta::try_seconds(60).unwrap(); + assert_eq!(datetime_add, datetime + TimeDelta::try_seconds(60).unwrap()); + + let timezone = FixedOffset::east_opt(60 * 60).unwrap(); + let datetime = datetime.with_timezone(&timezone); + let datetime_add = datetime_add.with_timezone(&timezone); + + assert_eq!(datetime_add, datetime + TimeDelta::try_seconds(60).unwrap()); + + let timezone = FixedOffset::west_opt(2 * 60 * 60).unwrap(); + let datetime = datetime.with_timezone(&timezone); + let datetime_add = datetime_add.with_timezone(&timezone); + + assert_eq!(datetime_add, datetime + TimeDelta::try_seconds(60).unwrap()); +} + +#[test] +#[cfg(feature = "clock")] +fn test_datetime_add_assign_local() { + let naivedatetime = NaiveDate::from_ymd_opt(2022, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); + + let datetime = Local.from_utc_datetime(&naivedatetime); + let mut datetime_add = Local.from_utc_datetime(&naivedatetime); + + // ensure we cross a DST transition + for i in 1..=365 { + datetime_add += TimeDelta::try_days(1).unwrap(); + assert_eq!(datetime_add, datetime + TimeDelta::try_days(i).unwrap()) + } +} + +#[test] +fn test_datetime_sub_assign() { + let naivedatetime = NaiveDate::from_ymd_opt(2000, 1, 1).unwrap().and_hms_opt(12, 0, 0).unwrap(); + let datetime = naivedatetime.and_utc(); + let mut datetime_sub = datetime; + + datetime_sub -= TimeDelta::try_minutes(90).unwrap(); + assert_eq!(datetime_sub, datetime - TimeDelta::try_minutes(90).unwrap()); + + let timezone = FixedOffset::east_opt(60 * 60).unwrap(); + let datetime = datetime.with_timezone(&timezone); + let datetime_sub = datetime_sub.with_timezone(&timezone); + + assert_eq!(datetime_sub, datetime - TimeDelta::try_minutes(90).unwrap()); + + let timezone = FixedOffset::west_opt(2 * 60 * 60).unwrap(); + let datetime = datetime.with_timezone(&timezone); + let datetime_sub = datetime_sub.with_timezone(&timezone); + + assert_eq!(datetime_sub, datetime - TimeDelta::try_minutes(90).unwrap()); +} + +#[test] +fn test_min_max_getters() { + let offset_min = FixedOffset::west_opt(2 * 60 * 60).unwrap(); + let beyond_min = offset_min.from_utc_datetime(&NaiveDateTime::MIN); + let offset_max = FixedOffset::east_opt(2 * 60 * 60).unwrap(); + let beyond_max = offset_max.from_utc_datetime(&NaiveDateTime::MAX); + + assert_eq!(format!("{:?}", beyond_min), "-262144-12-31T22:00:00-02:00"); + // RFC 2822 doesn't support years with more than 4 digits. + // assert_eq!(beyond_min.to_rfc2822(), ""); + #[cfg(feature = "alloc")] + assert_eq!(beyond_min.to_rfc3339(), "-262144-12-31T22:00:00-02:00"); + #[cfg(feature = "alloc")] + assert_eq!( + beyond_min.format("%Y-%m-%dT%H:%M:%S%:z").to_string(), + "-262144-12-31T22:00:00-02:00" + ); + assert_eq!(beyond_min.year(), -262144); + assert_eq!(beyond_min.month(), 12); + assert_eq!(beyond_min.month0(), 11); + assert_eq!(beyond_min.day(), 31); + assert_eq!(beyond_min.day0(), 30); + assert_eq!(beyond_min.ordinal(), 366); + assert_eq!(beyond_min.ordinal0(), 365); + assert_eq!(beyond_min.weekday(), Weekday::Wed); + assert_eq!(beyond_min.iso_week().year(), -262143); + assert_eq!(beyond_min.iso_week().week(), 1); + assert_eq!(beyond_min.hour(), 22); + assert_eq!(beyond_min.minute(), 0); + assert_eq!(beyond_min.second(), 0); + assert_eq!(beyond_min.nanosecond(), 0); + + assert_eq!(format!("{:?}", beyond_max), "+262143-01-01T01:59:59.999999999+02:00"); + // RFC 2822 doesn't support years with more than 4 digits. + // assert_eq!(beyond_max.to_rfc2822(), ""); + #[cfg(feature = "alloc")] + assert_eq!(beyond_max.to_rfc3339(), "+262143-01-01T01:59:59.999999999+02:00"); + #[cfg(feature = "alloc")] + assert_eq!( + beyond_max.format("%Y-%m-%dT%H:%M:%S%.9f%:z").to_string(), + "+262143-01-01T01:59:59.999999999+02:00" + ); + assert_eq!(beyond_max.year(), 262143); + assert_eq!(beyond_max.month(), 1); + assert_eq!(beyond_max.month0(), 0); + assert_eq!(beyond_max.day(), 1); + assert_eq!(beyond_max.day0(), 0); + assert_eq!(beyond_max.ordinal(), 1); + assert_eq!(beyond_max.ordinal0(), 0); + assert_eq!(beyond_max.weekday(), Weekday::Tue); + assert_eq!(beyond_max.iso_week().year(), 262143); + assert_eq!(beyond_max.iso_week().week(), 1); + assert_eq!(beyond_max.hour(), 1); + assert_eq!(beyond_max.minute(), 59); + assert_eq!(beyond_max.second(), 59); + assert_eq!(beyond_max.nanosecond(), 999_999_999); +} + +#[test] +fn test_min_max_setters() { + let offset_min = FixedOffset::west_opt(2 * 60 * 60).unwrap(); + let beyond_min = offset_min.from_utc_datetime(&NaiveDateTime::MIN); + let offset_max = FixedOffset::east_opt(2 * 60 * 60).unwrap(); + let beyond_max = offset_max.from_utc_datetime(&NaiveDateTime::MAX); + + assert_eq!(beyond_min.with_year(2020).unwrap().year(), 2020); + assert_eq!(beyond_min.with_year(beyond_min.year()), Some(beyond_min)); + assert_eq!(beyond_min.with_month(beyond_min.month()), Some(beyond_min)); + assert_eq!(beyond_min.with_month(3), None); + assert_eq!(beyond_min.with_month0(beyond_min.month0()), Some(beyond_min)); + assert_eq!(beyond_min.with_month0(3), None); + assert_eq!(beyond_min.with_day(beyond_min.day()), Some(beyond_min)); + assert_eq!(beyond_min.with_day(15), None); + assert_eq!(beyond_min.with_day0(beyond_min.day0()), Some(beyond_min)); + assert_eq!(beyond_min.with_day0(15), None); + assert_eq!(beyond_min.with_ordinal(beyond_min.ordinal()), Some(beyond_min)); + assert_eq!(beyond_min.with_ordinal(200), None); + assert_eq!(beyond_min.with_ordinal0(beyond_min.ordinal0()), Some(beyond_min)); + assert_eq!(beyond_min.with_ordinal0(200), None); + assert_eq!(beyond_min.with_hour(beyond_min.hour()), Some(beyond_min)); + assert_eq!( + beyond_min.with_hour(23), + beyond_min.checked_add_signed(TimeDelta::try_hours(1).unwrap()) + ); + assert_eq!(beyond_min.with_hour(5), None); + assert_eq!(beyond_min.with_minute(0), Some(beyond_min)); + assert_eq!(beyond_min.with_second(0), Some(beyond_min)); + assert_eq!(beyond_min.with_nanosecond(0), Some(beyond_min)); + + assert_eq!(beyond_max.with_year(2020).unwrap().year(), 2020); + assert_eq!(beyond_max.with_year(beyond_max.year()), Some(beyond_max)); + assert_eq!(beyond_max.with_month(beyond_max.month()), Some(beyond_max)); + assert_eq!(beyond_max.with_month(3), None); + assert_eq!(beyond_max.with_month0(beyond_max.month0()), Some(beyond_max)); + assert_eq!(beyond_max.with_month0(3), None); + assert_eq!(beyond_max.with_day(beyond_max.day()), Some(beyond_max)); + assert_eq!(beyond_max.with_day(15), None); + assert_eq!(beyond_max.with_day0(beyond_max.day0()), Some(beyond_max)); + assert_eq!(beyond_max.with_day0(15), None); + assert_eq!(beyond_max.with_ordinal(beyond_max.ordinal()), Some(beyond_max)); + assert_eq!(beyond_max.with_ordinal(200), None); + assert_eq!(beyond_max.with_ordinal0(beyond_max.ordinal0()), Some(beyond_max)); + assert_eq!(beyond_max.with_ordinal0(200), None); + assert_eq!(beyond_max.with_hour(beyond_max.hour()), Some(beyond_max)); + assert_eq!( + beyond_max.with_hour(0), + beyond_max.checked_sub_signed(TimeDelta::try_hours(1).unwrap()) + ); + assert_eq!(beyond_max.with_hour(5), None); + assert_eq!(beyond_max.with_minute(beyond_max.minute()), Some(beyond_max)); + assert_eq!(beyond_max.with_second(beyond_max.second()), Some(beyond_max)); + assert_eq!(beyond_max.with_nanosecond(beyond_max.nanosecond()), Some(beyond_max)); +} + +#[test] +fn test_min_max_add_days() { + let offset_min = FixedOffset::west_opt(2 * 60 * 60).unwrap(); + let beyond_min = offset_min.from_utc_datetime(&NaiveDateTime::MIN); + let offset_max = FixedOffset::east_opt(2 * 60 * 60).unwrap(); + let beyond_max = offset_max.from_utc_datetime(&NaiveDateTime::MAX); + let max_time = NaiveTime::from_hms_nano_opt(23, 59, 59, 999_999_999).unwrap(); + + assert_eq!(beyond_min.checked_add_days(Days::new(0)), Some(beyond_min)); + assert_eq!( + beyond_min.checked_add_days(Days::new(1)), + Some(offset_min.from_utc_datetime(&(NaiveDate::MIN + Days(1)).and_time(NaiveTime::MIN))) + ); + assert_eq!(beyond_min.checked_sub_days(Days::new(0)), Some(beyond_min)); + assert_eq!(beyond_min.checked_sub_days(Days::new(1)), None); + + assert_eq!(beyond_max.checked_add_days(Days::new(0)), Some(beyond_max)); + assert_eq!(beyond_max.checked_add_days(Days::new(1)), None); + assert_eq!(beyond_max.checked_sub_days(Days::new(0)), Some(beyond_max)); + assert_eq!( + beyond_max.checked_sub_days(Days::new(1)), + Some(offset_max.from_utc_datetime(&(NaiveDate::MAX - Days(1)).and_time(max_time))) + ); +} + +#[test] +fn test_min_max_add_months() { + let offset_min = FixedOffset::west_opt(2 * 60 * 60).unwrap(); + let beyond_min = offset_min.from_utc_datetime(&NaiveDateTime::MIN); + let offset_max = FixedOffset::east_opt(2 * 60 * 60).unwrap(); + let beyond_max = offset_max.from_utc_datetime(&NaiveDateTime::MAX); + let max_time = NaiveTime::from_hms_nano_opt(23, 59, 59, 999_999_999).unwrap(); + + assert_eq!(beyond_min.checked_add_months(Months::new(0)), Some(beyond_min)); + assert_eq!( + beyond_min.checked_add_months(Months::new(1)), + Some(offset_min.from_utc_datetime(&(NaiveDate::MIN + Months(1)).and_time(NaiveTime::MIN))) + ); + assert_eq!(beyond_min.checked_sub_months(Months::new(0)), Some(beyond_min)); + assert_eq!(beyond_min.checked_sub_months(Months::new(1)), None); + + assert_eq!(beyond_max.checked_add_months(Months::new(0)), Some(beyond_max)); + assert_eq!(beyond_max.checked_add_months(Months::new(1)), None); + assert_eq!(beyond_max.checked_sub_months(Months::new(0)), Some(beyond_max)); + assert_eq!( + beyond_max.checked_sub_months(Months::new(1)), + Some(offset_max.from_utc_datetime(&(NaiveDate::MAX - Months(1)).and_time(max_time))) + ); +} + +#[test] +#[should_panic] +fn test_local_beyond_min_datetime() { + let min = FixedOffset::west_opt(2 * 60 * 60).unwrap().from_utc_datetime(&NaiveDateTime::MIN); + let _ = min.naive_local(); +} + +#[test] +#[should_panic] +fn test_local_beyond_max_datetime() { + let max = FixedOffset::east_opt(2 * 60 * 60).unwrap().from_utc_datetime(&NaiveDateTime::MAX); + let _ = max.naive_local(); +} + +#[test] +#[cfg(feature = "clock")] +fn test_datetime_sub_assign_local() { + let naivedatetime = NaiveDate::from_ymd_opt(2022, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); + + let datetime = Local.from_utc_datetime(&naivedatetime); + let mut datetime_sub = Local.from_utc_datetime(&naivedatetime); + + // ensure we cross a DST transition + for i in 1..=365 { + datetime_sub -= TimeDelta::try_days(1).unwrap(); + assert_eq!(datetime_sub, datetime - TimeDelta::try_days(i).unwrap()) + } +} + +#[test] +fn test_core_duration_ops() { + use core::time::Duration; + + let mut utc_dt = Utc.with_ymd_and_hms(2023, 8, 29, 11, 34, 12).unwrap(); + let same = utc_dt + Duration::ZERO; + assert_eq!(utc_dt, same); + + utc_dt += Duration::new(3600, 0); + assert_eq!(utc_dt, Utc.with_ymd_and_hms(2023, 8, 29, 12, 34, 12).unwrap()); +} + +#[test] +#[should_panic] +fn test_core_duration_max() { + use core::time::Duration; + + let mut utc_dt = Utc.with_ymd_and_hms(2023, 8, 29, 11, 34, 12).unwrap(); + utc_dt += Duration::MAX; +} + +#[test] +#[cfg(feature = "clock")] +fn test_datetime_local_from_preserves_offset() { + let naivedatetime = NaiveDate::from_ymd_opt(2023, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); + + let datetime = Local.from_utc_datetime(&naivedatetime); + let offset = datetime.offset().fix(); + + let datetime_fixed: DateTime = datetime.into(); + assert_eq!(&offset, datetime_fixed.offset()); + assert_eq!(datetime.fixed_offset(), datetime_fixed); +} + +#[test] +fn test_datetime_fixed_offset() { + let naivedatetime = NaiveDate::from_ymd_opt(2023, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); + + let datetime = Utc.from_utc_datetime(&naivedatetime); + let fixed_utc = FixedOffset::east_opt(0).unwrap(); + assert_eq!(datetime.fixed_offset(), fixed_utc.from_local_datetime(&naivedatetime).unwrap()); + + let fixed_offset = FixedOffset::east_opt(3600).unwrap(); + let datetime_fixed = fixed_offset.from_local_datetime(&naivedatetime).unwrap(); + assert_eq!(datetime_fixed.fixed_offset(), datetime_fixed); +} + +#[test] +fn test_datetime_to_utc() { + let dt = + FixedOffset::east_opt(3600).unwrap().with_ymd_and_hms(2020, 2, 22, 23, 24, 25).unwrap(); + let dt_utc: DateTime = dt.to_utc(); + assert_eq!(dt, dt_utc); +} + +#[test] +fn test_add_sub_months() { + let utc_dt = Utc.with_ymd_and_hms(2018, 9, 5, 23, 58, 0).unwrap(); + assert_eq!(utc_dt + Months::new(15), Utc.with_ymd_and_hms(2019, 12, 5, 23, 58, 0).unwrap()); + + let utc_dt = Utc.with_ymd_and_hms(2020, 1, 31, 23, 58, 0).unwrap(); + assert_eq!(utc_dt + Months::new(1), Utc.with_ymd_and_hms(2020, 2, 29, 23, 58, 0).unwrap()); + assert_eq!(utc_dt + Months::new(2), Utc.with_ymd_and_hms(2020, 3, 31, 23, 58, 0).unwrap()); + + let utc_dt = Utc.with_ymd_and_hms(2018, 9, 5, 23, 58, 0).unwrap(); + assert_eq!(utc_dt - Months::new(15), Utc.with_ymd_and_hms(2017, 6, 5, 23, 58, 0).unwrap()); + + let utc_dt = Utc.with_ymd_and_hms(2020, 3, 31, 23, 58, 0).unwrap(); + assert_eq!(utc_dt - Months::new(1), Utc.with_ymd_and_hms(2020, 2, 29, 23, 58, 0).unwrap()); + assert_eq!(utc_dt - Months::new(2), Utc.with_ymd_and_hms(2020, 1, 31, 23, 58, 0).unwrap()); +} + +#[test] +fn test_auto_conversion() { + let utc_dt = Utc.with_ymd_and_hms(2018, 9, 5, 23, 58, 0).unwrap(); + let cdt_dt = FixedOffset::west_opt(5 * 60 * 60) + .unwrap() + .with_ymd_and_hms(2018, 9, 5, 18, 58, 0) + .unwrap(); + let utc_dt2: DateTime = cdt_dt.into(); + assert_eq!(utc_dt, utc_dt2); +} + +#[test] +#[cfg(feature = "clock")] +#[allow(deprecated)] +fn test_test_deprecated_from_offset() { + let now = Local::now(); + let naive = now.naive_local(); + let utc = now.naive_utc(); + let offset: FixedOffset = *now.offset(); + + assert_eq!(DateTime::::from_local(naive, offset), now); + assert_eq!(DateTime::::from_utc(utc, offset), now); +} + +#[test] +#[cfg(all(feature = "unstable-locales", feature = "alloc"))] +fn locale_decimal_point() { + use crate::Locale::{ar_SY, nl_NL}; + let dt = + Utc.with_ymd_and_hms(2018, 9, 5, 18, 58, 0).unwrap().with_nanosecond(123456780).unwrap(); + + assert_eq!(dt.format_localized("%T%.f", nl_NL).to_string(), "18:58:00,123456780"); + assert_eq!(dt.format_localized("%T%.3f", nl_NL).to_string(), "18:58:00,123"); + assert_eq!(dt.format_localized("%T%.6f", nl_NL).to_string(), "18:58:00,123456"); + assert_eq!(dt.format_localized("%T%.9f", nl_NL).to_string(), "18:58:00,123456780"); + + assert_eq!(dt.format_localized("%T%.f", ar_SY).to_string(), "18:58:00.123456780"); + assert_eq!(dt.format_localized("%T%.3f", ar_SY).to_string(), "18:58:00.123"); + assert_eq!(dt.format_localized("%T%.6f", ar_SY).to_string(), "18:58:00.123456"); + assert_eq!(dt.format_localized("%T%.9f", ar_SY).to_string(), "18:58:00.123456780"); +} + +/// This is an extended test for . +#[test] +fn nano_roundrip() { + const BILLION: i64 = 1_000_000_000; + + for nanos in [ + i64::MIN, + i64::MIN + 1, + i64::MIN + 2, + i64::MIN + BILLION - 1, + i64::MIN + BILLION, + i64::MIN + BILLION + 1, + -BILLION - 1, + -BILLION, + -BILLION + 1, + 0, + BILLION - 1, + BILLION, + BILLION + 1, + i64::MAX - BILLION - 1, + i64::MAX - BILLION, + i64::MAX - BILLION + 1, + i64::MAX - 2, + i64::MAX - 1, + i64::MAX, + ] { + println!("nanos: {}", nanos); + let dt = Utc.timestamp_nanos(nanos); + let nanos2 = dt.timestamp_nanos_opt().expect("value roundtrips"); + assert_eq!(nanos, nanos2); + } +} diff --git a/third_party/rust/chrono/src/div.rs b/third_party/rust/chrono/src/div.rs deleted file mode 100644 index 64b8e4bce45..00000000000 --- a/third_party/rust/chrono/src/div.rs +++ /dev/null @@ -1,41 +0,0 @@ -// This is a part of Chrono. -// Portions Copyright 2013-2014 The Rust Project Developers. -// See README.md and LICENSE.txt for details. - -//! Integer division utilities. (Shamelessly copied from [num](https://github.com/rust-lang/num/)) - -// Algorithm from [Daan Leijen. _Division and Modulus for Computer Scientists_, -// December 2001](http://research.microsoft.com/pubs/151917/divmodnote-letter.pdf) - -pub use num_integer::{div_floor, div_mod_floor, div_rem, mod_floor}; - -#[cfg(test)] -mod tests { - use super::{div_mod_floor, mod_floor}; - - #[test] - fn test_mod_floor() { - assert_eq!(mod_floor(8, 3), 2); - assert_eq!(mod_floor(8, -3), -1); - assert_eq!(mod_floor(-8, 3), 1); - assert_eq!(mod_floor(-8, -3), -2); - - assert_eq!(mod_floor(1, 2), 1); - assert_eq!(mod_floor(1, -2), -1); - assert_eq!(mod_floor(-1, 2), 1); - assert_eq!(mod_floor(-1, -2), -1); - } - - #[test] - fn test_div_mod_floor() { - assert_eq!(div_mod_floor(8, 3), (2, 2)); - assert_eq!(div_mod_floor(8, -3), (-3, -1)); - assert_eq!(div_mod_floor(-8, 3), (-3, 1)); - assert_eq!(div_mod_floor(-8, -3), (2, -2)); - - assert_eq!(div_mod_floor(1, 2), (0, 1)); - assert_eq!(div_mod_floor(1, -2), (-1, -1)); - assert_eq!(div_mod_floor(-1, 2), (-1, 1)); - assert_eq!(div_mod_floor(-1, -2), (0, -1)); - } -} diff --git a/third_party/rust/chrono/src/format/formatting.rs b/third_party/rust/chrono/src/format/formatting.rs new file mode 100644 index 00000000000..e27b764062e --- /dev/null +++ b/third_party/rust/chrono/src/format/formatting.rs @@ -0,0 +1,945 @@ +// This is a part of Chrono. +// See README.md and LICENSE.txt for details. + +//! Date and time formatting routines. + +#[cfg(all(feature = "alloc", not(feature = "std"), not(test)))] +use alloc::string::{String, ToString}; +#[cfg(feature = "alloc")] +use core::borrow::Borrow; +#[cfg(feature = "alloc")] +use core::fmt::Display; +use core::fmt::{self, Write}; + +#[cfg(feature = "alloc")] +use crate::offset::Offset; +#[cfg(any(feature = "alloc", feature = "serde"))] +use crate::{Datelike, FixedOffset, NaiveDateTime, Timelike}; +#[cfg(feature = "alloc")] +use crate::{NaiveDate, NaiveTime, Weekday}; + +#[cfg(feature = "alloc")] +use super::locales; +#[cfg(any(feature = "alloc", feature = "serde"))] +use super::{Colons, OffsetFormat, OffsetPrecision, Pad}; +#[cfg(feature = "alloc")] +use super::{Fixed, InternalFixed, InternalInternal, Item, Numeric}; +#[cfg(feature = "alloc")] +use locales::*; + +/// A *temporary* object which can be used as an argument to `format!` or others. +/// This is normally constructed via `format` methods of each date and time type. +#[cfg(feature = "alloc")] +#[derive(Debug)] +pub struct DelayedFormat { + /// The date view, if any. + date: Option, + /// The time view, if any. + time: Option, + /// The name and local-to-UTC difference for the offset (timezone), if any. + off: Option<(String, FixedOffset)>, + /// An iterator returning formatting items. + items: I, + /// Locale used for text. + /// ZST if the `unstable-locales` feature is not enabled. + locale: Locale, +} + +#[cfg(feature = "alloc")] +impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { + /// Makes a new `DelayedFormat` value out of local date and time. + #[must_use] + pub fn new(date: Option, time: Option, items: I) -> DelayedFormat { + DelayedFormat { date, time, off: None, items, locale: default_locale() } + } + + /// Makes a new `DelayedFormat` value out of local date and time and UTC offset. + #[must_use] + pub fn new_with_offset( + date: Option, + time: Option, + offset: &Off, + items: I, + ) -> DelayedFormat + where + Off: Offset + Display, + { + let name_and_diff = (offset.to_string(), offset.fix()); + DelayedFormat { date, time, off: Some(name_and_diff), items, locale: default_locale() } + } + + /// Makes a new `DelayedFormat` value out of local date and time and locale. + #[cfg(feature = "unstable-locales")] + #[must_use] + pub fn new_with_locale( + date: Option, + time: Option, + items: I, + locale: Locale, + ) -> DelayedFormat { + DelayedFormat { date, time, off: None, items, locale } + } + + /// Makes a new `DelayedFormat` value out of local date and time, UTC offset and locale. + #[cfg(feature = "unstable-locales")] + #[must_use] + pub fn new_with_offset_and_locale( + date: Option, + time: Option, + offset: &Off, + items: I, + locale: Locale, + ) -> DelayedFormat + where + Off: Offset + Display, + { + let name_and_diff = (offset.to_string(), offset.fix()); + DelayedFormat { date, time, off: Some(name_and_diff), items, locale } + } + + /// Formats `DelayedFormat` into a `core::fmt::Write` instance. + /// # Errors + /// This function returns a `core::fmt::Error` if formatting into the `core::fmt::Write` instance fails. + /// + /// # Example + /// ### Writing to a String + /// ``` + /// let dt = chrono::DateTime::from_timestamp(1643723400, 123456789).unwrap(); + /// let df = dt.format("%Y-%m-%d %H:%M:%S%.9f"); + /// let mut buffer = String::new(); + /// let _ = df.write_to(&mut buffer); + /// ``` + pub fn write_to(&self, w: &mut impl Write) -> fmt::Result { + for item in self.items.clone() { + match *item.borrow() { + Item::Literal(s) | Item::Space(s) => w.write_str(s), + #[cfg(feature = "alloc")] + Item::OwnedLiteral(ref s) | Item::OwnedSpace(ref s) => w.write_str(s), + Item::Numeric(ref spec, pad) => self.format_numeric(w, spec, pad), + Item::Fixed(ref spec) => self.format_fixed(w, spec), + Item::Error => Err(fmt::Error), + }?; + } + Ok(()) + } + + #[cfg(feature = "alloc")] + fn format_numeric(&self, w: &mut impl Write, spec: &Numeric, pad: Pad) -> fmt::Result { + use self::Numeric::*; + + fn write_one(w: &mut impl Write, v: u8) -> fmt::Result { + w.write_char((b'0' + v) as char) + } + + fn write_two(w: &mut impl Write, v: u8, pad: Pad) -> fmt::Result { + let ones = b'0' + v % 10; + match (v / 10, pad) { + (0, Pad::None) => {} + (0, Pad::Space) => w.write_char(' ')?, + (tens, _) => w.write_char((b'0' + tens) as char)?, + } + w.write_char(ones as char) + } + + #[inline] + fn write_year(w: &mut impl Write, year: i32, pad: Pad) -> fmt::Result { + if (1000..=9999).contains(&year) { + // fast path + write_hundreds(w, (year / 100) as u8)?; + write_hundreds(w, (year % 100) as u8) + } else { + write_n(w, 4, year as i64, pad, !(0..10_000).contains(&year)) + } + } + + fn write_n( + w: &mut impl Write, + n: usize, + v: i64, + pad: Pad, + always_sign: bool, + ) -> fmt::Result { + if always_sign { + match pad { + Pad::None => write!(w, "{:+}", v), + Pad::Zero => write!(w, "{:+01$}", v, n + 1), + Pad::Space => write!(w, "{:+1$}", v, n + 1), + } + } else { + match pad { + Pad::None => write!(w, "{}", v), + Pad::Zero => write!(w, "{:01$}", v, n), + Pad::Space => write!(w, "{:1$}", v, n), + } + } + } + + match (spec, self.date, self.time) { + (Year, Some(d), _) => write_year(w, d.year(), pad), + (YearDiv100, Some(d), _) => write_two(w, d.year().div_euclid(100) as u8, pad), + (YearMod100, Some(d), _) => write_two(w, d.year().rem_euclid(100) as u8, pad), + (IsoYear, Some(d), _) => write_year(w, d.iso_week().year(), pad), + (IsoYearDiv100, Some(d), _) => { + write_two(w, d.iso_week().year().div_euclid(100) as u8, pad) + } + (IsoYearMod100, Some(d), _) => { + write_two(w, d.iso_week().year().rem_euclid(100) as u8, pad) + } + (Quarter, Some(d), _) => write_one(w, d.quarter() as u8), + (Month, Some(d), _) => write_two(w, d.month() as u8, pad), + (Day, Some(d), _) => write_two(w, d.day() as u8, pad), + (WeekFromSun, Some(d), _) => write_two(w, d.weeks_from(Weekday::Sun) as u8, pad), + (WeekFromMon, Some(d), _) => write_two(w, d.weeks_from(Weekday::Mon) as u8, pad), + (IsoWeek, Some(d), _) => write_two(w, d.iso_week().week() as u8, pad), + (NumDaysFromSun, Some(d), _) => write_one(w, d.weekday().num_days_from_sunday() as u8), + (WeekdayFromMon, Some(d), _) => write_one(w, d.weekday().number_from_monday() as u8), + (Ordinal, Some(d), _) => write_n(w, 3, d.ordinal() as i64, pad, false), + (Hour, _, Some(t)) => write_two(w, t.hour() as u8, pad), + (Hour12, _, Some(t)) => write_two(w, t.hour12().1 as u8, pad), + (Minute, _, Some(t)) => write_two(w, t.minute() as u8, pad), + (Second, _, Some(t)) => { + write_two(w, (t.second() + t.nanosecond() / 1_000_000_000) as u8, pad) + } + (Nanosecond, _, Some(t)) => { + write_n(w, 9, (t.nanosecond() % 1_000_000_000) as i64, pad, false) + } + (Timestamp, Some(d), Some(t)) => { + let offset = self.off.as_ref().map(|(_, o)| i64::from(o.local_minus_utc())); + let timestamp = d.and_time(t).and_utc().timestamp() - offset.unwrap_or(0); + write_n(w, 9, timestamp, pad, false) + } + (Internal(_), _, _) => Ok(()), // for future expansion + _ => Err(fmt::Error), // insufficient arguments for given format + } + } + + #[cfg(feature = "alloc")] + fn format_fixed(&self, w: &mut impl Write, spec: &Fixed) -> fmt::Result { + use Fixed::*; + use InternalInternal::*; + + match (spec, self.date, self.time, self.off.as_ref()) { + (ShortMonthName, Some(d), _, _) => { + w.write_str(short_months(self.locale)[d.month0() as usize]) + } + (LongMonthName, Some(d), _, _) => { + w.write_str(long_months(self.locale)[d.month0() as usize]) + } + (ShortWeekdayName, Some(d), _, _) => w.write_str( + short_weekdays(self.locale)[d.weekday().num_days_from_sunday() as usize], + ), + (LongWeekdayName, Some(d), _, _) => { + w.write_str(long_weekdays(self.locale)[d.weekday().num_days_from_sunday() as usize]) + } + (LowerAmPm, _, Some(t), _) => { + let ampm = if t.hour12().0 { am_pm(self.locale)[1] } else { am_pm(self.locale)[0] }; + for c in ampm.chars().flat_map(|c| c.to_lowercase()) { + w.write_char(c)? + } + Ok(()) + } + (UpperAmPm, _, Some(t), _) => { + let ampm = if t.hour12().0 { am_pm(self.locale)[1] } else { am_pm(self.locale)[0] }; + w.write_str(ampm) + } + (Nanosecond, _, Some(t), _) => { + let nano = t.nanosecond() % 1_000_000_000; + if nano == 0 { + Ok(()) + } else { + w.write_str(decimal_point(self.locale))?; + if nano % 1_000_000 == 0 { + write!(w, "{:03}", nano / 1_000_000) + } else if nano % 1_000 == 0 { + write!(w, "{:06}", nano / 1_000) + } else { + write!(w, "{:09}", nano) + } + } + } + (Nanosecond3, _, Some(t), _) => { + w.write_str(decimal_point(self.locale))?; + write!(w, "{:03}", t.nanosecond() / 1_000_000 % 1000) + } + (Nanosecond6, _, Some(t), _) => { + w.write_str(decimal_point(self.locale))?; + write!(w, "{:06}", t.nanosecond() / 1_000 % 1_000_000) + } + (Nanosecond9, _, Some(t), _) => { + w.write_str(decimal_point(self.locale))?; + write!(w, "{:09}", t.nanosecond() % 1_000_000_000) + } + (Internal(InternalFixed { val: Nanosecond3NoDot }), _, Some(t), _) => { + write!(w, "{:03}", t.nanosecond() / 1_000_000 % 1_000) + } + (Internal(InternalFixed { val: Nanosecond6NoDot }), _, Some(t), _) => { + write!(w, "{:06}", t.nanosecond() / 1_000 % 1_000_000) + } + (Internal(InternalFixed { val: Nanosecond9NoDot }), _, Some(t), _) => { + write!(w, "{:09}", t.nanosecond() % 1_000_000_000) + } + (TimezoneName, _, _, Some((tz_name, _))) => write!(w, "{}", tz_name), + (TimezoneOffset | TimezoneOffsetZ, _, _, Some((_, off))) => { + let offset_format = OffsetFormat { + precision: OffsetPrecision::Minutes, + colons: Colons::Maybe, + allow_zulu: *spec == TimezoneOffsetZ, + padding: Pad::Zero, + }; + offset_format.format(w, *off) + } + (TimezoneOffsetColon | TimezoneOffsetColonZ, _, _, Some((_, off))) => { + let offset_format = OffsetFormat { + precision: OffsetPrecision::Minutes, + colons: Colons::Colon, + allow_zulu: *spec == TimezoneOffsetColonZ, + padding: Pad::Zero, + }; + offset_format.format(w, *off) + } + (TimezoneOffsetDoubleColon, _, _, Some((_, off))) => { + let offset_format = OffsetFormat { + precision: OffsetPrecision::Seconds, + colons: Colons::Colon, + allow_zulu: false, + padding: Pad::Zero, + }; + offset_format.format(w, *off) + } + (TimezoneOffsetTripleColon, _, _, Some((_, off))) => { + let offset_format = OffsetFormat { + precision: OffsetPrecision::Hours, + colons: Colons::None, + allow_zulu: false, + padding: Pad::Zero, + }; + offset_format.format(w, *off) + } + (RFC2822, Some(d), Some(t), Some((_, off))) => { + write_rfc2822(w, crate::NaiveDateTime::new(d, t), *off) + } + (RFC3339, Some(d), Some(t), Some((_, off))) => write_rfc3339( + w, + crate::NaiveDateTime::new(d, t), + *off, + SecondsFormat::AutoSi, + false, + ), + _ => Err(fmt::Error), // insufficient arguments for given format + } + } +} + +#[cfg(feature = "alloc")] +impl<'a, I: Iterator + Clone, B: Borrow>> Display for DelayedFormat { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut result = String::new(); + self.write_to(&mut result)?; + f.pad(&result) + } +} + +/// Tries to format given arguments with given formatting items. +/// Internally used by `DelayedFormat`. +#[cfg(feature = "alloc")] +#[deprecated(since = "0.4.32", note = "Use DelayedFormat::fmt or DelayedFormat::write_to instead")] +pub fn format<'a, I, B>( + w: &mut fmt::Formatter, + date: Option<&NaiveDate>, + time: Option<&NaiveTime>, + off: Option<&(String, FixedOffset)>, + items: I, +) -> fmt::Result +where + I: Iterator + Clone, + B: Borrow>, +{ + DelayedFormat { + date: date.copied(), + time: time.copied(), + off: off.cloned(), + items, + locale: default_locale(), + } + .fmt(w) +} + +/// Formats single formatting item +#[cfg(feature = "alloc")] +#[deprecated(since = "0.4.32", note = "Use DelayedFormat::fmt or DelayedFormat::write_to instead")] +pub fn format_item( + w: &mut fmt::Formatter, + date: Option<&NaiveDate>, + time: Option<&NaiveTime>, + off: Option<&(String, FixedOffset)>, + item: &Item<'_>, +) -> fmt::Result { + DelayedFormat { + date: date.copied(), + time: time.copied(), + off: off.cloned(), + items: [item].into_iter(), + locale: default_locale(), + } + .fmt(w) +} + +#[cfg(any(feature = "alloc", feature = "serde"))] +impl OffsetFormat { + /// Writes an offset from UTC with the format defined by `self`. + fn format(&self, w: &mut impl Write, off: FixedOffset) -> fmt::Result { + let off = off.local_minus_utc(); + if self.allow_zulu && off == 0 { + w.write_char('Z')?; + return Ok(()); + } + let (sign, off) = if off < 0 { ('-', -off) } else { ('+', off) }; + + let hours; + let mut mins = 0; + let mut secs = 0; + let precision = match self.precision { + OffsetPrecision::Hours => { + // Minutes and seconds are simply truncated + hours = (off / 3600) as u8; + OffsetPrecision::Hours + } + OffsetPrecision::Minutes | OffsetPrecision::OptionalMinutes => { + // Round seconds to the nearest minute. + let minutes = (off + 30) / 60; + mins = (minutes % 60) as u8; + hours = (minutes / 60) as u8; + if self.precision == OffsetPrecision::OptionalMinutes && mins == 0 { + OffsetPrecision::Hours + } else { + OffsetPrecision::Minutes + } + } + OffsetPrecision::Seconds + | OffsetPrecision::OptionalSeconds + | OffsetPrecision::OptionalMinutesAndSeconds => { + let minutes = off / 60; + secs = (off % 60) as u8; + mins = (minutes % 60) as u8; + hours = (minutes / 60) as u8; + if self.precision != OffsetPrecision::Seconds && secs == 0 { + if self.precision == OffsetPrecision::OptionalMinutesAndSeconds && mins == 0 { + OffsetPrecision::Hours + } else { + OffsetPrecision::Minutes + } + } else { + OffsetPrecision::Seconds + } + } + }; + let colons = self.colons == Colons::Colon; + + if hours < 10 { + if self.padding == Pad::Space { + w.write_char(' ')?; + } + w.write_char(sign)?; + if self.padding == Pad::Zero { + w.write_char('0')?; + } + w.write_char((b'0' + hours) as char)?; + } else { + w.write_char(sign)?; + write_hundreds(w, hours)?; + } + if let OffsetPrecision::Minutes | OffsetPrecision::Seconds = precision { + if colons { + w.write_char(':')?; + } + write_hundreds(w, mins)?; + } + if let OffsetPrecision::Seconds = precision { + if colons { + w.write_char(':')?; + } + write_hundreds(w, secs)?; + } + Ok(()) + } +} + +/// Specific formatting options for seconds. This may be extended in the +/// future, so exhaustive matching in external code is not recommended. +/// +/// See the `TimeZone::to_rfc3339_opts` function for usage. +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] +#[allow(clippy::manual_non_exhaustive)] +pub enum SecondsFormat { + /// Format whole seconds only, with no decimal point nor subseconds. + Secs, + + /// Use fixed 3 subsecond digits. This corresponds to [Fixed::Nanosecond3]. + Millis, + + /// Use fixed 6 subsecond digits. This corresponds to [Fixed::Nanosecond6]. + Micros, + + /// Use fixed 9 subsecond digits. This corresponds to [Fixed::Nanosecond9]. + Nanos, + + /// Automatically select one of `Secs`, `Millis`, `Micros`, or `Nanos` to display all available + /// non-zero sub-second digits. This corresponds to [Fixed::Nanosecond]. + AutoSi, + + // Do not match against this. + #[doc(hidden)] + __NonExhaustive, +} + +/// Writes the date, time and offset to the string. same as `%Y-%m-%dT%H:%M:%S%.f%:z` +#[inline] +#[cfg(any(feature = "alloc", feature = "serde"))] +pub(crate) fn write_rfc3339( + w: &mut impl Write, + dt: NaiveDateTime, + off: FixedOffset, + secform: SecondsFormat, + use_z: bool, +) -> fmt::Result { + let year = dt.date().year(); + if (0..=9999).contains(&year) { + write_hundreds(w, (year / 100) as u8)?; + write_hundreds(w, (year % 100) as u8)?; + } else { + // ISO 8601 requires the explicit sign for out-of-range years + write!(w, "{:+05}", year)?; + } + w.write_char('-')?; + write_hundreds(w, dt.date().month() as u8)?; + w.write_char('-')?; + write_hundreds(w, dt.date().day() as u8)?; + + w.write_char('T')?; + + let (hour, min, mut sec) = dt.time().hms(); + let mut nano = dt.nanosecond(); + if nano >= 1_000_000_000 { + sec += 1; + nano -= 1_000_000_000; + } + write_hundreds(w, hour as u8)?; + w.write_char(':')?; + write_hundreds(w, min as u8)?; + w.write_char(':')?; + let sec = sec; + write_hundreds(w, sec as u8)?; + + match secform { + SecondsFormat::Secs => {} + SecondsFormat::Millis => write!(w, ".{:03}", nano / 1_000_000)?, + SecondsFormat::Micros => write!(w, ".{:06}", nano / 1000)?, + SecondsFormat::Nanos => write!(w, ".{:09}", nano)?, + SecondsFormat::AutoSi => { + if nano == 0 { + } else if nano % 1_000_000 == 0 { + write!(w, ".{:03}", nano / 1_000_000)? + } else if nano % 1_000 == 0 { + write!(w, ".{:06}", nano / 1_000)? + } else { + write!(w, ".{:09}", nano)? + } + } + SecondsFormat::__NonExhaustive => unreachable!(), + }; + + OffsetFormat { + precision: OffsetPrecision::Minutes, + colons: Colons::Colon, + allow_zulu: use_z, + padding: Pad::Zero, + } + .format(w, off) +} + +#[cfg(feature = "alloc")] +/// write datetimes like `Tue, 1 Jul 2003 10:52:37 +0200`, same as `%a, %d %b %Y %H:%M:%S %z` +pub(crate) fn write_rfc2822( + w: &mut impl Write, + dt: NaiveDateTime, + off: FixedOffset, +) -> fmt::Result { + let year = dt.year(); + // RFC2822 is only defined on years 0 through 9999 + if !(0..=9999).contains(&year) { + return Err(fmt::Error); + } + + let english = default_locale(); + + w.write_str(short_weekdays(english)[dt.weekday().num_days_from_sunday() as usize])?; + w.write_str(", ")?; + let day = dt.day(); + if day < 10 { + w.write_char((b'0' + day as u8) as char)?; + } else { + write_hundreds(w, day as u8)?; + } + w.write_char(' ')?; + w.write_str(short_months(english)[dt.month0() as usize])?; + w.write_char(' ')?; + write_hundreds(w, (year / 100) as u8)?; + write_hundreds(w, (year % 100) as u8)?; + w.write_char(' ')?; + + let (hour, min, sec) = dt.time().hms(); + write_hundreds(w, hour as u8)?; + w.write_char(':')?; + write_hundreds(w, min as u8)?; + w.write_char(':')?; + let sec = sec + dt.nanosecond() / 1_000_000_000; + write_hundreds(w, sec as u8)?; + w.write_char(' ')?; + OffsetFormat { + precision: OffsetPrecision::Minutes, + colons: Colons::None, + allow_zulu: false, + padding: Pad::Zero, + } + .format(w, off) +} + +/// Equivalent to `{:02}` formatting for n < 100. +pub(crate) fn write_hundreds(w: &mut impl Write, n: u8) -> fmt::Result { + if n >= 100 { + return Err(fmt::Error); + } + + let tens = b'0' + n / 10; + let ones = b'0' + n % 10; + w.write_char(tens as char)?; + w.write_char(ones as char) +} + +#[cfg(test)] +#[cfg(feature = "alloc")] +mod tests { + use super::{Colons, OffsetFormat, OffsetPrecision, Pad}; + use crate::FixedOffset; + #[cfg(feature = "alloc")] + use crate::{NaiveDate, NaiveTime, TimeZone, Timelike, Utc}; + + #[cfg(feature = "alloc")] + #[test] + fn test_delayed_write_to() { + let dt = crate::DateTime::from_timestamp(1643723400, 123456789).unwrap(); + let df = dt.format("%Y-%m-%d %H:%M:%S%.9f"); + + let mut dt_str = String::new(); + + df.write_to(&mut dt_str).unwrap(); + assert_eq!(dt_str, "2022-02-01 13:50:00.123456789"); + } + + #[cfg(all(feature = "std", feature = "unstable-locales", feature = "alloc"))] + #[test] + fn test_with_locale_delayed_write_to() { + use crate::DateTime; + use crate::format::locales::Locale; + + let dt = DateTime::from_timestamp(1643723400, 123456789).unwrap(); + let df = dt.format_localized("%A, %B %d, %Y", Locale::ja_JP); + + let mut dt_str = String::new(); + + df.write_to(&mut dt_str).unwrap(); + + assert_eq!(dt_str, "火曜日, 2月 01, 2022"); + } + + #[test] + #[cfg(feature = "alloc")] + fn test_date_format() { + let d = NaiveDate::from_ymd_opt(2012, 3, 4).unwrap(); + assert_eq!(d.format("%Y,%C,%y,%G,%g").to_string(), "2012,20,12,2012,12"); + assert_eq!(d.format("%m,%b,%h,%B").to_string(), "03,Mar,Mar,March"); + assert_eq!(d.format("%q").to_string(), "1"); + assert_eq!(d.format("%d,%e").to_string(), "04, 4"); + assert_eq!(d.format("%U,%W,%V").to_string(), "10,09,09"); + assert_eq!(d.format("%a,%A,%w,%u").to_string(), "Sun,Sunday,0,7"); + assert_eq!(d.format("%j").to_string(), "064"); // since 2012 is a leap year + assert_eq!(d.format("%D,%x").to_string(), "03/04/12,03/04/12"); + assert_eq!(d.format("%F").to_string(), "2012-03-04"); + assert_eq!(d.format("%v").to_string(), " 4-Mar-2012"); + assert_eq!(d.format("%t%n%%%n%t").to_string(), "\t\n%\n\t"); + + // non-four-digit years + assert_eq!( + NaiveDate::from_ymd_opt(12345, 1, 1).unwrap().format("%Y").to_string(), + "+12345" + ); + assert_eq!(NaiveDate::from_ymd_opt(1234, 1, 1).unwrap().format("%Y").to_string(), "1234"); + assert_eq!(NaiveDate::from_ymd_opt(123, 1, 1).unwrap().format("%Y").to_string(), "0123"); + assert_eq!(NaiveDate::from_ymd_opt(12, 1, 1).unwrap().format("%Y").to_string(), "0012"); + assert_eq!(NaiveDate::from_ymd_opt(1, 1, 1).unwrap().format("%Y").to_string(), "0001"); + assert_eq!(NaiveDate::from_ymd_opt(0, 1, 1).unwrap().format("%Y").to_string(), "0000"); + assert_eq!(NaiveDate::from_ymd_opt(-1, 1, 1).unwrap().format("%Y").to_string(), "-0001"); + assert_eq!(NaiveDate::from_ymd_opt(-12, 1, 1).unwrap().format("%Y").to_string(), "-0012"); + assert_eq!(NaiveDate::from_ymd_opt(-123, 1, 1).unwrap().format("%Y").to_string(), "-0123"); + assert_eq!(NaiveDate::from_ymd_opt(-1234, 1, 1).unwrap().format("%Y").to_string(), "-1234"); + assert_eq!( + NaiveDate::from_ymd_opt(-12345, 1, 1).unwrap().format("%Y").to_string(), + "-12345" + ); + + // corner cases + assert_eq!( + NaiveDate::from_ymd_opt(2007, 12, 31).unwrap().format("%G,%g,%U,%W,%V").to_string(), + "2008,08,52,53,01" + ); + assert_eq!( + NaiveDate::from_ymd_opt(2010, 1, 3).unwrap().format("%G,%g,%U,%W,%V").to_string(), + "2009,09,01,00,53" + ); + } + + #[test] + #[cfg(feature = "alloc")] + fn test_time_format() { + let t = NaiveTime::from_hms_nano_opt(3, 5, 7, 98765432).unwrap(); + assert_eq!(t.format("%H,%k,%I,%l,%P,%p").to_string(), "03, 3,03, 3,am,AM"); + assert_eq!(t.format("%M").to_string(), "05"); + assert_eq!(t.format("%S,%f,%.f").to_string(), "07,098765432,.098765432"); + assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".098,.098765,.098765432"); + assert_eq!(t.format("%R").to_string(), "03:05"); + assert_eq!(t.format("%T,%X").to_string(), "03:05:07,03:05:07"); + assert_eq!(t.format("%r").to_string(), "03:05:07 AM"); + assert_eq!(t.format("%t%n%%%n%t").to_string(), "\t\n%\n\t"); + + let t = NaiveTime::from_hms_micro_opt(3, 5, 7, 432100).unwrap(); + assert_eq!(t.format("%S,%f,%.f").to_string(), "07,432100000,.432100"); + assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".432,.432100,.432100000"); + + let t = NaiveTime::from_hms_milli_opt(3, 5, 7, 210).unwrap(); + assert_eq!(t.format("%S,%f,%.f").to_string(), "07,210000000,.210"); + assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".210,.210000,.210000000"); + + let t = NaiveTime::from_hms_opt(3, 5, 7).unwrap(); + assert_eq!(t.format("%S,%f,%.f").to_string(), "07,000000000,"); + assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".000,.000000,.000000000"); + + // corner cases + assert_eq!( + NaiveTime::from_hms_opt(13, 57, 9).unwrap().format("%r").to_string(), + "01:57:09 PM" + ); + assert_eq!( + NaiveTime::from_hms_milli_opt(23, 59, 59, 1_000).unwrap().format("%X").to_string(), + "23:59:60" + ); + } + + #[test] + #[cfg(feature = "alloc")] + fn test_datetime_format() { + let dt = + NaiveDate::from_ymd_opt(2010, 9, 8).unwrap().and_hms_milli_opt(7, 6, 54, 321).unwrap(); + assert_eq!(dt.format("%c").to_string(), "Wed Sep 8 07:06:54 2010"); + assert_eq!(dt.format("%s").to_string(), "1283929614"); + assert_eq!(dt.format("%t%n%%%n%t").to_string(), "\t\n%\n\t"); + + // a horror of leap second: coming near to you. + let dt = NaiveDate::from_ymd_opt(2012, 6, 30) + .unwrap() + .and_hms_milli_opt(23, 59, 59, 1_000) + .unwrap(); + assert_eq!(dt.format("%c").to_string(), "Sat Jun 30 23:59:60 2012"); + assert_eq!(dt.format("%s").to_string(), "1341100799"); // not 1341100800, it's intentional. + } + + #[test] + #[cfg(feature = "alloc")] + fn test_datetime_format_alignment() { + let datetime = Utc + .with_ymd_and_hms(2007, 1, 2, 12, 34, 56) + .unwrap() + .with_nanosecond(123456789) + .unwrap(); + + // Item::Literal, odd number of padding bytes. + let percent = datetime.format("%%"); + assert_eq!(" %", format!("{:>4}", percent)); + assert_eq!("% ", format!("{:<4}", percent)); + assert_eq!(" % ", format!("{:^4}", percent)); + + // Item::Numeric, custom non-ASCII padding character + let year = datetime.format("%Y"); + assert_eq!("——2007", format!("{:—>6}", year)); + assert_eq!("2007——", format!("{:—<6}", year)); + assert_eq!("—2007—", format!("{:—^6}", year)); + + // Item::Fixed + let tz = datetime.format("%Z"); + assert_eq!(" UTC", format!("{:>5}", tz)); + assert_eq!("UTC ", format!("{:<5}", tz)); + assert_eq!(" UTC ", format!("{:^5}", tz)); + + // [Item::Numeric, Item::Space, Item::Literal, Item::Space, Item::Numeric] + let ymd = datetime.format("%Y %B %d"); + assert_eq!(" 2007 January 02", format!("{:>17}", ymd)); + assert_eq!("2007 January 02 ", format!("{:<17}", ymd)); + assert_eq!(" 2007 January 02 ", format!("{:^17}", ymd)); + + // Truncated + let time = datetime.format("%T%.6f"); + assert_eq!("12:34:56.1234", format!("{:.13}", time)); + } + + #[test] + fn test_offset_formatting() { + fn check_all(precision: OffsetPrecision, expected: [[&str; 7]; 12]) { + fn check( + precision: OffsetPrecision, + colons: Colons, + padding: Pad, + allow_zulu: bool, + offsets: [FixedOffset; 7], + expected: [&str; 7], + ) { + let offset_format = OffsetFormat { precision, colons, allow_zulu, padding }; + for (offset, expected) in offsets.iter().zip(expected.iter()) { + let mut output = String::new(); + offset_format.format(&mut output, *offset).unwrap(); + assert_eq!(&output, expected); + } + } + // +03:45, -03:30, +11:00, -11:00:22, +02:34:26, -12:34:30, +00:00 + let offsets = [ + FixedOffset::east_opt(13_500).unwrap(), + FixedOffset::east_opt(-12_600).unwrap(), + FixedOffset::east_opt(39_600).unwrap(), + FixedOffset::east_opt(-39_622).unwrap(), + FixedOffset::east_opt(9266).unwrap(), + FixedOffset::east_opt(-45270).unwrap(), + FixedOffset::east_opt(0).unwrap(), + ]; + check(precision, Colons::Colon, Pad::Zero, false, offsets, expected[0]); + check(precision, Colons::Colon, Pad::Zero, true, offsets, expected[1]); + check(precision, Colons::Colon, Pad::Space, false, offsets, expected[2]); + check(precision, Colons::Colon, Pad::Space, true, offsets, expected[3]); + check(precision, Colons::Colon, Pad::None, false, offsets, expected[4]); + check(precision, Colons::Colon, Pad::None, true, offsets, expected[5]); + check(precision, Colons::None, Pad::Zero, false, offsets, expected[6]); + check(precision, Colons::None, Pad::Zero, true, offsets, expected[7]); + check(precision, Colons::None, Pad::Space, false, offsets, expected[8]); + check(precision, Colons::None, Pad::Space, true, offsets, expected[9]); + check(precision, Colons::None, Pad::None, false, offsets, expected[10]); + check(precision, Colons::None, Pad::None, true, offsets, expected[11]); + // `Colons::Maybe` should format the same as `Colons::None` + check(precision, Colons::Maybe, Pad::Zero, false, offsets, expected[6]); + check(precision, Colons::Maybe, Pad::Zero, true, offsets, expected[7]); + check(precision, Colons::Maybe, Pad::Space, false, offsets, expected[8]); + check(precision, Colons::Maybe, Pad::Space, true, offsets, expected[9]); + check(precision, Colons::Maybe, Pad::None, false, offsets, expected[10]); + check(precision, Colons::Maybe, Pad::None, true, offsets, expected[11]); + } + check_all( + OffsetPrecision::Hours, + [ + ["+03", "-03", "+11", "-11", "+02", "-12", "+00"], + ["+03", "-03", "+11", "-11", "+02", "-12", "Z"], + [" +3", " -3", "+11", "-11", " +2", "-12", " +0"], + [" +3", " -3", "+11", "-11", " +2", "-12", "Z"], + ["+3", "-3", "+11", "-11", "+2", "-12", "+0"], + ["+3", "-3", "+11", "-11", "+2", "-12", "Z"], + ["+03", "-03", "+11", "-11", "+02", "-12", "+00"], + ["+03", "-03", "+11", "-11", "+02", "-12", "Z"], + [" +3", " -3", "+11", "-11", " +2", "-12", " +0"], + [" +3", " -3", "+11", "-11", " +2", "-12", "Z"], + ["+3", "-3", "+11", "-11", "+2", "-12", "+0"], + ["+3", "-3", "+11", "-11", "+2", "-12", "Z"], + ], + ); + check_all( + OffsetPrecision::Minutes, + [ + ["+03:45", "-03:30", "+11:00", "-11:00", "+02:34", "-12:35", "+00:00"], + ["+03:45", "-03:30", "+11:00", "-11:00", "+02:34", "-12:35", "Z"], + [" +3:45", " -3:30", "+11:00", "-11:00", " +2:34", "-12:35", " +0:00"], + [" +3:45", " -3:30", "+11:00", "-11:00", " +2:34", "-12:35", "Z"], + ["+3:45", "-3:30", "+11:00", "-11:00", "+2:34", "-12:35", "+0:00"], + ["+3:45", "-3:30", "+11:00", "-11:00", "+2:34", "-12:35", "Z"], + ["+0345", "-0330", "+1100", "-1100", "+0234", "-1235", "+0000"], + ["+0345", "-0330", "+1100", "-1100", "+0234", "-1235", "Z"], + [" +345", " -330", "+1100", "-1100", " +234", "-1235", " +000"], + [" +345", " -330", "+1100", "-1100", " +234", "-1235", "Z"], + ["+345", "-330", "+1100", "-1100", "+234", "-1235", "+000"], + ["+345", "-330", "+1100", "-1100", "+234", "-1235", "Z"], + ], + ); + #[rustfmt::skip] + check_all( + OffsetPrecision::Seconds, + [ + ["+03:45:00", "-03:30:00", "+11:00:00", "-11:00:22", "+02:34:26", "-12:34:30", "+00:00:00"], + ["+03:45:00", "-03:30:00", "+11:00:00", "-11:00:22", "+02:34:26", "-12:34:30", "Z"], + [" +3:45:00", " -3:30:00", "+11:00:00", "-11:00:22", " +2:34:26", "-12:34:30", " +0:00:00"], + [" +3:45:00", " -3:30:00", "+11:00:00", "-11:00:22", " +2:34:26", "-12:34:30", "Z"], + ["+3:45:00", "-3:30:00", "+11:00:00", "-11:00:22", "+2:34:26", "-12:34:30", "+0:00:00"], + ["+3:45:00", "-3:30:00", "+11:00:00", "-11:00:22", "+2:34:26", "-12:34:30", "Z"], + ["+034500", "-033000", "+110000", "-110022", "+023426", "-123430", "+000000"], + ["+034500", "-033000", "+110000", "-110022", "+023426", "-123430", "Z"], + [" +34500", " -33000", "+110000", "-110022", " +23426", "-123430", " +00000"], + [" +34500", " -33000", "+110000", "-110022", " +23426", "-123430", "Z"], + ["+34500", "-33000", "+110000", "-110022", "+23426", "-123430", "+00000"], + ["+34500", "-33000", "+110000", "-110022", "+23426", "-123430", "Z"], + ], + ); + check_all( + OffsetPrecision::OptionalMinutes, + [ + ["+03:45", "-03:30", "+11", "-11", "+02:34", "-12:35", "+00"], + ["+03:45", "-03:30", "+11", "-11", "+02:34", "-12:35", "Z"], + [" +3:45", " -3:30", "+11", "-11", " +2:34", "-12:35", " +0"], + [" +3:45", " -3:30", "+11", "-11", " +2:34", "-12:35", "Z"], + ["+3:45", "-3:30", "+11", "-11", "+2:34", "-12:35", "+0"], + ["+3:45", "-3:30", "+11", "-11", "+2:34", "-12:35", "Z"], + ["+0345", "-0330", "+11", "-11", "+0234", "-1235", "+00"], + ["+0345", "-0330", "+11", "-11", "+0234", "-1235", "Z"], + [" +345", " -330", "+11", "-11", " +234", "-1235", " +0"], + [" +345", " -330", "+11", "-11", " +234", "-1235", "Z"], + ["+345", "-330", "+11", "-11", "+234", "-1235", "+0"], + ["+345", "-330", "+11", "-11", "+234", "-1235", "Z"], + ], + ); + check_all( + OffsetPrecision::OptionalSeconds, + [ + ["+03:45", "-03:30", "+11:00", "-11:00:22", "+02:34:26", "-12:34:30", "+00:00"], + ["+03:45", "-03:30", "+11:00", "-11:00:22", "+02:34:26", "-12:34:30", "Z"], + [" +3:45", " -3:30", "+11:00", "-11:00:22", " +2:34:26", "-12:34:30", " +0:00"], + [" +3:45", " -3:30", "+11:00", "-11:00:22", " +2:34:26", "-12:34:30", "Z"], + ["+3:45", "-3:30", "+11:00", "-11:00:22", "+2:34:26", "-12:34:30", "+0:00"], + ["+3:45", "-3:30", "+11:00", "-11:00:22", "+2:34:26", "-12:34:30", "Z"], + ["+0345", "-0330", "+1100", "-110022", "+023426", "-123430", "+0000"], + ["+0345", "-0330", "+1100", "-110022", "+023426", "-123430", "Z"], + [" +345", " -330", "+1100", "-110022", " +23426", "-123430", " +000"], + [" +345", " -330", "+1100", "-110022", " +23426", "-123430", "Z"], + ["+345", "-330", "+1100", "-110022", "+23426", "-123430", "+000"], + ["+345", "-330", "+1100", "-110022", "+23426", "-123430", "Z"], + ], + ); + check_all( + OffsetPrecision::OptionalMinutesAndSeconds, + [ + ["+03:45", "-03:30", "+11", "-11:00:22", "+02:34:26", "-12:34:30", "+00"], + ["+03:45", "-03:30", "+11", "-11:00:22", "+02:34:26", "-12:34:30", "Z"], + [" +3:45", " -3:30", "+11", "-11:00:22", " +2:34:26", "-12:34:30", " +0"], + [" +3:45", " -3:30", "+11", "-11:00:22", " +2:34:26", "-12:34:30", "Z"], + ["+3:45", "-3:30", "+11", "-11:00:22", "+2:34:26", "-12:34:30", "+0"], + ["+3:45", "-3:30", "+11", "-11:00:22", "+2:34:26", "-12:34:30", "Z"], + ["+0345", "-0330", "+11", "-110022", "+023426", "-123430", "+00"], + ["+0345", "-0330", "+11", "-110022", "+023426", "-123430", "Z"], + [" +345", " -330", "+11", "-110022", " +23426", "-123430", " +0"], + [" +345", " -330", "+11", "-110022", " +23426", "-123430", "Z"], + ["+345", "-330", "+11", "-110022", "+23426", "-123430", "+0"], + ["+345", "-330", "+11", "-110022", "+23426", "-123430", "Z"], + ], + ); + } +} diff --git a/third_party/rust/chrono/src/format/locales.rs b/third_party/rust/chrono/src/format/locales.rs index f7b4bbde5bd..c615476c0cb 100644 --- a/third_party/rust/chrono/src/format/locales.rs +++ b/third_party/rust/chrono/src/format/locales.rs @@ -1,33 +1,103 @@ -use pure_rust_locales::{locale_match, Locale}; +#[cfg(feature = "unstable-locales")] +mod localized { + use pure_rust_locales::{Locale, locale_match}; -pub(crate) fn short_months(locale: Locale) -> &'static [&'static str] { - locale_match!(locale => LC_TIME::ABMON) + pub(crate) const fn default_locale() -> Locale { + Locale::POSIX + } + + pub(crate) const fn short_months(locale: Locale) -> &'static [&'static str] { + locale_match!(locale => LC_TIME::ABMON) + } + + pub(crate) const fn long_months(locale: Locale) -> &'static [&'static str] { + locale_match!(locale => LC_TIME::MON) + } + + pub(crate) const fn short_weekdays(locale: Locale) -> &'static [&'static str] { + locale_match!(locale => LC_TIME::ABDAY) + } + + pub(crate) const fn long_weekdays(locale: Locale) -> &'static [&'static str] { + locale_match!(locale => LC_TIME::DAY) + } + + pub(crate) const fn am_pm(locale: Locale) -> &'static [&'static str] { + locale_match!(locale => LC_TIME::AM_PM) + } + + pub(crate) const fn decimal_point(locale: Locale) -> &'static str { + locale_match!(locale => LC_NUMERIC::DECIMAL_POINT) + } + + pub(crate) const fn d_fmt(locale: Locale) -> &'static str { + locale_match!(locale => LC_TIME::D_FMT) + } + + pub(crate) const fn d_t_fmt(locale: Locale) -> &'static str { + locale_match!(locale => LC_TIME::D_T_FMT) + } + + pub(crate) const fn t_fmt(locale: Locale) -> &'static str { + locale_match!(locale => LC_TIME::T_FMT) + } + + pub(crate) const fn t_fmt_ampm(locale: Locale) -> &'static str { + locale_match!(locale => LC_TIME::T_FMT_AMPM) + } } -pub(crate) fn long_months(locale: Locale) -> &'static [&'static str] { - locale_match!(locale => LC_TIME::MON) +#[cfg(feature = "unstable-locales")] +pub(crate) use localized::*; +#[cfg(feature = "unstable-locales")] +pub use pure_rust_locales::Locale; + +#[cfg(not(feature = "unstable-locales"))] +mod unlocalized { + #[derive(Copy, Clone, Debug)] + pub(crate) struct Locale; + + pub(crate) const fn default_locale() -> Locale { + Locale + } + + pub(crate) const fn short_months(_locale: Locale) -> &'static [&'static str] { + &["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] + } + + pub(crate) const fn long_months(_locale: Locale) -> &'static [&'static str] { + &[ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", + ] + } + + pub(crate) const fn short_weekdays(_locale: Locale) -> &'static [&'static str] { + &["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] + } + + pub(crate) const fn long_weekdays(_locale: Locale) -> &'static [&'static str] { + &["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"] + } + + pub(crate) const fn am_pm(_locale: Locale) -> &'static [&'static str] { + &["AM", "PM"] + } + + pub(crate) const fn decimal_point(_locale: Locale) -> &'static str { + "." + } } -pub(crate) fn short_weekdays(locale: Locale) -> &'static [&'static str] { - locale_match!(locale => LC_TIME::ABDAY) -} - -pub(crate) fn long_weekdays(locale: Locale) -> &'static [&'static str] { - locale_match!(locale => LC_TIME::DAY) -} - -pub(crate) fn am_pm(locale: Locale) -> &'static [&'static str] { - locale_match!(locale => LC_TIME::AM_PM) -} - -pub(crate) fn d_fmt(locale: Locale) -> &'static str { - locale_match!(locale => LC_TIME::D_FMT) -} - -pub(crate) fn d_t_fmt(locale: Locale) -> &'static str { - locale_match!(locale => LC_TIME::D_T_FMT) -} - -pub(crate) fn t_fmt(locale: Locale) -> &'static str { - locale_match!(locale => LC_TIME::T_FMT) -} +#[cfg(not(feature = "unstable-locales"))] +pub(crate) use unlocalized::*; diff --git a/third_party/rust/chrono/src/format/mod.rs b/third_party/rust/chrono/src/format/mod.rs index a641f196d32..241be7a1d95 100644 --- a/third_party/rust/chrono/src/format/mod.rs +++ b/third_party/rust/chrono/src/format/mod.rs @@ -12,50 +12,70 @@ //! which are just an [`Iterator`](https://doc.rust-lang.org/std/iter/trait.Iterator.html) of //! the [`Item`](./enum.Item.html) type. //! They are generated from more readable **format strings**; -//! currently Chrono supports [one built-in syntax closely resembling -//! C's `strftime` format](./strftime/index.html). +//! currently Chrono supports a built-in syntax closely resembling +//! C's `strftime` format. The available options can be found [here](./strftime/index.html). +//! +//! # Example +//! ``` +//! # #[cfg(feature = "alloc")] { +//! use chrono::{NaiveDateTime, TimeZone, Utc}; +//! +//! let date_time = Utc.with_ymd_and_hms(2020, 11, 10, 0, 1, 32).unwrap(); +//! +//! let formatted = format!("{}", date_time.format("%Y-%m-%d %H:%M:%S")); +//! assert_eq!(formatted, "2020-11-10 00:01:32"); +//! +//! let parsed = NaiveDateTime::parse_from_str(&formatted, "%Y-%m-%d %H:%M:%S")?.and_utc(); +//! assert_eq!(parsed, date_time); +//! # } +//! # Ok::<(), chrono::ParseError>(()) +//! ``` -#![allow(ellipsis_inclusive_range_patterns)] - -#[cfg(feature = "alloc")] +#[cfg(all(feature = "alloc", not(feature = "std"), not(test)))] use alloc::boxed::Box; -#[cfg(feature = "alloc")] -use alloc::string::{String, ToString}; -#[cfg(any(feature = "alloc", feature = "std", test))] -use core::borrow::Borrow; use core::fmt; use core::str::FromStr; -#[cfg(any(feature = "std", test))] +#[cfg(feature = "std")] use std::error::Error; -#[cfg(any(feature = "alloc", feature = "std", test))] -use naive::{NaiveDate, NaiveTime}; -#[cfg(any(feature = "alloc", feature = "std", test))] -use offset::{FixedOffset, Offset}; -#[cfg(any(feature = "alloc", feature = "std", test))] -use {Datelike, Timelike}; -use {Month, ParseMonthError, ParseWeekdayError, Weekday}; +use crate::{Month, ParseMonthError, ParseWeekdayError, Weekday}; -#[cfg(feature = "unstable-locales")] +mod formatting; +mod parsed; + +// due to the size of parsing routines, they are in separate modules. +mod parse; +pub(crate) mod scan; + +pub mod strftime; + +#[allow(unused)] +// TODO: remove '#[allow(unused)]' once we use this module for parsing or something else that does +// not require `alloc`. pub(crate) mod locales; -pub use self::parse::parse; -pub use self::parsed::Parsed; -pub use self::strftime::StrftimeItems; -/// L10n locales. +pub use formatting::SecondsFormat; +pub(crate) use formatting::write_hundreds; +#[cfg(feature = "alloc")] +pub(crate) use formatting::write_rfc2822; +#[cfg(any(feature = "alloc", feature = "serde"))] +pub(crate) use formatting::write_rfc3339; +#[cfg(feature = "alloc")] +#[allow(deprecated)] +pub use formatting::{DelayedFormat, format, format_item}; #[cfg(feature = "unstable-locales")] -pub use pure_rust_locales::Locale; - -#[cfg(not(feature = "unstable-locales"))] -#[derive(Debug)] -struct Locale; +pub use locales::Locale; +pub(crate) use parse::parse_rfc3339; +pub use parse::{parse, parse_and_remainder}; +pub use parsed::Parsed; +pub use strftime::StrftimeItems; /// An uninhabited type used for `InternalNumeric` and `InternalFixed` below. -#[derive(Clone, PartialEq, Eq)] +#[derive(Clone, PartialEq, Eq, Hash)] enum Void {} /// Padding characters for numeric items. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] pub enum Pad { /// No padding. None, @@ -78,10 +98,11 @@ pub enum Pad { /// It also trims the preceding whitespace if any. /// It cannot parse the negative number, so some date and time cannot be formatted then /// parsed with the same formatting items. -#[derive(Clone, PartialEq, Eq, Debug)] +#[non_exhaustive] +#[derive(Clone, PartialEq, Eq, Debug, Hash)] pub enum Numeric { /// Full Gregorian year (FW=4, PW=∞). - /// May accept years before 1 BCE or after 9999 CE, given an initial sign. + /// May accept years before 1 BCE or after 9999 CE, given an initial sign (+/-). Year, /// Gregorian year divided by 100 (century number; FW=PW=2). Implies the non-negative year. YearDiv100, @@ -94,6 +115,8 @@ pub enum Numeric { IsoYearDiv100, /// Year in the ISO week date, modulo 100 (FW=PW=2). Cannot be negative. IsoYearMod100, + /// Quarter (FW=PW=1). + Quarter, /// Month (FW=PW=2). Month, /// Day of the month (FW=PW=2). @@ -134,24 +157,11 @@ pub enum Numeric { } /// An opaque type representing numeric item types for internal uses only. +#[derive(Clone, Eq, Hash, PartialEq)] pub struct InternalNumeric { _dummy: Void, } -impl Clone for InternalNumeric { - fn clone(&self) -> Self { - match self._dummy {} - } -} - -impl PartialEq for InternalNumeric { - fn eq(&self, _other: &InternalNumeric) -> bool { - match self._dummy {} - } -} - -impl Eq for InternalNumeric {} - impl fmt::Debug for InternalNumeric { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "") @@ -162,7 +172,8 @@ impl fmt::Debug for InternalNumeric { /// /// They have their own rules of formatting and parsing. /// Otherwise noted, they print in the specified cases but parse case-insensitively. -#[derive(Clone, PartialEq, Eq, Debug)] +#[non_exhaustive] +#[derive(Clone, PartialEq, Eq, Debug, Hash)] pub enum Fixed { /// Abbreviated month names. /// @@ -208,6 +219,18 @@ pub enum Fixed { /// The offset is limited from `-24:00` to `+24:00`, /// which is the same as [`FixedOffset`](../offset/struct.FixedOffset.html)'s range. TimezoneOffsetColon, + /// Offset from the local time to UTC with seconds (`+09:00:00` or `-04:00:00` or `+00:00:00`). + /// + /// In the parser, the colon can be omitted and/or surrounded with any amount of whitespace. + /// The offset is limited from `-24:00:00` to `+24:00:00`, + /// which is the same as [`FixedOffset`](../offset/struct.FixedOffset.html)'s range. + TimezoneOffsetDoubleColon, + /// Offset from the local time to UTC without minutes (`+09` or `-04` or `+00`). + /// + /// In the parser, the colon can be omitted and/or surrounded with any amount of whitespace. + /// The offset is limited from `-24` to `+24`, + /// which is the same as [`FixedOffset`](../offset/struct.FixedOffset.html)'s range. + TimezoneOffsetTripleColon, /// Offset from the local time to UTC (`+09:00` or `-04:00` or `Z`). /// /// In the parser, the colon can be omitted and/or surrounded with any amount of whitespace, @@ -234,12 +257,12 @@ pub enum Fixed { } /// An opaque type representing fixed-format item types for internal uses only. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct InternalFixed { val: InternalInternal, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] enum InternalInternal { /// Same as [`TimezoneOffsetColonZ`](#variant.TimezoneOffsetColonZ), but /// allows missing minutes (per [ISO 8601][iso8601]). @@ -258,18 +281,63 @@ enum InternalInternal { Nanosecond9NoDot, } +/// Type for specifying the format of UTC offsets. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub struct OffsetFormat { + /// See `OffsetPrecision`. + pub precision: OffsetPrecision, + /// Separator between hours, minutes and seconds. + pub colons: Colons, + /// Represent `+00:00` as `Z`. + pub allow_zulu: bool, + /// Pad the hour value to two digits. + pub padding: Pad, +} + +/// The precision of an offset from UTC formatting item. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum OffsetPrecision { + /// Format offset from UTC as only hours. Not recommended, it is not uncommon for timezones to + /// have an offset of 30 minutes, 15 minutes, etc. + /// Any minutes and seconds get truncated. + Hours, + /// Format offset from UTC as hours and minutes. + /// Any seconds will be rounded to the nearest minute. + Minutes, + /// Format offset from UTC as hours, minutes and seconds. + Seconds, + /// Format offset from UTC as hours, and optionally with minutes. + /// Any seconds will be rounded to the nearest minute. + OptionalMinutes, + /// Format offset from UTC as hours and minutes, and optionally seconds. + OptionalSeconds, + /// Format offset from UTC as hours and optionally minutes and seconds. + OptionalMinutesAndSeconds, +} + +/// The separator between hours and minutes in an offset. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum Colons { + /// No separator + None, + /// Colon (`:`) as separator + Colon, + /// No separator when formatting, colon allowed when parsing. + Maybe, +} + /// A single formatting item. This is used for both formatting and parsing. -#[derive(Clone, PartialEq, Eq, Debug)] +#[derive(Clone, PartialEq, Eq, Debug, Hash)] pub enum Item<'a> { /// A literally printed and parsed text. Literal(&'a str), /// Same as `Literal` but with the string owned by the item. - #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg(feature = "alloc")] OwnedLiteral(Box), /// Whitespace. Prints literally but reads zero or more whitespace. Space(&'a str), /// Same as `Space` but with the string owned by the item. - #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg(feature = "alloc")] OwnedSpace(Box), /// Numeric item. Can be optionally padded to the maximal length (if any) when formatting; /// the parser simply ignores any padded whitespace and zeroes. @@ -280,49 +348,57 @@ pub enum Item<'a> { Error, } -macro_rules! lit { - ($x:expr) => { - Item::Literal($x) - }; +const fn num(numeric: Numeric) -> Item<'static> { + Item::Numeric(numeric, Pad::None) } -macro_rules! sp { - ($x:expr) => { - Item::Space($x) - }; + +const fn num0(numeric: Numeric) -> Item<'static> { + Item::Numeric(numeric, Pad::Zero) } -macro_rules! num { - ($x:ident) => { - Item::Numeric(Numeric::$x, Pad::None) - }; + +const fn nums(numeric: Numeric) -> Item<'static> { + Item::Numeric(numeric, Pad::Space) } -macro_rules! num0 { - ($x:ident) => { - Item::Numeric(Numeric::$x, Pad::Zero) - }; + +const fn fixed(fixed: Fixed) -> Item<'static> { + Item::Fixed(fixed) } -macro_rules! nums { - ($x:ident) => { - Item::Numeric(Numeric::$x, Pad::Space) - }; + +const fn internal_fixed(val: InternalInternal) -> Item<'static> { + Item::Fixed(Fixed::Internal(InternalFixed { val })) } -macro_rules! fix { - ($x:ident) => { - Item::Fixed(Fixed::$x) - }; -} -macro_rules! internal_fix { - ($x:ident) => { - Item::Fixed(Fixed::Internal(InternalFixed { val: InternalInternal::$x })) - }; + +impl Item<'_> { + /// Convert items that contain a reference to the format string into an owned variant. + #[cfg(any(feature = "alloc", feature = "std"))] + pub fn to_owned(self) -> Item<'static> { + match self { + Item::Literal(s) => Item::OwnedLiteral(Box::from(s)), + Item::Space(s) => Item::OwnedSpace(Box::from(s)), + Item::Numeric(n, p) => Item::Numeric(n, p), + Item::Fixed(f) => Item::Fixed(f), + Item::OwnedLiteral(l) => Item::OwnedLiteral(l), + Item::OwnedSpace(s) => Item::OwnedSpace(s), + Item::Error => Item::Error, + } + } } /// An error from the `parse` function. -#[derive(Debug, Clone, PartialEq, Eq, Copy)] +#[derive(Debug, Clone, PartialEq, Eq, Copy, Hash)] pub struct ParseError(ParseErrorKind); +impl ParseError { + /// The category of parse error + pub const fn kind(&self) -> ParseErrorKind { + self.0 + } +} + /// The category of parse error -#[derive(Debug, Clone, PartialEq, Eq, Copy)] -enum ParseErrorKind { +#[allow(clippy::manual_non_exhaustive)] +#[derive(Debug, Clone, PartialEq, Eq, Copy, Hash)] +pub enum ParseErrorKind { /// Given field is out of permitted range. OutOfRange, @@ -348,8 +424,12 @@ enum ParseErrorKind { /// All formatting items have been read but there is a remaining input. TooLong, - /// There was an error on the formatting string, or there were non-supported formating items. + /// There was an error on the formatting string, or there were non-supported formatting items. BadFormat, + + // TODO: Change this to `#[non_exhaustive]` (on the enum) with the next breaking release. + #[doc(hidden)] + __Nonexhaustive, } /// Same as `Result`. @@ -365,11 +445,12 @@ impl fmt::Display for ParseError { ParseErrorKind::TooShort => write!(f, "premature end of input"), ParseErrorKind::TooLong => write!(f, "trailing input"), ParseErrorKind::BadFormat => write!(f, "bad or unsupported format string"), + _ => unreachable!(), } } } -#[cfg(any(feature = "std", test))] +#[cfg(feature = "std")] impl Error for ParseError { #[allow(deprecated)] fn description(&self) -> &str { @@ -378,465 +459,40 @@ impl Error for ParseError { } // to be used in this module and submodules -const OUT_OF_RANGE: ParseError = ParseError(ParseErrorKind::OutOfRange); +pub(crate) const OUT_OF_RANGE: ParseError = ParseError(ParseErrorKind::OutOfRange); const IMPOSSIBLE: ParseError = ParseError(ParseErrorKind::Impossible); const NOT_ENOUGH: ParseError = ParseError(ParseErrorKind::NotEnough); const INVALID: ParseError = ParseError(ParseErrorKind::Invalid); const TOO_SHORT: ParseError = ParseError(ParseErrorKind::TooShort); -const TOO_LONG: ParseError = ParseError(ParseErrorKind::TooLong); +pub(crate) const TOO_LONG: ParseError = ParseError(ParseErrorKind::TooLong); const BAD_FORMAT: ParseError = ParseError(ParseErrorKind::BadFormat); -/// Formats single formatting item -#[cfg(any(feature = "alloc", feature = "std", test))] -pub fn format_item<'a>( - w: &mut fmt::Formatter, - date: Option<&NaiveDate>, - time: Option<&NaiveTime>, - off: Option<&(String, FixedOffset)>, - item: &Item<'a>, -) -> fmt::Result { - let mut result = String::new(); - format_inner(&mut result, date, time, off, item, None)?; - w.pad(&result) -} - -#[cfg(any(feature = "alloc", feature = "std", test))] -fn format_inner<'a>( - result: &mut String, - date: Option<&NaiveDate>, - time: Option<&NaiveTime>, - off: Option<&(String, FixedOffset)>, - item: &Item<'a>, - _locale: Option, -) -> fmt::Result { - #[cfg(feature = "unstable-locales")] - let (short_months, long_months, short_weekdays, long_weekdays, am_pm, am_pm_lowercase) = { - let locale = _locale.unwrap_or(Locale::POSIX); - let am_pm = locales::am_pm(locale); - ( - locales::short_months(locale), - locales::long_months(locale), - locales::short_weekdays(locale), - locales::long_weekdays(locale), - am_pm, - &[am_pm[0].to_lowercase(), am_pm[1].to_lowercase()], - ) - }; - #[cfg(not(feature = "unstable-locales"))] - let (short_months, long_months, short_weekdays, long_weekdays, am_pm, am_pm_lowercase) = { - ( - &["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], - &[ - "January", - "February", - "March", - "April", - "May", - "June", - "July", - "August", - "September", - "October", - "November", - "December", - ], - &["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], - &["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], - &["AM", "PM"], - &["am", "pm"], - ) - }; - - use core::fmt::Write; - use div::{div_floor, mod_floor}; - - match *item { - Item::Literal(s) | Item::Space(s) => result.push_str(s), - #[cfg(any(feature = "alloc", feature = "std", test))] - Item::OwnedLiteral(ref s) | Item::OwnedSpace(ref s) => result.push_str(s), - - Item::Numeric(ref spec, ref pad) => { - use self::Numeric::*; - - let week_from_sun = |d: &NaiveDate| { - (d.ordinal() as i32 - d.weekday().num_days_from_sunday() as i32 + 7) / 7 - }; - let week_from_mon = |d: &NaiveDate| { - (d.ordinal() as i32 - d.weekday().num_days_from_monday() as i32 + 7) / 7 - }; - - let (width, v) = match *spec { - Year => (4, date.map(|d| i64::from(d.year()))), - YearDiv100 => (2, date.map(|d| div_floor(i64::from(d.year()), 100))), - YearMod100 => (2, date.map(|d| mod_floor(i64::from(d.year()), 100))), - IsoYear => (4, date.map(|d| i64::from(d.iso_week().year()))), - IsoYearDiv100 => (2, date.map(|d| div_floor(i64::from(d.iso_week().year()), 100))), - IsoYearMod100 => (2, date.map(|d| mod_floor(i64::from(d.iso_week().year()), 100))), - Month => (2, date.map(|d| i64::from(d.month()))), - Day => (2, date.map(|d| i64::from(d.day()))), - WeekFromSun => (2, date.map(|d| i64::from(week_from_sun(d)))), - WeekFromMon => (2, date.map(|d| i64::from(week_from_mon(d)))), - IsoWeek => (2, date.map(|d| i64::from(d.iso_week().week()))), - NumDaysFromSun => (1, date.map(|d| i64::from(d.weekday().num_days_from_sunday()))), - WeekdayFromMon => (1, date.map(|d| i64::from(d.weekday().number_from_monday()))), - Ordinal => (3, date.map(|d| i64::from(d.ordinal()))), - Hour => (2, time.map(|t| i64::from(t.hour()))), - Hour12 => (2, time.map(|t| i64::from(t.hour12().1))), - Minute => (2, time.map(|t| i64::from(t.minute()))), - Second => (2, time.map(|t| i64::from(t.second() + t.nanosecond() / 1_000_000_000))), - Nanosecond => (9, time.map(|t| i64::from(t.nanosecond() % 1_000_000_000))), - Timestamp => ( - 1, - match (date, time, off) { - (Some(d), Some(t), None) => Some(d.and_time(*t).timestamp()), - (Some(d), Some(t), Some(&(_, off))) => { - Some((d.and_time(*t) - off).timestamp()) - } - (_, _, _) => None, - }, - ), - - // for the future expansion - Internal(ref int) => match int._dummy {}, - }; - - if let Some(v) = v { - if (spec == &Year || spec == &IsoYear) && !(0 <= v && v < 10_000) { - // non-four-digit years require an explicit sign as per ISO 8601 - match *pad { - Pad::None => write!(result, "{:+}", v), - Pad::Zero => write!(result, "{:+01$}", v, width + 1), - Pad::Space => write!(result, "{:+1$}", v, width + 1), - } - } else { - match *pad { - Pad::None => write!(result, "{}", v), - Pad::Zero => write!(result, "{:01$}", v, width), - Pad::Space => write!(result, "{:1$}", v, width), - } - }? - } else { - return Err(fmt::Error); // insufficient arguments for given format - } - } - - Item::Fixed(ref spec) => { - use self::Fixed::*; - - /// Prints an offset from UTC in the format of `+HHMM` or `+HH:MM`. - /// `Z` instead of `+00[:]00` is allowed when `allow_zulu` is true. - fn write_local_minus_utc( - result: &mut String, - off: FixedOffset, - allow_zulu: bool, - use_colon: bool, - ) -> fmt::Result { - let off = off.local_minus_utc(); - if !allow_zulu || off != 0 { - let (sign, off) = if off < 0 { ('-', -off) } else { ('+', off) }; - if use_colon { - write!(result, "{}{:02}:{:02}", sign, off / 3600, off / 60 % 60) - } else { - write!(result, "{}{:02}{:02}", sign, off / 3600, off / 60 % 60) - } - } else { - result.push_str("Z"); - Ok(()) - } - } - - let ret = - match *spec { - ShortMonthName => date.map(|d| { - result.push_str(short_months[d.month0() as usize]); - Ok(()) - }), - LongMonthName => date.map(|d| { - result.push_str(long_months[d.month0() as usize]); - Ok(()) - }), - ShortWeekdayName => date.map(|d| { - result - .push_str(short_weekdays[d.weekday().num_days_from_sunday() as usize]); - Ok(()) - }), - LongWeekdayName => date.map(|d| { - result.push_str(long_weekdays[d.weekday().num_days_from_sunday() as usize]); - Ok(()) - }), - LowerAmPm => time.map(|t| { - #[cfg_attr(feature = "cargo-clippy", allow(useless_asref))] - { - result.push_str(if t.hour12().0 { - am_pm_lowercase[1].as_ref() - } else { - am_pm_lowercase[0].as_ref() - }); - } - Ok(()) - }), - UpperAmPm => time.map(|t| { - result.push_str(if t.hour12().0 { am_pm[1] } else { am_pm[0] }); - Ok(()) - }), - Nanosecond => time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - if nano == 0 { - Ok(()) - } else if nano % 1_000_000 == 0 { - write!(result, ".{:03}", nano / 1_000_000) - } else if nano % 1_000 == 0 { - write!(result, ".{:06}", nano / 1_000) - } else { - write!(result, ".{:09}", nano) - } - }), - Nanosecond3 => time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - write!(result, ".{:03}", nano / 1_000_000) - }), - Nanosecond6 => time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - write!(result, ".{:06}", nano / 1_000) - }), - Nanosecond9 => time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - write!(result, ".{:09}", nano) - }), - Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) => time - .map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - write!(result, "{:03}", nano / 1_000_000) - }), - Internal(InternalFixed { val: InternalInternal::Nanosecond6NoDot }) => time - .map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - write!(result, "{:06}", nano / 1_000) - }), - Internal(InternalFixed { val: InternalInternal::Nanosecond9NoDot }) => time - .map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - write!(result, "{:09}", nano) - }), - TimezoneName => off.map(|&(ref name, _)| { - result.push_str(name); - Ok(()) - }), - TimezoneOffsetColon => { - off.map(|&(_, off)| write_local_minus_utc(result, off, false, true)) - } - TimezoneOffsetColonZ => { - off.map(|&(_, off)| write_local_minus_utc(result, off, true, true)) - } - TimezoneOffset => { - off.map(|&(_, off)| write_local_minus_utc(result, off, false, false)) - } - TimezoneOffsetZ => { - off.map(|&(_, off)| write_local_minus_utc(result, off, true, false)) - } - Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive }) => { - panic!("Do not try to write %#z it is undefined") - } - RFC2822 => - // same as `%a, %d %b %Y %H:%M:%S %z` - { - if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) { - let sec = t.second() + t.nanosecond() / 1_000_000_000; - write!( - result, - "{}, {:02} {} {:04} {:02}:{:02}:{:02} ", - short_weekdays[d.weekday().num_days_from_sunday() as usize], - d.day(), - short_months[d.month0() as usize], - d.year(), - t.hour(), - t.minute(), - sec - )?; - Some(write_local_minus_utc(result, off, false, false)) - } else { - None - } - } - RFC3339 => - // same as `%Y-%m-%dT%H:%M:%S%.f%:z` - { - if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) { - // reuse `Debug` impls which already print ISO 8601 format. - // this is faster in this way. - write!(result, "{:?}T{:?}", d, t)?; - Some(write_local_minus_utc(result, off, false, true)) - } else { - None - } - } - }; - - match ret { - Some(ret) => ret?, - None => return Err(fmt::Error), // insufficient arguments for given format - } - } - - Item::Error => return Err(fmt::Error), - } - Ok(()) -} - -/// Tries to format given arguments with given formatting items. -/// Internally used by `DelayedFormat`. -#[cfg(any(feature = "alloc", feature = "std", test))] -pub fn format<'a, I, B>( - w: &mut fmt::Formatter, - date: Option<&NaiveDate>, - time: Option<&NaiveTime>, - off: Option<&(String, FixedOffset)>, - items: I, -) -> fmt::Result -where - I: Iterator + Clone, - B: Borrow>, -{ - let mut result = String::new(); - for item in items { - format_inner(&mut result, date, time, off, item.borrow(), None)?; - } - w.pad(&result) -} - -mod parsed; - -// due to the size of parsing routines, they are in separate modules. -mod parse; -mod scan; - -pub mod strftime; - -/// A *temporary* object which can be used as an argument to `format!` or others. -/// This is normally constructed via `format` methods of each date and time type. -#[cfg(any(feature = "alloc", feature = "std", test))] -#[derive(Debug)] -pub struct DelayedFormat { - /// The date view, if any. - date: Option, - /// The time view, if any. - time: Option, - /// The name and local-to-UTC difference for the offset (timezone), if any. - off: Option<(String, FixedOffset)>, - /// An iterator returning formatting items. - items: I, - /// Locale used for text. - locale: Option, -} - -#[cfg(any(feature = "alloc", feature = "std", test))] -impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { - /// Makes a new `DelayedFormat` value out of local date and time. - pub fn new(date: Option, time: Option, items: I) -> DelayedFormat { - DelayedFormat { date: date, time: time, off: None, items: items, locale: None } - } - - /// Makes a new `DelayedFormat` value out of local date and time and UTC offset. - pub fn new_with_offset( - date: Option, - time: Option, - offset: &Off, - items: I, - ) -> DelayedFormat - where - Off: Offset + fmt::Display, - { - let name_and_diff = (offset.to_string(), offset.fix()); - DelayedFormat { - date: date, - time: time, - off: Some(name_and_diff), - items: items, - locale: None, - } - } - - /// Makes a new `DelayedFormat` value out of local date and time and locale. - #[cfg(feature = "unstable-locales")] - pub fn new_with_locale( - date: Option, - time: Option, - items: I, - locale: Locale, - ) -> DelayedFormat { - DelayedFormat { date: date, time: time, off: None, items: items, locale: Some(locale) } - } - - /// Makes a new `DelayedFormat` value out of local date and time, UTC offset and locale. - #[cfg(feature = "unstable-locales")] - pub fn new_with_offset_and_locale( - date: Option, - time: Option, - offset: &Off, - items: I, - locale: Locale, - ) -> DelayedFormat - where - Off: Offset + fmt::Display, - { - let name_and_diff = (offset.to_string(), offset.fix()); - DelayedFormat { - date: date, - time: time, - off: Some(name_and_diff), - items: items, - locale: Some(locale), - } - } -} - -#[cfg(any(feature = "alloc", feature = "std", test))] -impl<'a, I: Iterator + Clone, B: Borrow>> fmt::Display for DelayedFormat { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - #[cfg(feature = "unstable-locales")] - { - if let Some(locale) = self.locale { - return format_localized( - f, - self.date.as_ref(), - self.time.as_ref(), - self.off.as_ref(), - self.items.clone(), - locale, - ); - } - } - - format(f, self.date.as_ref(), self.time.as_ref(), self.off.as_ref(), self.items.clone()) - } -} - // this implementation is here only because we need some private code from `scan` -/// Parsing a `str` into a `Weekday` uses the format [`%W`](./format/strftime/index.html). +/// Parsing a `str` into a `Weekday` uses the format [`%A`](./format/strftime/index.html). /// /// # Example /// -/// ~~~~ +/// ``` /// use chrono::Weekday; /// /// assert_eq!("Sunday".parse::(), Ok(Weekday::Sun)); /// assert!("any day".parse::().is_err()); -/// ~~~~ +/// ``` /// /// The parsing is case-insensitive. /// -/// ~~~~ +/// ``` /// # use chrono::Weekday; /// assert_eq!("mON".parse::(), Ok(Weekday::Mon)); -/// ~~~~ +/// ``` /// /// Only the shortest form (e.g. `sun`) and the longest form (e.g. `sunday`) is accepted. /// -/// ~~~~ +/// ``` /// # use chrono::Weekday; /// assert!("thurs".parse::().is_err()); -/// ~~~~ +/// ``` impl FromStr for Weekday { type Err = ParseWeekdayError; @@ -849,68 +505,31 @@ impl FromStr for Weekday { } } -/// Formats single formatting item -#[cfg(feature = "unstable-locales")] -pub fn format_item_localized<'a>( - w: &mut fmt::Formatter, - date: Option<&NaiveDate>, - time: Option<&NaiveTime>, - off: Option<&(String, FixedOffset)>, - item: &Item<'a>, - locale: Locale, -) -> fmt::Result { - let mut result = String::new(); - format_inner(&mut result, date, time, off, item, Some(locale))?; - w.pad(&result) -} - -/// Tries to format given arguments with given formatting items. -/// Internally used by `DelayedFormat`. -#[cfg(feature = "unstable-locales")] -pub fn format_localized<'a, I, B>( - w: &mut fmt::Formatter, - date: Option<&NaiveDate>, - time: Option<&NaiveTime>, - off: Option<&(String, FixedOffset)>, - items: I, - locale: Locale, -) -> fmt::Result -where - I: Iterator + Clone, - B: Borrow>, -{ - let mut result = String::new(); - for item in items { - format_inner(&mut result, date, time, off, item.borrow(), Some(locale))?; - } - w.pad(&result) -} - -/// Parsing a `str` into a `Month` uses the format [`%W`](./format/strftime/index.html). +/// Parsing a `str` into a `Month` uses the format [`%B`](./format/strftime/index.html). /// /// # Example /// -/// ~~~~ +/// ``` /// use chrono::Month; /// /// assert_eq!("January".parse::(), Ok(Month::January)); /// assert!("any day".parse::().is_err()); -/// ~~~~ +/// ``` /// /// The parsing is case-insensitive. /// -/// ~~~~ +/// ``` /// # use chrono::Month; /// assert_eq!("fEbruARy".parse::(), Ok(Month::February)); -/// ~~~~ +/// ``` /// /// Only the shortest form (e.g. `jan`) and the longest form (e.g. `january`) is accepted. /// -/// ~~~~ +/// ``` /// # use chrono::Month; /// assert!("septem".parse::().is_err()); /// assert!("Augustin".parse::().is_err()); -/// ~~~~ +/// ``` impl FromStr for Month { type Err = ParseMonthError; diff --git a/third_party/rust/chrono/src/format/parse.rs b/third_party/rust/chrono/src/format/parse.rs index 2fce8277b16..40b5b052469 100644 --- a/third_party/rust/chrono/src/format/parse.rs +++ b/third_party/rust/chrono/src/format/parse.rs @@ -4,17 +4,14 @@ //! Date and time parsing routines. -#![allow(deprecated)] - use core::borrow::Borrow; use core::str; -use core::usize; use super::scan; +use super::{BAD_FORMAT, INVALID, OUT_OF_RANGE, TOO_LONG, TOO_SHORT}; use super::{Fixed, InternalFixed, InternalInternal, Item, Numeric, Pad, Parsed}; -use super::{ParseError, ParseErrorKind, ParseResult}; -use super::{BAD_FORMAT, INVALID, NOT_ENOUGH, OUT_OF_RANGE, TOO_LONG, TOO_SHORT}; -use {DateTime, FixedOffset, Weekday}; +use super::{ParseError, ParseResult}; +use crate::{DateTime, FixedOffset, Weekday}; fn set_weekday_with_num_days_from_sunday(p: &mut Parsed, v: i64) -> ParseResult<()> { p.set_weekday(match v { @@ -53,7 +50,10 @@ fn parse_rfc2822<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st // an adapted RFC 2822 syntax from Section 3.3 and 4.3: // - // date-time = [ day-of-week "," ] date 1*S time *S + // c-char = + // c-escape = "\" + // comment = "(" *(comment / c-char / c-escape) ")" *S + // date-time = [ day-of-week "," ] date 1*S time *S *comment // day-of-week = *S day-name *S // day-name = "Mon" / "Tue" / "Wed" / "Thu" / "Fri" / "Sat" / "Sun" // date = day month year @@ -79,9 +79,10 @@ fn parse_rfc2822<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st // // - we do not recognize a folding white space (FWS) or comment (CFWS). // for our purposes, instead, we accept any sequence of Unicode - // white space characters (denoted here to `S`). any actual RFC 2822 - // parser is expected to parse FWS and/or CFWS themselves and replace - // it with a single SP (`%x20`); this is legitimate. + // white space characters (denoted here to `S`). For comments, we accept + // any text within parentheses while respecting escaped parentheses. + // Any actual RFC 2822 parser is expected to parse FWS and/or CFWS themselves + // and replace it with a single SP (`%x20`); this is legitimate. // // - two-digit year < 50 should be interpreted by adding 2000. // two-digit year >= 50 or three-digit year should be interpreted @@ -96,7 +97,7 @@ fn parse_rfc2822<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st // since we do not directly go to a `DateTime` so one can recover // the offset information from `Parsed` anyway. - s = s.trim_left(); + s = s.trim_start(); if let Ok((s_, weekday)) = scan::short_weekday(s) { if !s_.starts_with(',') { @@ -106,7 +107,7 @@ fn parse_rfc2822<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st parsed.set_weekday(weekday)?; } - s = s.trim_left(); + s = s.trim_start(); parsed.set_day(try_consume!(scan::number(s, 1, 2)))?; s = scan::space(s)?; // mandatory parsed.set_month(1 + i64::from(try_consume!(scan::short_month0(s))))?; @@ -117,10 +118,10 @@ fn parse_rfc2822<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st let mut year = try_consume!(scan::number(s, 2, usize::MAX)); let yearlen = prevlen - s.len(); match (yearlen, year) { - (2, 0...49) => { + (2, 0..=49) => { year += 2000; } // 47 -> 2047, 05 -> 2005 - (2, 50...99) => { + (2, 50..=99) => { year += 1900; } // 79 -> 1979 (3, _) => { @@ -132,23 +133,25 @@ fn parse_rfc2822<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st s = scan::space(s)?; // mandatory parsed.set_hour(try_consume!(scan::number(s, 2, 2)))?; - s = scan::char(s.trim_left(), b':')?.trim_left(); // *S ":" *S + s = scan::char(s.trim_start(), b':')?.trim_start(); // *S ":" *S parsed.set_minute(try_consume!(scan::number(s, 2, 2)))?; - if let Ok(s_) = scan::char(s.trim_left(), b':') { + if let Ok(s_) = scan::char(s.trim_start(), b':') { // [ ":" *S 2DIGIT ] parsed.set_second(try_consume!(scan::number(s_, 2, 2)))?; } s = scan::space(s)?; // mandatory - if let Some(offset) = try_consume!(scan::timezone_offset_2822(s)) { - // only set the offset when it is definitely known (i.e. not `-0000`) - parsed.set_offset(i64::from(offset))?; + parsed.set_offset(i64::from(try_consume!(scan::timezone_offset_2822(s))))?; + + // optional comments + while let Ok((s_out, ())) = scan::comment_2822(s) { + s = s_out; } Ok((s, ())) } -fn parse_rfc3339<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a str, ())> { +pub(crate) fn parse_rfc3339<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a str, ())> { macro_rules! try_consume { ($e:expr) => {{ let (s_, v) = $e?; @@ -183,6 +186,8 @@ fn parse_rfc3339<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st // - unlike RFC 2822, the valid offset ranges from -23:59 to +23:59. // note that this restriction is unique to RFC 3339 and not ISO 8601. // since this is not a typical Chrono behavior, we check it earlier. + // + // - For readability a full-date and a full-time may be separated by a space character. parsed.set_year(try_consume!(scan::number(s, 4, 4)))?; s = scan::char(s, b'-')?; @@ -191,7 +196,7 @@ fn parse_rfc3339<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st parsed.set_day(try_consume!(scan::number(s, 2, 2)))?; s = match s.as_bytes().first() { - Some(&b't') | Some(&b'T') => &s[1..], + Some(&b't' | &b'T' | &b' ') => &s[1..], Some(_) => return Err(INVALID), None => return Err(TOO_SHORT), }; @@ -206,8 +211,13 @@ fn parse_rfc3339<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st parsed.set_nanosecond(nanosecond)?; } - let offset = try_consume!(scan::timezone_offset_zulu(s, |s| scan::char(s, b':'))); - if offset <= -86_400 || offset >= 86_400 { + let offset = try_consume!(scan::timezone_offset(s, |s| scan::char(s, b':'), true, false, true)); + // This range check is similar to the one in `FixedOffset::east_opt`, so it would be redundant. + // But it is possible to read the offset directly from `Parsed`. We want to only successfully + // populate `Parsed` if the input is fully valid RFC 3339. + // Max for the hours field is `23`, and for the minutes field `59`. + const MAX_RFC3339_OFFSET: i32 = (23 * 60 + 59) * 60; + if !(-MAX_RFC3339_OFFSET..=MAX_RFC3339_OFFSET).contains(&offset) { return Err(OUT_OF_RANGE); } parsed.set_offset(i64::from(offset))?; @@ -236,14 +246,44 @@ where I: Iterator, B: Borrow>, { - parse_internal(parsed, s, items).map(|_| ()).map_err(|(_s, e)| e) + match parse_internal(parsed, s, items) { + Ok("") => Ok(()), + Ok(_) => Err(TOO_LONG), // if there are trailing chars it is an error + Err(e) => Err(e), + } +} + +/// Tries to parse given string into `parsed` with given formatting items. +/// Returns `Ok` with a slice of the unparsed remainder. +/// +/// This particular date and time parser is: +/// +/// - Greedy. It will consume the longest possible prefix. +/// For example, `April` is always consumed entirely when the long month name is requested; +/// it equally accepts `Apr`, but prefers the longer prefix in this case. +/// +/// - Padding-agnostic (for numeric items). +/// The [`Pad`](./enum.Pad.html) field is completely ignored, +/// so one can prepend any number of zeroes before numbers. +/// +/// - (Still) obeying the intrinsic parsing width. This allows, for example, parsing `HHMMSS`. +pub fn parse_and_remainder<'a, 'b, I, B>( + parsed: &mut Parsed, + s: &'b str, + items: I, +) -> ParseResult<&'b str> +where + I: Iterator, + B: Borrow>, +{ + parse_internal(parsed, s, items) } fn parse_internal<'a, 'b, I, B>( parsed: &mut Parsed, mut s: &'b str, items: I, -) -> Result<&'b str, (&'b str, ParseError)> +) -> Result<&'b str, ParseError> where I: Iterator, B: Borrow>, @@ -255,7 +295,7 @@ where s = s_; v } - Err(e) => return Err((s, e)), + Err(e) => return Err(e), } }}; } @@ -264,32 +304,32 @@ where match *item.borrow() { Item::Literal(prefix) => { if s.len() < prefix.len() { - return Err((s, TOO_SHORT)); + return Err(TOO_SHORT); } if !s.starts_with(prefix) { - return Err((s, INVALID)); + return Err(INVALID); } s = &s[prefix.len()..]; } - #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg(feature = "alloc")] Item::OwnedLiteral(ref prefix) => { if s.len() < prefix.len() { - return Err((s, TOO_SHORT)); + return Err(TOO_SHORT); } if !s.starts_with(&prefix[..]) { - return Err((s, INVALID)); + return Err(INVALID); } s = &s[prefix.len()..]; } Item::Space(_) => { - s = s.trim_left(); + s = s.trim_start(); } - #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg(feature = "alloc")] Item::OwnedSpace(_) => { - s = s.trim_left(); + s = s.trim_start(); } Item::Numeric(ref spec, ref _pad) => { @@ -303,6 +343,7 @@ where IsoYear => (4, true, Parsed::set_isoyear), IsoYearDiv100 => (2, false, Parsed::set_isoyear_div_100), IsoYearMod100 => (2, false, Parsed::set_isoyear_mod_100), + Quarter => (1, false, Parsed::set_quarter), Month => (2, false, Parsed::set_month), Day => (2, false, Parsed::set_day), WeekFromSun => (2, false, Parsed::set_week_from_sun), @@ -322,11 +363,11 @@ where Internal(ref int) => match int._dummy {}, }; - s = s.trim_left(); + s = s.trim_start(); let v = if signed { if s.starts_with('-') { let v = try_consume!(scan::number(&s[1..], 1, usize::MAX)); - 0i64.checked_sub(v).ok_or((s, OUT_OF_RANGE))? + 0i64.checked_sub(v).ok_or(OUT_OF_RANGE)? } else if s.starts_with('+') { try_consume!(scan::number(&s[1..], 1, usize::MAX)) } else { @@ -336,7 +377,7 @@ where } else { try_consume!(scan::number(s, 1, width)) }; - set(parsed, v).map_err(|e| (s, e))?; + set(parsed, v)?; } Item::Fixed(ref spec) => { @@ -345,590 +386,1469 @@ where match spec { &ShortMonthName => { let month0 = try_consume!(scan::short_month0(s)); - parsed.set_month(i64::from(month0) + 1).map_err(|e| (s, e))?; + parsed.set_month(i64::from(month0) + 1)?; } &LongMonthName => { let month0 = try_consume!(scan::short_or_long_month0(s)); - parsed.set_month(i64::from(month0) + 1).map_err(|e| (s, e))?; + parsed.set_month(i64::from(month0) + 1)?; } &ShortWeekdayName => { let weekday = try_consume!(scan::short_weekday(s)); - parsed.set_weekday(weekday).map_err(|e| (s, e))?; + parsed.set_weekday(weekday)?; } &LongWeekdayName => { let weekday = try_consume!(scan::short_or_long_weekday(s)); - parsed.set_weekday(weekday).map_err(|e| (s, e))?; + parsed.set_weekday(weekday)?; } &LowerAmPm | &UpperAmPm => { if s.len() < 2 { - return Err((s, TOO_SHORT)); + return Err(TOO_SHORT); } let ampm = match (s.as_bytes()[0] | 32, s.as_bytes()[1] | 32) { (b'a', b'm') => false, (b'p', b'm') => true, - _ => return Err((s, INVALID)), + _ => return Err(INVALID), }; - parsed.set_ampm(ampm).map_err(|e| (s, e))?; + parsed.set_ampm(ampm)?; s = &s[2..]; } &Nanosecond | &Nanosecond3 | &Nanosecond6 | &Nanosecond9 => { if s.starts_with('.') { let nano = try_consume!(scan::nanosecond(&s[1..])); - parsed.set_nanosecond(nano).map_err(|e| (s, e))?; + parsed.set_nanosecond(nano)?; } } &Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) => { if s.len() < 3 { - return Err((s, TOO_SHORT)); + return Err(TOO_SHORT); } let nano = try_consume!(scan::nanosecond_fixed(s, 3)); - parsed.set_nanosecond(nano).map_err(|e| (s, e))?; + parsed.set_nanosecond(nano)?; } &Internal(InternalFixed { val: InternalInternal::Nanosecond6NoDot }) => { if s.len() < 6 { - return Err((s, TOO_SHORT)); + return Err(TOO_SHORT); } let nano = try_consume!(scan::nanosecond_fixed(s, 6)); - parsed.set_nanosecond(nano).map_err(|e| (s, e))?; + parsed.set_nanosecond(nano)?; } &Internal(InternalFixed { val: InternalInternal::Nanosecond9NoDot }) => { if s.len() < 9 { - return Err((s, TOO_SHORT)); + return Err(TOO_SHORT); } let nano = try_consume!(scan::nanosecond_fixed(s, 9)); - parsed.set_nanosecond(nano).map_err(|e| (s, e))?; + parsed.set_nanosecond(nano)?; } &TimezoneName => { - try_consume!(scan::timezone_name_skip(s)); + try_consume!(Ok((s.trim_start_matches(|c: char| !c.is_whitespace()), ()))); } - &TimezoneOffsetColon | &TimezoneOffset => { + &TimezoneOffsetColon + | &TimezoneOffsetDoubleColon + | &TimezoneOffsetTripleColon + | &TimezoneOffset => { let offset = try_consume!(scan::timezone_offset( - s.trim_left(), - scan::colon_or_space + s.trim_start(), + scan::colon_or_space, + false, + false, + true, )); - parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; + parsed.set_offset(i64::from(offset))?; } &TimezoneOffsetColonZ | &TimezoneOffsetZ => { - let offset = try_consume!(scan::timezone_offset_zulu( - s.trim_left(), - scan::colon_or_space + let offset = try_consume!(scan::timezone_offset( + s.trim_start(), + scan::colon_or_space, + true, + false, + true, )); - parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; + parsed.set_offset(i64::from(offset))?; } &Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive, }) => { - let offset = try_consume!(scan::timezone_offset_permissive( - s.trim_left(), - scan::colon_or_space + let offset = try_consume!(scan::timezone_offset( + s.trim_start(), + scan::colon_or_space, + true, + true, + true, )); - parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; + parsed.set_offset(i64::from(offset))?; } &RFC2822 => try_consume!(parse_rfc2822(parsed, s)), - &RFC3339 => try_consume!(parse_rfc3339(parsed, s)), + &RFC3339 => { + // Used for the `%+` specifier, which has the description: + // "Same as `%Y-%m-%dT%H:%M:%S%.f%:z` (...) + // This format also supports having a `Z` or `UTC` in place of `%:z`." + // Use the relaxed parser to match this description. + try_consume!(parse_rfc3339_relaxed(parsed, s)) + } } } Item::Error => { - return Err((s, BAD_FORMAT)); + return Err(BAD_FORMAT); } } } - - // if there are trailling chars, it is an error - if !s.is_empty() { - Err((s, TOO_LONG)) - } else { - Ok(s) - } + Ok(s) } +/// Accepts a relaxed form of RFC3339. +/// A space or a 'T' are accepted as the separator between the date and time +/// parts. Additional spaces are allowed between each component. +/// +/// All of these examples are equivalent: +/// ``` +/// # use chrono::{DateTime, offset::FixedOffset}; +/// "2012-12-12T12:12:12Z".parse::>()?; +/// "2012-12-12 12:12:12Z".parse::>()?; +/// "2012- 12-12T12: 12:12Z".parse::>()?; +/// # Ok::<(), chrono::ParseError>(()) +/// ``` impl str::FromStr for DateTime { type Err = ParseError; fn from_str(s: &str) -> ParseResult> { - const DATE_ITEMS: &'static [Item<'static>] = &[ - Item::Numeric(Numeric::Year, Pad::Zero), - Item::Space(""), - Item::Literal("-"), - Item::Numeric(Numeric::Month, Pad::Zero), - Item::Space(""), - Item::Literal("-"), - Item::Numeric(Numeric::Day, Pad::Zero), - ]; - const TIME_ITEMS: &'static [Item<'static>] = &[ - Item::Numeric(Numeric::Hour, Pad::Zero), - Item::Space(""), - Item::Literal(":"), - Item::Numeric(Numeric::Minute, Pad::Zero), - Item::Space(""), - Item::Literal(":"), - Item::Numeric(Numeric::Second, Pad::Zero), - Item::Fixed(Fixed::Nanosecond), - Item::Space(""), - Item::Fixed(Fixed::TimezoneOffsetZ), - Item::Space(""), - ]; - let mut parsed = Parsed::new(); - match parse_internal(&mut parsed, s, DATE_ITEMS.iter()) { - Err((remainder, e)) if e.0 == ParseErrorKind::TooLong => { - if remainder.starts_with('T') || remainder.starts_with(' ') { - parse(&mut parsed, &remainder[1..], TIME_ITEMS.iter())?; - } else { - Err(INVALID)?; - } - } - Err((_s, e)) => Err(e)?, - Ok(_) => Err(NOT_ENOUGH)?, - }; + let (s, _) = parse_rfc3339_relaxed(&mut parsed, s)?; + if !s.trim_start().is_empty() { + return Err(TOO_LONG); + } parsed.to_datetime() } } +/// Accepts a relaxed form of RFC3339. +/// +/// Differences with RFC3339: +/// - Values don't require padding to two digits. +/// - Years outside the range 0...=9999 are accepted, but they must include a sign. +/// - `UTC` is accepted as a valid timezone name/offset (for compatibility with the debug format of +/// `DateTime`. +/// - There can be spaces between any of the components. +/// - The colon in the offset may be missing. +fn parse_rfc3339_relaxed<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a str, ())> { + const DATE_ITEMS: &[Item<'static>] = &[ + Item::Numeric(Numeric::Year, Pad::Zero), + Item::Space(""), + Item::Literal("-"), + Item::Numeric(Numeric::Month, Pad::Zero), + Item::Space(""), + Item::Literal("-"), + Item::Numeric(Numeric::Day, Pad::Zero), + ]; + const TIME_ITEMS: &[Item<'static>] = &[ + Item::Numeric(Numeric::Hour, Pad::Zero), + Item::Space(""), + Item::Literal(":"), + Item::Numeric(Numeric::Minute, Pad::Zero), + Item::Space(""), + Item::Literal(":"), + Item::Numeric(Numeric::Second, Pad::Zero), + Item::Fixed(Fixed::Nanosecond), + Item::Space(""), + ]; + + s = parse_internal(parsed, s, DATE_ITEMS.iter())?; + + s = match s.as_bytes().first() { + Some(&b't' | &b'T' | &b' ') => &s[1..], + Some(_) => return Err(INVALID), + None => return Err(TOO_SHORT), + }; + + s = parse_internal(parsed, s, TIME_ITEMS.iter())?; + s = s.trim_start(); + let (s, offset) = if s.len() >= 3 && "UTC".as_bytes().eq_ignore_ascii_case(&s.as_bytes()[..3]) { + (&s[3..], 0) + } else { + scan::timezone_offset(s, scan::colon_or_space, true, false, true)? + }; + parsed.set_offset(i64::from(offset))?; + Ok((s, ())) +} + #[cfg(test)] -#[test] -fn test_parse() { - use super::IMPOSSIBLE; - use super::*; +mod tests { + use crate::format::*; + use crate::{DateTime, FixedOffset, NaiveDateTime, TimeZone, Timelike, Utc}; - // workaround for Rust issue #22255 - fn parse_all(s: &str, items: &[Item]) -> ParseResult { - let mut parsed = Parsed::new(); - parse(&mut parsed, s, items.iter())?; - Ok(parsed) - } - - macro_rules! check { - ($fmt:expr, $items:expr; $err:tt) => ( - assert_eq!(parse_all($fmt, &$items), Err($err)) - ); - ($fmt:expr, $items:expr; $($k:ident: $v:expr),*) => (#[allow(unused_mut)] { + macro_rules! parsed { + ($($k:ident: $v:expr),*) => (#[allow(unused_mut)] { let mut expected = Parsed::new(); $(expected.$k = Some($v);)* - assert_eq!(parse_all($fmt, &$items), Ok(expected)) + Ok(expected) }); } - // empty string - check!("", []; ); - check!(" ", []; TOO_LONG); - check!("a", []; TOO_LONG); + #[test] + fn test_parse_whitespace_and_literal() { + use crate::format::Item::{Literal, Space}; - // whitespaces - check!("", [sp!("")]; ); - check!(" ", [sp!("")]; ); - check!("\t", [sp!("")]; ); - check!(" \n\r \n", [sp!("")]; ); - check!("a", [sp!("")]; TOO_LONG); + // empty string + parses("", &[]); + check(" ", &[], Err(TOO_LONG)); + check("a", &[], Err(TOO_LONG)); + check("abc", &[], Err(TOO_LONG)); + check("🤠", &[], Err(TOO_LONG)); - // literal - check!("", [lit!("a")]; TOO_SHORT); - check!(" ", [lit!("a")]; INVALID); - check!("a", [lit!("a")]; ); - check!("aa", [lit!("a")]; TOO_LONG); - check!("A", [lit!("a")]; INVALID); - check!("xy", [lit!("xy")]; ); - check!("xy", [lit!("x"), lit!("y")]; ); - check!("x y", [lit!("x"), lit!("y")]; INVALID); - check!("xy", [lit!("x"), sp!(""), lit!("y")]; ); - check!("x y", [lit!("x"), sp!(""), lit!("y")]; ); + // whitespaces + parses("", &[Space("")]); + parses(" ", &[Space(" ")]); + parses(" ", &[Space(" ")]); + parses(" ", &[Space(" ")]); + parses(" ", &[Space("")]); + parses(" ", &[Space(" ")]); + parses(" ", &[Space(" ")]); + parses(" ", &[Space(" ")]); + parses("", &[Space(" ")]); + parses(" ", &[Space(" ")]); + parses(" ", &[Space(" ")]); + parses(" ", &[Space(" "), Space(" ")]); + parses(" ", &[Space(" "), Space(" ")]); + parses(" ", &[Space(" "), Space(" ")]); + parses(" ", &[Space(" "), Space(" ")]); + parses(" ", &[Space(" "), Space(" ")]); + parses(" ", &[Space(" "), Space(" "), Space(" ")]); + parses("\t", &[Space("")]); + parses(" \n\r \n", &[Space("")]); + parses("\t", &[Space("\t")]); + parses("\t", &[Space(" ")]); + parses(" ", &[Space("\t")]); + parses("\t\r", &[Space("\t\r")]); + parses("\t\r ", &[Space("\t\r ")]); + parses("\t \r", &[Space("\t \r")]); + parses(" \t\r", &[Space(" \t\r")]); + parses(" \n\r \n", &[Space(" \n\r \n")]); + parses(" \t\n", &[Space(" \t")]); + parses(" \n\t", &[Space(" \t\n")]); + parses("\u{2002}", &[Space("\u{2002}")]); + // most unicode whitespace characters + parses( + "\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{3000}", + &[Space( + "\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{3000}", + )], + ); + // most unicode whitespace characters + parses( + "\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{3000}", + &[ + Space("\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}"), + Space("\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{3000}"), + ], + ); + check("a", &[Space("")], Err(TOO_LONG)); + check("a", &[Space(" ")], Err(TOO_LONG)); + // a Space containing a literal does not match a literal + check("a", &[Space("a")], Err(TOO_LONG)); + check("abc", &[Space("")], Err(TOO_LONG)); + check("abc", &[Space(" ")], Err(TOO_LONG)); + check(" abc", &[Space("")], Err(TOO_LONG)); + check(" abc", &[Space(" ")], Err(TOO_LONG)); - // numeric - check!("1987", [num!(Year)]; year: 1987); - check!("1987 ", [num!(Year)]; TOO_LONG); - check!("0x12", [num!(Year)]; TOO_LONG); // `0` is parsed - check!("x123", [num!(Year)]; INVALID); - check!("2015", [num!(Year)]; year: 2015); - check!("0000", [num!(Year)]; year: 0); - check!("9999", [num!(Year)]; year: 9999); - check!(" \t987", [num!(Year)]; year: 987); - check!("5", [num!(Year)]; year: 5); - check!("5\0", [num!(Year)]; TOO_LONG); - check!("\05", [num!(Year)]; INVALID); - check!("", [num!(Year)]; TOO_SHORT); - check!("12345", [num!(Year), lit!("5")]; year: 1234); - check!("12345", [nums!(Year), lit!("5")]; year: 1234); - check!("12345", [num0!(Year), lit!("5")]; year: 1234); - check!("12341234", [num!(Year), num!(Year)]; year: 1234); - check!("1234 1234", [num!(Year), num!(Year)]; year: 1234); - check!("1234 1235", [num!(Year), num!(Year)]; IMPOSSIBLE); - check!("1234 1234", [num!(Year), lit!("x"), num!(Year)]; INVALID); - check!("1234x1234", [num!(Year), lit!("x"), num!(Year)]; year: 1234); - check!("1234xx1234", [num!(Year), lit!("x"), num!(Year)]; INVALID); - check!("1234 x 1234", [num!(Year), lit!("x"), num!(Year)]; INVALID); + // `\u{0363}` is combining diacritic mark "COMBINING LATIN SMALL LETTER A" - // signed numeric - check!("-42", [num!(Year)]; year: -42); - check!("+42", [num!(Year)]; year: 42); - check!("-0042", [num!(Year)]; year: -42); - check!("+0042", [num!(Year)]; year: 42); - check!("-42195", [num!(Year)]; year: -42195); - check!("+42195", [num!(Year)]; year: 42195); - check!(" -42195", [num!(Year)]; year: -42195); - check!(" +42195", [num!(Year)]; year: 42195); - check!(" - 42", [num!(Year)]; INVALID); - check!(" + 42", [num!(Year)]; INVALID); - check!("-", [num!(Year)]; TOO_SHORT); - check!("+", [num!(Year)]; TOO_SHORT); + // literal + parses("", &[Literal("")]); + check("", &[Literal("a")], Err(TOO_SHORT)); + check(" ", &[Literal("a")], Err(INVALID)); + parses("a", &[Literal("a")]); + parses("+", &[Literal("+")]); + parses("-", &[Literal("-")]); + parses("−", &[Literal("−")]); // MINUS SIGN (U+2212) + parses(" ", &[Literal(" ")]); // a Literal may contain whitespace and match whitespace + check("aa", &[Literal("a")], Err(TOO_LONG)); + check("🤠", &[Literal("a")], Err(INVALID)); + check("A", &[Literal("a")], Err(INVALID)); + check("a", &[Literal("z")], Err(INVALID)); + check("a", &[Literal("🤠")], Err(TOO_SHORT)); + check("a", &[Literal("\u{0363}a")], Err(TOO_SHORT)); + check("\u{0363}a", &[Literal("a")], Err(INVALID)); + parses("\u{0363}a", &[Literal("\u{0363}a")]); + check("a", &[Literal("ab")], Err(TOO_SHORT)); + parses("xy", &[Literal("xy")]); + parses("xy", &[Literal("x"), Literal("y")]); + parses("1", &[Literal("1")]); + parses("1234", &[Literal("1234")]); + parses("+1234", &[Literal("+1234")]); + parses("-1234", &[Literal("-1234")]); + parses("−1234", &[Literal("−1234")]); // MINUS SIGN (U+2212) + parses("PST", &[Literal("PST")]); + parses("🤠", &[Literal("🤠")]); + parses("🤠a", &[Literal("🤠"), Literal("a")]); + parses("🤠a🤠", &[Literal("🤠"), Literal("a🤠")]); + parses("a🤠b", &[Literal("a"), Literal("🤠"), Literal("b")]); + // literals can be together + parses("xy", &[Literal("xy")]); + parses("xyz", &[Literal("xyz")]); + // or literals can be apart + parses("xy", &[Literal("x"), Literal("y")]); + parses("xyz", &[Literal("x"), Literal("yz")]); + parses("xyz", &[Literal("xy"), Literal("z")]); + parses("xyz", &[Literal("x"), Literal("y"), Literal("z")]); + // + check("x y", &[Literal("x"), Literal("y")], Err(INVALID)); + parses("xy", &[Literal("x"), Space(""), Literal("y")]); + parses("x y", &[Literal("x"), Space(""), Literal("y")]); + parses("x y", &[Literal("x"), Space(" "), Literal("y")]); - // unsigned numeric - check!("345", [num!(Ordinal)]; ordinal: 345); - check!("+345", [num!(Ordinal)]; INVALID); - check!("-345", [num!(Ordinal)]; INVALID); - check!(" 345", [num!(Ordinal)]; ordinal: 345); - check!(" +345", [num!(Ordinal)]; INVALID); - check!(" -345", [num!(Ordinal)]; INVALID); + // whitespaces + literals + parses("a\n", &[Literal("a"), Space("\n")]); + parses("\tab\n", &[Space("\t"), Literal("ab"), Space("\n")]); + parses( + "ab\tcd\ne", + &[Literal("ab"), Space("\t"), Literal("cd"), Space("\n"), Literal("e")], + ); + parses( + "+1ab\tcd\r\n+,.", + &[Literal("+1ab"), Space("\t"), Literal("cd"), Space("\r\n"), Literal("+,.")], + ); + // whitespace and literals can be intermixed + parses("a\tb", &[Literal("a\tb")]); + parses("a\tb", &[Literal("a"), Space("\t"), Literal("b")]); + } - // various numeric fields - check!("1234 5678", - [num!(Year), num!(IsoYear)]; - year: 1234, isoyear: 5678); - check!("12 34 56 78", - [num!(YearDiv100), num!(YearMod100), num!(IsoYearDiv100), num!(IsoYearMod100)]; - year_div_100: 12, year_mod_100: 34, isoyear_div_100: 56, isoyear_mod_100: 78); - check!("1 2 3 4 5 6", - [num!(Month), num!(Day), num!(WeekFromSun), num!(WeekFromMon), num!(IsoWeek), - num!(NumDaysFromSun)]; - month: 1, day: 2, week_from_sun: 3, week_from_mon: 4, isoweek: 5, weekday: Weekday::Sat); - check!("7 89 01", - [num!(WeekdayFromMon), num!(Ordinal), num!(Hour12)]; - weekday: Weekday::Sun, ordinal: 89, hour_mod_12: 1); - check!("23 45 6 78901234 567890123", - [num!(Hour), num!(Minute), num!(Second), num!(Nanosecond), num!(Timestamp)]; - hour_div_12: 1, hour_mod_12: 11, minute: 45, second: 6, nanosecond: 78_901_234, - timestamp: 567_890_123); + #[test] + fn test_parse_numeric() { + use crate::format::Item::{Literal, Space}; + use crate::format::Numeric::*; - // fixed: month and weekday names - check!("apr", [fix!(ShortMonthName)]; month: 4); - check!("Apr", [fix!(ShortMonthName)]; month: 4); - check!("APR", [fix!(ShortMonthName)]; month: 4); - check!("ApR", [fix!(ShortMonthName)]; month: 4); - check!("April", [fix!(ShortMonthName)]; TOO_LONG); // `Apr` is parsed - check!("A", [fix!(ShortMonthName)]; TOO_SHORT); - check!("Sol", [fix!(ShortMonthName)]; INVALID); - check!("Apr", [fix!(LongMonthName)]; month: 4); - check!("Apri", [fix!(LongMonthName)]; TOO_LONG); // `Apr` is parsed - check!("April", [fix!(LongMonthName)]; month: 4); - check!("Aprill", [fix!(LongMonthName)]; TOO_LONG); - check!("Aprill", [fix!(LongMonthName), lit!("l")]; month: 4); - check!("Aprl", [fix!(LongMonthName), lit!("l")]; month: 4); - check!("April", [fix!(LongMonthName), lit!("il")]; TOO_SHORT); // do not backtrack - check!("thu", [fix!(ShortWeekdayName)]; weekday: Weekday::Thu); - check!("Thu", [fix!(ShortWeekdayName)]; weekday: Weekday::Thu); - check!("THU", [fix!(ShortWeekdayName)]; weekday: Weekday::Thu); - check!("tHu", [fix!(ShortWeekdayName)]; weekday: Weekday::Thu); - check!("Thursday", [fix!(ShortWeekdayName)]; TOO_LONG); // `Thu` is parsed - check!("T", [fix!(ShortWeekdayName)]; TOO_SHORT); - check!("The", [fix!(ShortWeekdayName)]; INVALID); - check!("Nop", [fix!(ShortWeekdayName)]; INVALID); - check!("Thu", [fix!(LongWeekdayName)]; weekday: Weekday::Thu); - check!("Thur", [fix!(LongWeekdayName)]; TOO_LONG); // `Thu` is parsed - check!("Thurs", [fix!(LongWeekdayName)]; TOO_LONG); // ditto - check!("Thursday", [fix!(LongWeekdayName)]; weekday: Weekday::Thu); - check!("Thursdays", [fix!(LongWeekdayName)]; TOO_LONG); - check!("Thursdays", [fix!(LongWeekdayName), lit!("s")]; weekday: Weekday::Thu); - check!("Thus", [fix!(LongWeekdayName), lit!("s")]; weekday: Weekday::Thu); - check!("Thursday", [fix!(LongWeekdayName), lit!("rsday")]; TOO_SHORT); // do not backtrack + // numeric + check("1987", &[num(Year)], parsed!(year: 1987)); + check("1987 ", &[num(Year)], Err(TOO_LONG)); + check("0x12", &[num(Year)], Err(TOO_LONG)); // `0` is parsed + check("x123", &[num(Year)], Err(INVALID)); + check("o123", &[num(Year)], Err(INVALID)); + check("2015", &[num(Year)], parsed!(year: 2015)); + check("0000", &[num(Year)], parsed!(year: 0)); + check("9999", &[num(Year)], parsed!(year: 9999)); + check(" \t987", &[num(Year)], parsed!(year: 987)); + check(" \t987", &[Space(" \t"), num(Year)], parsed!(year: 987)); + check(" \t987🤠", &[Space(" \t"), num(Year), Literal("🤠")], parsed!(year: 987)); + check("987🤠", &[num(Year), Literal("🤠")], parsed!(year: 987)); + check("5", &[num(Year)], parsed!(year: 5)); + check("5\0", &[num(Year)], Err(TOO_LONG)); + check("\x005", &[num(Year)], Err(INVALID)); + check("", &[num(Year)], Err(TOO_SHORT)); + check("12345", &[num(Year), Literal("5")], parsed!(year: 1234)); + check("12345", &[nums(Year), Literal("5")], parsed!(year: 1234)); + check("12345", &[num0(Year), Literal("5")], parsed!(year: 1234)); + check("12341234", &[num(Year), num(Year)], parsed!(year: 1234)); + check("1234 1234", &[num(Year), num(Year)], parsed!(year: 1234)); + check("1234 1234", &[num(Year), Space(" "), num(Year)], parsed!(year: 1234)); + check("1234 1235", &[num(Year), num(Year)], Err(IMPOSSIBLE)); + check("1234 1234", &[num(Year), Literal("x"), num(Year)], Err(INVALID)); + check("1234x1234", &[num(Year), Literal("x"), num(Year)], parsed!(year: 1234)); + check("1234 x 1234", &[num(Year), Literal("x"), num(Year)], Err(INVALID)); + check("1234xx1234", &[num(Year), Literal("x"), num(Year)], Err(INVALID)); + check("1234xx1234", &[num(Year), Literal("xx"), num(Year)], parsed!(year: 1234)); + check( + "1234 x 1234", + &[num(Year), Space(" "), Literal("x"), Space(" "), num(Year)], + parsed!(year: 1234), + ); + check( + "1234 x 1235", + &[num(Year), Space(" "), Literal("x"), Space(" "), Literal("1235")], + parsed!(year: 1234), + ); - // fixed: am/pm - check!("am", [fix!(LowerAmPm)]; hour_div_12: 0); - check!("pm", [fix!(LowerAmPm)]; hour_div_12: 1); - check!("AM", [fix!(LowerAmPm)]; hour_div_12: 0); - check!("PM", [fix!(LowerAmPm)]; hour_div_12: 1); - check!("am", [fix!(UpperAmPm)]; hour_div_12: 0); - check!("pm", [fix!(UpperAmPm)]; hour_div_12: 1); - check!("AM", [fix!(UpperAmPm)]; hour_div_12: 0); - check!("PM", [fix!(UpperAmPm)]; hour_div_12: 1); - check!("Am", [fix!(LowerAmPm)]; hour_div_12: 0); - check!(" Am", [fix!(LowerAmPm)]; INVALID); - check!("ame", [fix!(LowerAmPm)]; TOO_LONG); // `am` is parsed - check!("a", [fix!(LowerAmPm)]; TOO_SHORT); - check!("p", [fix!(LowerAmPm)]; TOO_SHORT); - check!("x", [fix!(LowerAmPm)]; TOO_SHORT); - check!("xx", [fix!(LowerAmPm)]; INVALID); - check!("", [fix!(LowerAmPm)]; TOO_SHORT); + // signed numeric + check("-42", &[num(Year)], parsed!(year: -42)); + check("+42", &[num(Year)], parsed!(year: 42)); + check("-0042", &[num(Year)], parsed!(year: -42)); + check("+0042", &[num(Year)], parsed!(year: 42)); + check("-42195", &[num(Year)], parsed!(year: -42195)); + check("−42195", &[num(Year)], Err(INVALID)); // MINUS SIGN (U+2212) + check("+42195", &[num(Year)], parsed!(year: 42195)); + check(" -42195", &[num(Year)], parsed!(year: -42195)); + check(" +42195", &[num(Year)], parsed!(year: 42195)); + check(" -42195", &[num(Year)], parsed!(year: -42195)); + check(" +42195", &[num(Year)], parsed!(year: 42195)); + check("-42195 ", &[num(Year)], Err(TOO_LONG)); + check("+42195 ", &[num(Year)], Err(TOO_LONG)); + check(" - 42", &[num(Year)], Err(INVALID)); + check(" + 42", &[num(Year)], Err(INVALID)); + check(" -42195", &[Space(" "), num(Year)], parsed!(year: -42195)); + check(" −42195", &[Space(" "), num(Year)], Err(INVALID)); // MINUS SIGN (U+2212) + check(" +42195", &[Space(" "), num(Year)], parsed!(year: 42195)); + check(" - 42", &[Space(" "), num(Year)], Err(INVALID)); + check(" + 42", &[Space(" "), num(Year)], Err(INVALID)); + check("-", &[num(Year)], Err(TOO_SHORT)); + check("+", &[num(Year)], Err(TOO_SHORT)); - // fixed: dot plus nanoseconds - check!("", [fix!(Nanosecond)]; ); // no field set, but not an error - check!("4", [fix!(Nanosecond)]; TOO_LONG); // never consumes `4` - check!("4", [fix!(Nanosecond), num!(Second)]; second: 4); - check!(".0", [fix!(Nanosecond)]; nanosecond: 0); - check!(".4", [fix!(Nanosecond)]; nanosecond: 400_000_000); - check!(".42", [fix!(Nanosecond)]; nanosecond: 420_000_000); - check!(".421", [fix!(Nanosecond)]; nanosecond: 421_000_000); - check!(".42195", [fix!(Nanosecond)]; nanosecond: 421_950_000); - check!(".421950803", [fix!(Nanosecond)]; nanosecond: 421_950_803); - check!(".421950803547", [fix!(Nanosecond)]; nanosecond: 421_950_803); - check!(".000000003547", [fix!(Nanosecond)]; nanosecond: 3); - check!(".000000000547", [fix!(Nanosecond)]; nanosecond: 0); - check!(".", [fix!(Nanosecond)]; TOO_SHORT); - check!(".4x", [fix!(Nanosecond)]; TOO_LONG); - check!(". 4", [fix!(Nanosecond)]; INVALID); - check!(" .4", [fix!(Nanosecond)]; TOO_LONG); // no automatic trimming + // unsigned numeric + check("345", &[num(Ordinal)], parsed!(ordinal: 345)); + check("+345", &[num(Ordinal)], Err(INVALID)); + check("-345", &[num(Ordinal)], Err(INVALID)); + check(" 345", &[num(Ordinal)], parsed!(ordinal: 345)); + check("−345", &[num(Ordinal)], Err(INVALID)); // MINUS SIGN (U+2212) + check("345 ", &[num(Ordinal)], Err(TOO_LONG)); + check(" 345", &[Space(" "), num(Ordinal)], parsed!(ordinal: 345)); + check("345 ", &[num(Ordinal), Space(" ")], parsed!(ordinal: 345)); + check("345🤠 ", &[num(Ordinal), Literal("🤠"), Space(" ")], parsed!(ordinal: 345)); + check("345🤠", &[num(Ordinal)], Err(TOO_LONG)); + check("\u{0363}345", &[num(Ordinal)], Err(INVALID)); + check(" +345", &[num(Ordinal)], Err(INVALID)); + check(" -345", &[num(Ordinal)], Err(INVALID)); + check("\t345", &[Space("\t"), num(Ordinal)], parsed!(ordinal: 345)); + check(" +345", &[Space(" "), num(Ordinal)], Err(INVALID)); + check(" -345", &[Space(" "), num(Ordinal)], Err(INVALID)); - // fixed: nanoseconds without the dot - check!("", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); - check!("0", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); - check!("4", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); - check!("42", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); - check!("421", [internal_fix!(Nanosecond3NoDot)]; nanosecond: 421_000_000); - check!("42143", [internal_fix!(Nanosecond3NoDot), num!(Second)]; nanosecond: 421_000_000, second: 43); - check!("42195", [internal_fix!(Nanosecond3NoDot)]; TOO_LONG); - check!("4x", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); - check!(" 4", [internal_fix!(Nanosecond3NoDot)]; INVALID); - check!(".421", [internal_fix!(Nanosecond3NoDot)]; INVALID); + // various numeric fields + check("1234 5678", &[num(Year), num(IsoYear)], parsed!(year: 1234, isoyear: 5678)); + check("1234 5678", &[num(Year), num(IsoYear)], parsed!(year: 1234, isoyear: 5678)); + check( + "12 34 56 78", + &[num(YearDiv100), num(YearMod100), num(IsoYearDiv100), num(IsoYearMod100)], + parsed!(year_div_100: 12, year_mod_100: 34, isoyear_div_100: 56, isoyear_mod_100: 78), + ); + check( + "1 1 2 3 4 5", + &[ + num(Quarter), + num(Month), + num(Day), + num(WeekFromSun), + num(NumDaysFromSun), + num(IsoWeek), + ], + parsed!(quarter: 1, month: 1, day: 2, week_from_sun: 3, weekday: Weekday::Thu, isoweek: 5), + ); + check( + "6 7 89 01", + &[num(WeekFromMon), num(WeekdayFromMon), num(Ordinal), num(Hour12)], + parsed!(week_from_mon: 6, weekday: Weekday::Sun, ordinal: 89, hour_mod_12: 1), + ); + check( + "23 45 6 78901234 567890123", + &[num(Hour), num(Minute), num(Second), num(Nanosecond), num(Timestamp)], + parsed!(hour_div_12: 1, hour_mod_12: 11, minute: 45, second: 6, nanosecond: 78_901_234, timestamp: 567_890_123), + ); + } - check!("", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); - check!("0", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); - check!("42195", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); - check!("421950", [internal_fix!(Nanosecond6NoDot)]; nanosecond: 421_950_000); - check!("000003", [internal_fix!(Nanosecond6NoDot)]; nanosecond: 3000); - check!("000000", [internal_fix!(Nanosecond6NoDot)]; nanosecond: 0); - check!("4x", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); - check!(" 4", [internal_fix!(Nanosecond6NoDot)]; INVALID); - check!(".42100", [internal_fix!(Nanosecond6NoDot)]; INVALID); + #[test] + fn test_parse_fixed() { + use crate::format::Fixed::*; + use crate::format::Item::{Literal, Space}; - check!("", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT); - check!("42195", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT); - check!("421950803", [internal_fix!(Nanosecond9NoDot)]; nanosecond: 421_950_803); - check!("000000003", [internal_fix!(Nanosecond9NoDot)]; nanosecond: 3); - check!("42195080354", [internal_fix!(Nanosecond9NoDot), num!(Second)]; nanosecond: 421_950_803, second: 54); // don't skip digits that come after the 9 - check!("421950803547", [internal_fix!(Nanosecond9NoDot)]; TOO_LONG); - check!("000000000", [internal_fix!(Nanosecond9NoDot)]; nanosecond: 0); - check!("00000000x", [internal_fix!(Nanosecond9NoDot)]; INVALID); - check!(" 4", [internal_fix!(Nanosecond9NoDot)]; INVALID); - check!(".42100000", [internal_fix!(Nanosecond9NoDot)]; INVALID); + // fixed: month and weekday names + check("apr", &[fixed(ShortMonthName)], parsed!(month: 4)); + check("Apr", &[fixed(ShortMonthName)], parsed!(month: 4)); + check("APR", &[fixed(ShortMonthName)], parsed!(month: 4)); + check("ApR", &[fixed(ShortMonthName)], parsed!(month: 4)); + check("\u{0363}APR", &[fixed(ShortMonthName)], Err(INVALID)); + check("April", &[fixed(ShortMonthName)], Err(TOO_LONG)); // `Apr` is parsed + check("A", &[fixed(ShortMonthName)], Err(TOO_SHORT)); + check("Sol", &[fixed(ShortMonthName)], Err(INVALID)); + check("Apr", &[fixed(LongMonthName)], parsed!(month: 4)); + check("Apri", &[fixed(LongMonthName)], Err(TOO_LONG)); // `Apr` is parsed + check("April", &[fixed(LongMonthName)], parsed!(month: 4)); + check("Aprill", &[fixed(LongMonthName)], Err(TOO_LONG)); + check("Aprill", &[fixed(LongMonthName), Literal("l")], parsed!(month: 4)); + check("Aprl", &[fixed(LongMonthName), Literal("l")], parsed!(month: 4)); + check("April", &[fixed(LongMonthName), Literal("il")], Err(TOO_SHORT)); // do not backtrack + check("thu", &[fixed(ShortWeekdayName)], parsed!(weekday: Weekday::Thu)); + check("Thu", &[fixed(ShortWeekdayName)], parsed!(weekday: Weekday::Thu)); + check("THU", &[fixed(ShortWeekdayName)], parsed!(weekday: Weekday::Thu)); + check("tHu", &[fixed(ShortWeekdayName)], parsed!(weekday: Weekday::Thu)); + check("Thursday", &[fixed(ShortWeekdayName)], Err(TOO_LONG)); // `Thu` is parsed + check("T", &[fixed(ShortWeekdayName)], Err(TOO_SHORT)); + check("The", &[fixed(ShortWeekdayName)], Err(INVALID)); + check("Nop", &[fixed(ShortWeekdayName)], Err(INVALID)); + check("Thu", &[fixed(LongWeekdayName)], parsed!(weekday: Weekday::Thu)); + check("Thur", &[fixed(LongWeekdayName)], Err(TOO_LONG)); // `Thu` is parsed + check("Thurs", &[fixed(LongWeekdayName)], Err(TOO_LONG)); // `Thu` is parsed + check("Thursday", &[fixed(LongWeekdayName)], parsed!(weekday: Weekday::Thu)); + check("Thursdays", &[fixed(LongWeekdayName)], Err(TOO_LONG)); + check("Thursdays", &[fixed(LongWeekdayName), Literal("s")], parsed!(weekday: Weekday::Thu)); + check("Thus", &[fixed(LongWeekdayName), Literal("s")], parsed!(weekday: Weekday::Thu)); + check("Thursday", &[fixed(LongWeekdayName), Literal("rsday")], Err(TOO_SHORT)); // do not backtrack - // fixed: timezone offsets - check!("+00:00", [fix!(TimezoneOffset)]; offset: 0); - check!("-00:00", [fix!(TimezoneOffset)]; offset: 0); - check!("+00:01", [fix!(TimezoneOffset)]; offset: 60); - check!("-00:01", [fix!(TimezoneOffset)]; offset: -60); - check!("+00:30", [fix!(TimezoneOffset)]; offset: 30 * 60); - check!("-00:30", [fix!(TimezoneOffset)]; offset: -30 * 60); - check!("+04:56", [fix!(TimezoneOffset)]; offset: 296 * 60); - check!("-04:56", [fix!(TimezoneOffset)]; offset: -296 * 60); - check!("+24:00", [fix!(TimezoneOffset)]; offset: 24 * 60 * 60); - check!("-24:00", [fix!(TimezoneOffset)]; offset: -24 * 60 * 60); - check!("+99:59", [fix!(TimezoneOffset)]; offset: (100 * 60 - 1) * 60); - check!("-99:59", [fix!(TimezoneOffset)]; offset: -(100 * 60 - 1) * 60); - check!("+00:59", [fix!(TimezoneOffset)]; offset: 59 * 60); - check!("+00:60", [fix!(TimezoneOffset)]; OUT_OF_RANGE); - check!("+00:99", [fix!(TimezoneOffset)]; OUT_OF_RANGE); - check!("#12:34", [fix!(TimezoneOffset)]; INVALID); - check!("12:34", [fix!(TimezoneOffset)]; INVALID); - check!("+12:34 ", [fix!(TimezoneOffset)]; TOO_LONG); - check!(" +12:34", [fix!(TimezoneOffset)]; offset: 754 * 60); - check!("\t -12:34", [fix!(TimezoneOffset)]; offset: -754 * 60); - check!("", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+1", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+12", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+123", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+1234", [fix!(TimezoneOffset)]; offset: 754 * 60); - check!("+12345", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12345", [fix!(TimezoneOffset), num!(Day)]; offset: 754 * 60, day: 5); - check!("Z", [fix!(TimezoneOffset)]; INVALID); - check!("z", [fix!(TimezoneOffset)]; INVALID); - check!("Z", [fix!(TimezoneOffsetZ)]; offset: 0); - check!("z", [fix!(TimezoneOffsetZ)]; offset: 0); - check!("Y", [fix!(TimezoneOffsetZ)]; INVALID); - check!("Zulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 0); - check!("zulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 0); - check!("+1234ulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 754 * 60); - check!("+12:34ulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 754 * 60); - check!("Z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); - check!("z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); - check!("+12:00", [internal_fix!(TimezoneOffsetPermissive)]; offset: 12 * 60 * 60); - check!("+12", [internal_fix!(TimezoneOffsetPermissive)]; offset: 12 * 60 * 60); - check!("CEST 5", [fix!(TimezoneName), lit!(" "), num!(Day)]; day: 5); + // fixed: am/pm + check("am", &[fixed(LowerAmPm)], parsed!(hour_div_12: 0)); + check("pm", &[fixed(LowerAmPm)], parsed!(hour_div_12: 1)); + check("AM", &[fixed(LowerAmPm)], parsed!(hour_div_12: 0)); + check("PM", &[fixed(LowerAmPm)], parsed!(hour_div_12: 1)); + check("am", &[fixed(UpperAmPm)], parsed!(hour_div_12: 0)); + check("pm", &[fixed(UpperAmPm)], parsed!(hour_div_12: 1)); + check("AM", &[fixed(UpperAmPm)], parsed!(hour_div_12: 0)); + check("PM", &[fixed(UpperAmPm)], parsed!(hour_div_12: 1)); + check("Am", &[fixed(LowerAmPm)], parsed!(hour_div_12: 0)); + check(" Am", &[Space(" "), fixed(LowerAmPm)], parsed!(hour_div_12: 0)); + check("Am🤠", &[fixed(LowerAmPm), Literal("🤠")], parsed!(hour_div_12: 0)); + check("🤠Am", &[Literal("🤠"), fixed(LowerAmPm)], parsed!(hour_div_12: 0)); + check("\u{0363}am", &[fixed(LowerAmPm)], Err(INVALID)); + check("\u{0360}am", &[fixed(LowerAmPm)], Err(INVALID)); + check(" Am", &[fixed(LowerAmPm)], Err(INVALID)); + check("Am ", &[fixed(LowerAmPm)], Err(TOO_LONG)); + check("a.m.", &[fixed(LowerAmPm)], Err(INVALID)); + check("A.M.", &[fixed(LowerAmPm)], Err(INVALID)); + check("ame", &[fixed(LowerAmPm)], Err(TOO_LONG)); // `am` is parsed + check("a", &[fixed(LowerAmPm)], Err(TOO_SHORT)); + check("p", &[fixed(LowerAmPm)], Err(TOO_SHORT)); + check("x", &[fixed(LowerAmPm)], Err(TOO_SHORT)); + check("xx", &[fixed(LowerAmPm)], Err(INVALID)); + check("", &[fixed(LowerAmPm)], Err(TOO_SHORT)); + } - // some practical examples - check!("2015-02-04T14:37:05+09:00", - [num!(Year), lit!("-"), num!(Month), lit!("-"), num!(Day), lit!("T"), - num!(Hour), lit!(":"), num!(Minute), lit!(":"), num!(Second), fix!(TimezoneOffset)]; - year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, - minute: 37, second: 5, offset: 32400); - check!("20150204143705567", - [num!(Year), num!(Month), num!(Day), - num!(Hour), num!(Minute), num!(Second), internal_fix!(Nanosecond3NoDot)]; - year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, - minute: 37, second: 5, nanosecond: 567000000); - check!("Mon, 10 Jun 2013 09:32:37 GMT", - [fix!(ShortWeekdayName), lit!(","), sp!(" "), num!(Day), sp!(" "), - fix!(ShortMonthName), sp!(" "), num!(Year), sp!(" "), num!(Hour), lit!(":"), - num!(Minute), lit!(":"), num!(Second), sp!(" "), lit!("GMT")]; - year: 2013, month: 6, day: 10, weekday: Weekday::Mon, - hour_div_12: 0, hour_mod_12: 9, minute: 32, second: 37); - check!("Sun Aug 02 13:39:15 CEST 2020", - [fix!(ShortWeekdayName), sp!(" "), fix!(ShortMonthName), sp!(" "), - num!(Day), sp!(" "), num!(Hour), lit!(":"), num!(Minute), lit!(":"), - num!(Second), sp!(" "), fix!(TimezoneName), sp!(" "), num!(Year)]; - year: 2020, month: 8, day: 2, weekday: Weekday::Sun, - hour_div_12: 1, hour_mod_12: 1, minute: 39, second: 15); - check!("20060102150405", - [num!(Year), num!(Month), num!(Day), num!(Hour), num!(Minute), num!(Second)]; - year: 2006, month: 1, day: 2, hour_div_12: 1, hour_mod_12: 3, minute: 4, second: 5); - check!("3:14PM", - [num!(Hour12), lit!(":"), num!(Minute), fix!(LowerAmPm)]; - hour_div_12: 1, hour_mod_12: 3, minute: 14); - check!("12345678901234.56789", - [num!(Timestamp), lit!("."), num!(Nanosecond)]; - nanosecond: 56_789, timestamp: 12_345_678_901_234); - check!("12345678901234.56789", - [num!(Timestamp), fix!(Nanosecond)]; - nanosecond: 567_890_000, timestamp: 12_345_678_901_234); -} + #[test] + fn test_parse_fixed_nanosecond() { + use crate::format::Fixed::Nanosecond; + use crate::format::InternalInternal::*; + use crate::format::Item::Literal; + use crate::format::Numeric::Second; -#[cfg(test)] -#[test] -fn test_rfc2822() { - use super::NOT_ENOUGH; - use super::*; - use offset::FixedOffset; - use DateTime; + // fixed: dot plus nanoseconds + check("", &[fixed(Nanosecond)], parsed!()); // no field set, but not an error + check(".", &[fixed(Nanosecond)], Err(TOO_SHORT)); + check("4", &[fixed(Nanosecond)], Err(TOO_LONG)); // never consumes `4` + check("4", &[fixed(Nanosecond), num(Second)], parsed!(second: 4)); + check(".0", &[fixed(Nanosecond)], parsed!(nanosecond: 0)); + check(".4", &[fixed(Nanosecond)], parsed!(nanosecond: 400_000_000)); + check(".42", &[fixed(Nanosecond)], parsed!(nanosecond: 420_000_000)); + check(".421", &[fixed(Nanosecond)], parsed!(nanosecond: 421_000_000)); + check(".42195", &[fixed(Nanosecond)], parsed!(nanosecond: 421_950_000)); + check(".421951", &[fixed(Nanosecond)], parsed!(nanosecond: 421_951_000)); + check(".4219512", &[fixed(Nanosecond)], parsed!(nanosecond: 421_951_200)); + check(".42195123", &[fixed(Nanosecond)], parsed!(nanosecond: 421_951_230)); + check(".421950803", &[fixed(Nanosecond)], parsed!(nanosecond: 421_950_803)); + check(".4219508035", &[fixed(Nanosecond)], parsed!(nanosecond: 421_950_803)); + check(".42195080354", &[fixed(Nanosecond)], parsed!(nanosecond: 421_950_803)); + check(".421950803547", &[fixed(Nanosecond)], parsed!(nanosecond: 421_950_803)); + check(".000000003", &[fixed(Nanosecond)], parsed!(nanosecond: 3)); + check(".0000000031", &[fixed(Nanosecond)], parsed!(nanosecond: 3)); + check(".0000000035", &[fixed(Nanosecond)], parsed!(nanosecond: 3)); + check(".000000003547", &[fixed(Nanosecond)], parsed!(nanosecond: 3)); + check(".0000000009", &[fixed(Nanosecond)], parsed!(nanosecond: 0)); + check(".000000000547", &[fixed(Nanosecond)], parsed!(nanosecond: 0)); + check(".0000000009999999999999999999999999", &[fixed(Nanosecond)], parsed!(nanosecond: 0)); + check(".4🤠", &[fixed(Nanosecond), Literal("🤠")], parsed!(nanosecond: 400_000_000)); + check(".4x", &[fixed(Nanosecond)], Err(TOO_LONG)); + check(". 4", &[fixed(Nanosecond)], Err(INVALID)); + check(" .4", &[fixed(Nanosecond)], Err(TOO_LONG)); // no automatic trimming - // Test data - (input, Ok(expected result after parse and format) or Err(error code)) - let testdates = [ - ("Tue, 20 Jan 2015 17:35:20 -0800", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // normal case - ("Fri, 2 Jan 2015 17:35:20 -0800", Ok("Fri, 02 Jan 2015 17:35:20 -0800")), // folding whitespace - ("Fri, 02 Jan 2015 17:35:20 -0800", Ok("Fri, 02 Jan 2015 17:35:20 -0800")), // leading zero - ("20 Jan 2015 17:35:20 -0800", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // no day of week - ("20 JAN 2015 17:35:20 -0800", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // upper case month - ("Tue, 20 Jan 2015 17:35 -0800", Ok("Tue, 20 Jan 2015 17:35:00 -0800")), // no second - ("11 Sep 2001 09:45:00 EST", Ok("Tue, 11 Sep 2001 09:45:00 -0500")), - ("30 Feb 2015 17:35:20 -0800", Err(OUT_OF_RANGE)), // bad day of month - ("Tue, 20 Jan 2015", Err(TOO_SHORT)), // omitted fields - ("Tue, 20 Avr 2015 17:35:20 -0800", Err(INVALID)), // bad month name - ("Tue, 20 Jan 2015 25:35:20 -0800", Err(OUT_OF_RANGE)), // bad hour - ("Tue, 20 Jan 2015 7:35:20 -0800", Err(INVALID)), // bad # of digits in hour - ("Tue, 20 Jan 2015 17:65:20 -0800", Err(OUT_OF_RANGE)), // bad minute - ("Tue, 20 Jan 2015 17:35:90 -0800", Err(OUT_OF_RANGE)), // bad second - ("Tue, 20 Jan 2015 17:35:20 -0890", Err(OUT_OF_RANGE)), // bad offset - ("6 Jun 1944 04:00:00Z", Err(INVALID)), // bad offset (zulu not allowed) - ("Tue, 20 Jan 2015 17:35:20 HAS", Err(NOT_ENOUGH)), // bad named time zone - ]; + // fixed: nanoseconds without the dot + check("", &[internal_fixed(Nanosecond3NoDot)], Err(TOO_SHORT)); + check(".", &[internal_fixed(Nanosecond3NoDot)], Err(TOO_SHORT)); + check("0", &[internal_fixed(Nanosecond3NoDot)], Err(TOO_SHORT)); + check("4", &[internal_fixed(Nanosecond3NoDot)], Err(TOO_SHORT)); + check("42", &[internal_fixed(Nanosecond3NoDot)], Err(TOO_SHORT)); + check("421", &[internal_fixed(Nanosecond3NoDot)], parsed!(nanosecond: 421_000_000)); + check("4210", &[internal_fixed(Nanosecond3NoDot)], Err(TOO_LONG)); + check( + "42143", + &[internal_fixed(Nanosecond3NoDot), num(Second)], + parsed!(nanosecond: 421_000_000, second: 43), + ); + check( + "421🤠", + &[internal_fixed(Nanosecond3NoDot), Literal("🤠")], + parsed!(nanosecond: 421_000_000), + ); + check( + "🤠421", + &[Literal("🤠"), internal_fixed(Nanosecond3NoDot)], + parsed!(nanosecond: 421_000_000), + ); + check("42195", &[internal_fixed(Nanosecond3NoDot)], Err(TOO_LONG)); + check("123456789", &[internal_fixed(Nanosecond3NoDot)], Err(TOO_LONG)); + check("4x", &[internal_fixed(Nanosecond3NoDot)], Err(TOO_SHORT)); + check(" 4", &[internal_fixed(Nanosecond3NoDot)], Err(INVALID)); + check(".421", &[internal_fixed(Nanosecond3NoDot)], Err(INVALID)); - fn rfc2822_to_datetime(date: &str) -> ParseResult> { + check("", &[internal_fixed(Nanosecond6NoDot)], Err(TOO_SHORT)); + check(".", &[internal_fixed(Nanosecond6NoDot)], Err(TOO_SHORT)); + check("0", &[internal_fixed(Nanosecond6NoDot)], Err(TOO_SHORT)); + check("1234", &[internal_fixed(Nanosecond6NoDot)], Err(TOO_SHORT)); + check("12345", &[internal_fixed(Nanosecond6NoDot)], Err(TOO_SHORT)); + check("421950", &[internal_fixed(Nanosecond6NoDot)], parsed!(nanosecond: 421_950_000)); + check("000003", &[internal_fixed(Nanosecond6NoDot)], parsed!(nanosecond: 3000)); + check("000000", &[internal_fixed(Nanosecond6NoDot)], parsed!(nanosecond: 0)); + check("1234567", &[internal_fixed(Nanosecond6NoDot)], Err(TOO_LONG)); + check("123456789", &[internal_fixed(Nanosecond6NoDot)], Err(TOO_LONG)); + check("4x", &[internal_fixed(Nanosecond6NoDot)], Err(TOO_SHORT)); + check(" 4", &[internal_fixed(Nanosecond6NoDot)], Err(INVALID)); + check(".42100", &[internal_fixed(Nanosecond6NoDot)], Err(INVALID)); + + check("", &[internal_fixed(Nanosecond9NoDot)], Err(TOO_SHORT)); + check(".", &[internal_fixed(Nanosecond9NoDot)], Err(TOO_SHORT)); + check("42195", &[internal_fixed(Nanosecond9NoDot)], Err(TOO_SHORT)); + check("12345678", &[internal_fixed(Nanosecond9NoDot)], Err(TOO_SHORT)); + check("421950803", &[internal_fixed(Nanosecond9NoDot)], parsed!(nanosecond: 421_950_803)); + check("000000003", &[internal_fixed(Nanosecond9NoDot)], parsed!(nanosecond: 3)); + check( + "42195080354", + &[internal_fixed(Nanosecond9NoDot), num(Second)], + parsed!(nanosecond: 421_950_803, second: 54), + ); // don't skip digits that come after the 9 + check("1234567890", &[internal_fixed(Nanosecond9NoDot)], Err(TOO_LONG)); + check("000000000", &[internal_fixed(Nanosecond9NoDot)], parsed!(nanosecond: 0)); + check("00000000x", &[internal_fixed(Nanosecond9NoDot)], Err(INVALID)); + check(" 4", &[internal_fixed(Nanosecond9NoDot)], Err(INVALID)); + check(".42100000", &[internal_fixed(Nanosecond9NoDot)], Err(INVALID)); + } + + #[test] + fn test_parse_fixed_timezone_offset() { + use crate::format::Fixed::*; + use crate::format::InternalInternal::*; + use crate::format::Item::Literal; + + // TimezoneOffset + check("1", &[fixed(TimezoneOffset)], Err(INVALID)); + check("12", &[fixed(TimezoneOffset)], Err(INVALID)); + check("123", &[fixed(TimezoneOffset)], Err(INVALID)); + check("1234", &[fixed(TimezoneOffset)], Err(INVALID)); + check("12345", &[fixed(TimezoneOffset)], Err(INVALID)); + check("123456", &[fixed(TimezoneOffset)], Err(INVALID)); + check("1234567", &[fixed(TimezoneOffset)], Err(INVALID)); + check("+1", &[fixed(TimezoneOffset)], Err(TOO_SHORT)); + check("+12", &[fixed(TimezoneOffset)], Err(TOO_SHORT)); + check("+123", &[fixed(TimezoneOffset)], Err(TOO_SHORT)); + check("+1234", &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); + check("+12345", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+123456", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+1234567", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+12345678", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+12:", &[fixed(TimezoneOffset)], Err(TOO_SHORT)); + check("+12:3", &[fixed(TimezoneOffset)], Err(TOO_SHORT)); + check("+12:34", &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); + check("-12:34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); + check("−12:34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check("+12:34:", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+12:34:5", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+12:34:56", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+12:34:56:", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+12 34", &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); + check("+12 34", &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); + check("12:34", &[fixed(TimezoneOffset)], Err(INVALID)); + check("12:34:56", &[fixed(TimezoneOffset)], Err(INVALID)); + check("+12::34", &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); + check("+12: :34", &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); + check("+12:::34", &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); + check("+12::::34", &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); + check("+12::34", &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); + check("+12:34:56", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+12:3456", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+1234:56", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+1234:567", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+00:00", &[fixed(TimezoneOffset)], parsed!(offset: 0)); + check("-00:00", &[fixed(TimezoneOffset)], parsed!(offset: 0)); + check("−00:00", &[fixed(TimezoneOffset)], parsed!(offset: 0)); // MINUS SIGN (U+2212) + check("+00:01", &[fixed(TimezoneOffset)], parsed!(offset: 60)); + check("-00:01", &[fixed(TimezoneOffset)], parsed!(offset: -60)); + check("+00:30", &[fixed(TimezoneOffset)], parsed!(offset: 1_800)); + check("-00:30", &[fixed(TimezoneOffset)], parsed!(offset: -1_800)); + check("+24:00", &[fixed(TimezoneOffset)], parsed!(offset: 86_400)); + check("-24:00", &[fixed(TimezoneOffset)], parsed!(offset: -86_400)); + check("−24:00", &[fixed(TimezoneOffset)], parsed!(offset: -86_400)); // MINUS SIGN (U+2212) + check("+99:59", &[fixed(TimezoneOffset)], parsed!(offset: 359_940)); + check("-99:59", &[fixed(TimezoneOffset)], parsed!(offset: -359_940)); + check("+00:60", &[fixed(TimezoneOffset)], Err(OUT_OF_RANGE)); + check("+00:99", &[fixed(TimezoneOffset)], Err(OUT_OF_RANGE)); + check("#12:34", &[fixed(TimezoneOffset)], Err(INVALID)); + check("+12:34 ", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+12 34 ", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check(" +12:34", &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); + check(" -12:34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); + check(" −12:34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check(" +12:34", &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); + check(" -12:34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); + check("\t -12:34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); + check("-12: 34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); + check("-12 :34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); + check("-12 : 34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); + check("-12 : 34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); + check("-12 : 34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); + check("-12: 34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); + check("-12 :34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); + check("-12 : 34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); + check("12:34 ", &[fixed(TimezoneOffset)], Err(INVALID)); + check(" 12:34", &[fixed(TimezoneOffset)], Err(INVALID)); + check("", &[fixed(TimezoneOffset)], Err(TOO_SHORT)); + check("+", &[fixed(TimezoneOffset)], Err(TOO_SHORT)); + check( + "+12345", + &[fixed(TimezoneOffset), num(Numeric::Day)], + parsed!(offset: 45_240, day: 5), + ); + check( + "+12:345", + &[fixed(TimezoneOffset), num(Numeric::Day)], + parsed!(offset: 45_240, day: 5), + ); + check("+12:34:", &[fixed(TimezoneOffset), Literal(":")], parsed!(offset: 45_240)); + check("Z12:34", &[fixed(TimezoneOffset)], Err(INVALID)); + check("X12:34", &[fixed(TimezoneOffset)], Err(INVALID)); + check("Z+12:34", &[fixed(TimezoneOffset)], Err(INVALID)); + check("X+12:34", &[fixed(TimezoneOffset)], Err(INVALID)); + check("X−12:34", &[fixed(TimezoneOffset)], Err(INVALID)); // MINUS SIGN (U+2212) + check("🤠+12:34", &[fixed(TimezoneOffset)], Err(INVALID)); + check("+12:34🤠", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+12:🤠34", &[fixed(TimezoneOffset)], Err(INVALID)); + check("+1234🤠", &[fixed(TimezoneOffset), Literal("🤠")], parsed!(offset: 45_240)); + check("-1234🤠", &[fixed(TimezoneOffset), Literal("🤠")], parsed!(offset: -45_240)); + check("−1234🤠", &[fixed(TimezoneOffset), Literal("🤠")], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check("+12:34🤠", &[fixed(TimezoneOffset), Literal("🤠")], parsed!(offset: 45_240)); + check("-12:34🤠", &[fixed(TimezoneOffset), Literal("🤠")], parsed!(offset: -45_240)); + check("−12:34🤠", &[fixed(TimezoneOffset), Literal("🤠")], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check("🤠+12:34", &[Literal("🤠"), fixed(TimezoneOffset)], parsed!(offset: 45_240)); + check("Z", &[fixed(TimezoneOffset)], Err(INVALID)); + check("A", &[fixed(TimezoneOffset)], Err(INVALID)); + check("PST", &[fixed(TimezoneOffset)], Err(INVALID)); + check("#Z", &[fixed(TimezoneOffset)], Err(INVALID)); + check(":Z", &[fixed(TimezoneOffset)], Err(INVALID)); + check("+Z", &[fixed(TimezoneOffset)], Err(TOO_SHORT)); + check("+:Z", &[fixed(TimezoneOffset)], Err(INVALID)); + check("+Z:", &[fixed(TimezoneOffset)], Err(INVALID)); + check("z", &[fixed(TimezoneOffset)], Err(INVALID)); + check(" :Z", &[fixed(TimezoneOffset)], Err(INVALID)); + check(" Z", &[fixed(TimezoneOffset)], Err(INVALID)); + check(" z", &[fixed(TimezoneOffset)], Err(INVALID)); + + // TimezoneOffsetColon + check("1", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("12", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("123", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("1234", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("12345", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("123456", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("1234567", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("12345678", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("+1", &[fixed(TimezoneOffsetColon)], Err(TOO_SHORT)); + check("+12", &[fixed(TimezoneOffsetColon)], Err(TOO_SHORT)); + check("+123", &[fixed(TimezoneOffsetColon)], Err(TOO_SHORT)); + check("+1234", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("-1234", &[fixed(TimezoneOffsetColon)], parsed!(offset: -45_240)); + check("−1234", &[fixed(TimezoneOffsetColon)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check("+12345", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check("+123456", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check("+1234567", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check("+12345678", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check("1:", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("12:", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("12:3", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("12:34", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("12:34:", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("12:34:5", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("12:34:56", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("+1:", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("+12:", &[fixed(TimezoneOffsetColon)], Err(TOO_SHORT)); + check("+12:3", &[fixed(TimezoneOffsetColon)], Err(TOO_SHORT)); + check("+12:34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("-12:34", &[fixed(TimezoneOffsetColon)], parsed!(offset: -45_240)); + check("−12:34", &[fixed(TimezoneOffsetColon)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check("+12:34:", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check("+12:34:5", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check("+12:34:56", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check("+12:34:56:", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check("+12:34:56:7", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check("+12:34:56:78", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check("+12:3456", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check("+1234:56", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check("−12:34", &[fixed(TimezoneOffsetColon)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check("−12 : 34", &[fixed(TimezoneOffsetColon)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check("+12 :34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("+12: 34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("+12 34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("+12: 34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("+12 :34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("+12 : 34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("-12 : 34", &[fixed(TimezoneOffsetColon)], parsed!(offset: -45_240)); + check("+12 : 34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("+12 : 34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("+12 : 34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("+12::34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("+12: :34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("+12:::34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("+12::::34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("+12::34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("#1234", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("#12:34", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("+12:34 ", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check(" +12:34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("\t+12:34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("\t\t+12:34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("12:34 ", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check(" 12:34", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("", &[fixed(TimezoneOffsetColon)], Err(TOO_SHORT)); + check("+", &[fixed(TimezoneOffsetColon)], Err(TOO_SHORT)); + check(":", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check( + "+12345", + &[fixed(TimezoneOffsetColon), num(Numeric::Day)], + parsed!(offset: 45_240, day: 5), + ); + check( + "+12:345", + &[fixed(TimezoneOffsetColon), num(Numeric::Day)], + parsed!(offset: 45_240, day: 5), + ); + check("+12:34:", &[fixed(TimezoneOffsetColon), Literal(":")], parsed!(offset: 45_240)); + check("Z", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("A", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("PST", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("#Z", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check(":Z", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("+Z", &[fixed(TimezoneOffsetColon)], Err(TOO_SHORT)); + check("+:Z", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("+Z:", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("z", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check(" :Z", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check(" Z", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check(" z", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + // testing `TimezoneOffsetColon` also tests same path as `TimezoneOffsetDoubleColon` + // and `TimezoneOffsetTripleColon` for function `parse_internal`. + // No need for separate tests for `TimezoneOffsetDoubleColon` and + // `TimezoneOffsetTripleColon`. + + // TimezoneOffsetZ + check("1", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("12", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("123", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("1234", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("12345", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("123456", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("1234567", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("12345678", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("+1", &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); + check("+12", &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); + check("+123", &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); + check("+1234", &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); + check("-1234", &[fixed(TimezoneOffsetZ)], parsed!(offset: -45_240)); + check("−1234", &[fixed(TimezoneOffsetZ)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check("+12345", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("+123456", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("+1234567", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("+12345678", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("1:", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("12:", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("12:3", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("12:34", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("12:34:", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("12:34:5", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("12:34:56", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("+1:", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("+12:", &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); + check("+12:3", &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); + check("+12:34", &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); + check("-12:34", &[fixed(TimezoneOffsetZ)], parsed!(offset: -45_240)); + check("−12:34", &[fixed(TimezoneOffsetZ)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check("+12:34:", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("+12:34:5", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("+12:34:56", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("+12:34:56:", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("+12:34:56:7", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("+12:34:56:78", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("+12::34", &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); + check("+12:3456", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("+1234:56", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("+12 34", &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); + check("+12 34", &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); + check("+12: 34", &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); + check("+12 :34", &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); + check("+12 : 34", &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); + check("+12 : 34", &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); + check("+12 : 34", &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); + check("+12 : 34", &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); + check("12:34 ", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check(" 12:34", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("+12:34 ", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("+12 34 ", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check(" +12:34", &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); + check( + "+12345", + &[fixed(TimezoneOffsetZ), num(Numeric::Day)], + parsed!(offset: 45_240, day: 5), + ); + check( + "+12:345", + &[fixed(TimezoneOffsetZ), num(Numeric::Day)], + parsed!(offset: 45_240, day: 5), + ); + check("+12:34:", &[fixed(TimezoneOffsetZ), Literal(":")], parsed!(offset: 45_240)); + check("Z12:34", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("X12:34", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("Z", &[fixed(TimezoneOffsetZ)], parsed!(offset: 0)); + check("z", &[fixed(TimezoneOffsetZ)], parsed!(offset: 0)); + check(" Z", &[fixed(TimezoneOffsetZ)], parsed!(offset: 0)); + check(" z", &[fixed(TimezoneOffsetZ)], parsed!(offset: 0)); + check("\u{0363}Z", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("Z ", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("A", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("PST", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("#Z", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check(":Z", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check(":z", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("+Z", &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); + check("-Z", &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); + check("+A", &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); + check("+🙃", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("+Z:", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check(" :Z", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check(" +Z", &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); + check(" -Z", &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); + check("+:Z", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("Y", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("Zulu", &[fixed(TimezoneOffsetZ), Literal("ulu")], parsed!(offset: 0)); + check("zulu", &[fixed(TimezoneOffsetZ), Literal("ulu")], parsed!(offset: 0)); + check("+1234ulu", &[fixed(TimezoneOffsetZ), Literal("ulu")], parsed!(offset: 45_240)); + check("+12:34ulu", &[fixed(TimezoneOffsetZ), Literal("ulu")], parsed!(offset: 45_240)); + // Testing `TimezoneOffsetZ` also tests same path as `TimezoneOffsetColonZ` + // in function `parse_internal`. + // No need for separate tests for `TimezoneOffsetColonZ`. + + // TimezoneOffsetPermissive + check("1", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("12", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("123", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("1234", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("12345", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("123456", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("1234567", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("12345678", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("+1", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_SHORT)); + check("+12", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 43_200)); + check("+123", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_SHORT)); + check("+1234", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("-1234", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: -45_240)); + check("−1234", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check("+12345", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check("+123456", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check("+1234567", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check("+12345678", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check("1:", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("12:", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("12:3", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("12:34", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("12:34:", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("12:34:5", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("12:34:56", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("+1:", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("+12:", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 43_200)); + check("+12:3", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_SHORT)); + check("+12:34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("-12:34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: -45_240)); + check("−12:34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check("+12:34:", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check("+12:34:5", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check("+12:34:56", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check("+12:34:56:", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check("+12:34:56:7", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check("+12:34:56:78", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check("+12 34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12 34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12 :34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12: 34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12 : 34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12 :34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12: 34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12 : 34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12::34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12 ::34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12: :34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12:: 34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12 ::34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12: :34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12:: 34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12:::34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12::::34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("12:34 ", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check(" 12:34", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("+12:34 ", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check(" +12:34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check(" -12:34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: -45_240)); + check(" −12:34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check( + "+12345", + &[internal_fixed(TimezoneOffsetPermissive), num(Numeric::Day)], + parsed!(offset: 45_240, day: 5), + ); + check( + "+12:345", + &[internal_fixed(TimezoneOffsetPermissive), num(Numeric::Day)], + parsed!(offset: 45_240, day: 5), + ); + check( + "+12:34:", + &[internal_fixed(TimezoneOffsetPermissive), Literal(":")], + parsed!(offset: 45_240), + ); + check("🤠+12:34", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("+12:34🤠", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check("+12:🤠34", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check( + "+12:34🤠", + &[internal_fixed(TimezoneOffsetPermissive), Literal("🤠")], + parsed!(offset: 45_240), + ); + check( + "🤠+12:34", + &[Literal("🤠"), internal_fixed(TimezoneOffsetPermissive)], + parsed!(offset: 45_240), + ); + check("Z", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 0)); + check("A", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("PST", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("z", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 0)); + check(" Z", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 0)); + check(" z", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 0)); + check("Z ", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check("#Z", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check(":Z", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check(":z", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("+Z", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_SHORT)); + check("-Z", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_SHORT)); + check("+A", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_SHORT)); + check("+PST", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("+🙃", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("+Z:", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check(" :Z", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check(" +Z", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_SHORT)); + check(" -Z", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_SHORT)); + check("+:Z", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("Y", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + + // TimezoneName + check("CEST", &[fixed(TimezoneName)], parsed!()); + check("cest", &[fixed(TimezoneName)], parsed!()); // lowercase + check("XXXXXXXX", &[fixed(TimezoneName)], parsed!()); // not a real timezone name + check("!!!!", &[fixed(TimezoneName)], parsed!()); // not a real timezone name! + check("CEST 5", &[fixed(TimezoneName), Literal(" "), num(Numeric::Day)], parsed!(day: 5)); + check("CEST ", &[fixed(TimezoneName)], Err(TOO_LONG)); + check(" CEST", &[fixed(TimezoneName)], Err(TOO_LONG)); + check("CE ST", &[fixed(TimezoneName)], Err(TOO_LONG)); + } + + #[test] + #[rustfmt::skip] + fn test_parse_practical_examples() { + use crate::format::InternalInternal::*; + use crate::format::Item::{Literal, Space}; + use crate::format::Numeric::*; + + // some practical examples + check( + "2015-02-04T14:37:05+09:00", + &[ + num(Year), Literal("-"), num(Month), Literal("-"), num(Day), Literal("T"), + num(Hour), Literal(":"), num(Minute), Literal(":"), num(Second), + fixed(Fixed::TimezoneOffset), + ], + parsed!( + year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, minute: 37, + second: 5, offset: 32400 + ), + ); + check( + "2015-02-04T14:37:05-09:00", + &[ + num(Year), Literal("-"), num(Month), Literal("-"), num(Day), Literal("T"), + num(Hour), Literal(":"), num(Minute), Literal(":"), num(Second), + fixed(Fixed::TimezoneOffset), + ], + parsed!( + year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, minute: 37, + second: 5, offset: -32400 + ), + ); + check( + "2015-02-04T14:37:05−09:00", // timezone offset using MINUS SIGN (U+2212) + &[ + num(Year), Literal("-"), num(Month), Literal("-"), num(Day), Literal("T"), + num(Hour), Literal(":"), num(Minute), Literal(":"), num(Second), + fixed(Fixed::TimezoneOffset) + ], + parsed!( + year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, minute: 37, + second: 5, offset: -32400 + ), + ); + check( + "20150204143705567", + &[ + num(Year), num(Month), num(Day), num(Hour), num(Minute), num(Second), + internal_fixed(Nanosecond3NoDot) + ], + parsed!( + year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, minute: 37, + second: 5, nanosecond: 567000000 + ), + ); + check( + "Mon, 10 Jun 2013 09:32:37 GMT", + &[ + fixed(Fixed::ShortWeekdayName), Literal(","), Space(" "), num(Day), Space(" "), + fixed(Fixed::ShortMonthName), Space(" "), num(Year), Space(" "), num(Hour), + Literal(":"), num(Minute), Literal(":"), num(Second), Space(" "), Literal("GMT") + ], + parsed!( + year: 2013, month: 6, day: 10, weekday: Weekday::Mon, + hour_div_12: 0, hour_mod_12: 9, minute: 32, second: 37 + ), + ); + check( + "🤠Mon, 10 Jun🤠2013 09:32:37 GMT🤠", + &[ + Literal("🤠"), fixed(Fixed::ShortWeekdayName), Literal(","), Space(" "), num(Day), + Space(" "), fixed(Fixed::ShortMonthName), Literal("🤠"), num(Year), Space(" "), + num(Hour), Literal(":"), num(Minute), Literal(":"), num(Second), Space(" "), + Literal("GMT"), Literal("🤠") + ], + parsed!( + year: 2013, month: 6, day: 10, weekday: Weekday::Mon, + hour_div_12: 0, hour_mod_12: 9, minute: 32, second: 37 + ), + ); + check( + "Sun Aug 02 13:39:15 CEST 2020", + &[ + fixed(Fixed::ShortWeekdayName), Space(" "), fixed(Fixed::ShortMonthName), + Space(" "), num(Day), Space(" "), num(Hour), Literal(":"), num(Minute), + Literal(":"), num(Second), Space(" "), fixed(Fixed::TimezoneName), Space(" "), + num(Year) + ], + parsed!( + year: 2020, month: 8, day: 2, weekday: Weekday::Sun, + hour_div_12: 1, hour_mod_12: 1, minute: 39, second: 15 + ), + ); + check( + "20060102150405", + &[num(Year), num(Month), num(Day), num(Hour), num(Minute), num(Second)], + parsed!( + year: 2006, month: 1, day: 2, hour_div_12: 1, hour_mod_12: 3, minute: 4, second: 5 + ), + ); + check( + "3:14PM", + &[num(Hour12), Literal(":"), num(Minute), fixed(Fixed::LowerAmPm)], + parsed!(hour_div_12: 1, hour_mod_12: 3, minute: 14), + ); + check( + "12345678901234.56789", + &[num(Timestamp), Literal("."), num(Nanosecond)], + parsed!(nanosecond: 56_789, timestamp: 12_345_678_901_234), + ); + check( + "12345678901234.56789", + &[num(Timestamp), fixed(Fixed::Nanosecond)], + parsed!(nanosecond: 567_890_000, timestamp: 12_345_678_901_234), + ); + + // docstring examples from `impl str::FromStr` + check( + "2000-01-02T03:04:05Z", + &[ + num(Year), Literal("-"), num(Month), Literal("-"), num(Day), Literal("T"), + num(Hour), Literal(":"), num(Minute), Literal(":"), num(Second), + internal_fixed(TimezoneOffsetPermissive) + ], + parsed!( + year: 2000, month: 1, day: 2, hour_div_12: 0, hour_mod_12: 3, minute: 4, second: 5, + offset: 0 + ), + ); + check( + "2000-01-02 03:04:05Z", + &[ + num(Year), Literal("-"), num(Month), Literal("-"), num(Day), Space(" "), + num(Hour), Literal(":"), num(Minute), Literal(":"), num(Second), + internal_fixed(TimezoneOffsetPermissive) + ], + parsed!( + year: 2000, month: 1, day: 2, hour_div_12: 0, hour_mod_12: 3, minute: 4, second: 5, + offset: 0 + ), + ); + } + + #[track_caller] + fn parses(s: &str, items: &[Item]) { let mut parsed = Parsed::new(); - parse(&mut parsed, date, [Item::Fixed(Fixed::RFC2822)].iter())?; - parsed.to_datetime() + assert!(parse(&mut parsed, s, items.iter()).is_ok()); } - fn fmt_rfc2822_datetime(dt: DateTime) -> String { - dt.format_with_items([Item::Fixed(Fixed::RFC2822)].iter()).to_string() + #[track_caller] + fn check(s: &str, items: &[Item], expected: ParseResult) { + let mut parsed = Parsed::new(); + let result = parse(&mut parsed, s, items.iter()); + let parsed = result.map(|_| parsed); + assert_eq!(parsed, expected); } - // Test against test data above - for &(date, checkdate) in testdates.iter() { - let d = rfc2822_to_datetime(date); // parse a date - let dt = match d { - // did we get a value? - Ok(dt) => Ok(fmt_rfc2822_datetime(dt)), // yes, go on - Err(e) => Err(e), // otherwise keep an error for the comparison + #[test] + fn test_rfc2822() { + let ymd_hmsn = |y, m, d, h, n, s, nano, off| { + FixedOffset::east_opt(off * 60 * 60) + .unwrap() + .with_ymd_and_hms(y, m, d, h, n, s) + .unwrap() + .with_nanosecond(nano) + .unwrap() }; - if dt != checkdate.map(|s| s.to_string()) { - // check for expected result - panic!( - "Date conversion failed for {}\nReceived: {:?}\nExpected: {:?}", - date, dt, checkdate - ); + + // Test data - (input, Ok(expected result) or Err(error code)) + let testdates = [ + ("Tue, 20 Jan 2015 17:35:20 -0800", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), // normal case + ("Fri, 2 Jan 2015 17:35:20 -0800", Ok(ymd_hmsn(2015, 1, 2, 17, 35, 20, 0, -8))), // folding whitespace + ("Fri, 02 Jan 2015 17:35:20 -0800", Ok(ymd_hmsn(2015, 1, 2, 17, 35, 20, 0, -8))), // leading zero + ("Tue, 20 Jan 2015 17:35:20 -0800 (UTC)", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), // trailing comment + ( + r"Tue, 20 Jan 2015 17:35:20 -0800 ( (UTC ) (\( (a)\(( \t ) ) \\( \) ))", + Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8)), + ), // complex trailing comment + (r"Tue, 20 Jan 2015 17:35:20 -0800 (UTC\)", Err(TOO_LONG)), // incorrect comment, not enough closing parentheses + ( + "Tue, 20 Jan 2015 17:35:20 -0800 (UTC)\t \r\n(Anothercomment)", + Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8)), + ), // multiple comments + ("Tue, 20 Jan 2015 17:35:20 -0800 (UTC) ", Err(TOO_LONG)), // trailing whitespace after comment + ("20 Jan 2015 17:35:20 -0800", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), // no day of week + ("20 JAN 2015 17:35:20 -0800", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), // upper case month + ("Tue, 20 Jan 2015 17:35 -0800", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 0, 0, -8))), // no second + ("11 Sep 2001 09:45:00 +0000", Ok(ymd_hmsn(2001, 9, 11, 9, 45, 0, 0, 0))), + ("11 Sep 2001 09:45:00 EST", Ok(ymd_hmsn(2001, 9, 11, 9, 45, 0, 0, -5))), + ("11 Sep 2001 09:45:00 GMT", Ok(ymd_hmsn(2001, 9, 11, 9, 45, 0, 0, 0))), + ("30 Feb 2015 17:35:20 -0800", Err(OUT_OF_RANGE)), // bad day of month + ("Tue, 20 Jan 2015", Err(TOO_SHORT)), // omitted fields + ("Tue, 20 Avr 2015 17:35:20 -0800", Err(INVALID)), // bad month name + ("Tue, 20 Jan 2015 25:35:20 -0800", Err(OUT_OF_RANGE)), // bad hour + ("Tue, 20 Jan 2015 7:35:20 -0800", Err(INVALID)), // bad # of digits in hour + ("Tue, 20 Jan 2015 17:65:20 -0800", Err(OUT_OF_RANGE)), // bad minute + ("Tue, 20 Jan 2015 17:35:90 -0800", Err(OUT_OF_RANGE)), // bad second + ("Tue, 20 Jan 2015 17:35:20 -0890", Err(OUT_OF_RANGE)), // bad offset + ("6 Jun 1944 04:00:00Z", Err(INVALID)), // bad offset (zulu not allowed) + // named timezones that have specific timezone offsets + // see https://www.rfc-editor.org/rfc/rfc2822#section-4.3 + ("Tue, 20 Jan 2015 17:35:20 GMT", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, 0))), + ("Tue, 20 Jan 2015 17:35:20 UT", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, 0))), + ("Tue, 20 Jan 2015 17:35:20 ut", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, 0))), + ("Tue, 20 Jan 2015 17:35:20 EDT", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -4))), + ("Tue, 20 Jan 2015 17:35:20 EST", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -5))), + ("Tue, 20 Jan 2015 17:35:20 CDT", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -5))), + ("Tue, 20 Jan 2015 17:35:20 CST", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -6))), + ("Tue, 20 Jan 2015 17:35:20 MDT", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -6))), + ("Tue, 20 Jan 2015 17:35:20 MST", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -7))), + ("Tue, 20 Jan 2015 17:35:20 PDT", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -7))), + ("Tue, 20 Jan 2015 17:35:20 PST", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), + ("Tue, 20 Jan 2015 17:35:20 pst", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), + // named single-letter military timezones must fallback to +0000 + ("Tue, 20 Jan 2015 17:35:20 Z", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, 0))), + ("Tue, 20 Jan 2015 17:35:20 A", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, 0))), + ("Tue, 20 Jan 2015 17:35:20 a", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, 0))), + ("Tue, 20 Jan 2015 17:35:20 K", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, 0))), + ("Tue, 20 Jan 2015 17:35:20 k", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, 0))), + // named single-letter timezone "J" is specifically not valid + ("Tue, 20 Jan 2015 17:35:20 J", Err(INVALID)), + ("Tue, 20 Jan 2015 17:35:20 -0890", Err(OUT_OF_RANGE)), // bad offset minutes + ("Tue, 20 Jan 2015 17:35:20Z", Err(INVALID)), // bad offset: zulu not allowed + ("Tue, 20 Jan 2015 17:35:20 Zulu", Err(INVALID)), // bad offset: zulu not allowed + ("Tue, 20 Jan 2015 17:35:20 ZULU", Err(INVALID)), // bad offset: zulu not allowed + ("Tue, 20 Jan 2015 17:35:20 −0800", Err(INVALID)), // bad offset: timezone offset using MINUS SIGN (U+2212), not specified for RFC 2822 + ("Tue, 20 Jan 2015 17:35:20 0800", Err(INVALID)), // missing offset sign + ("Tue, 20 Jan 2015 17:35:20 HAS", Err(INVALID)), // bad named timezone + ("Tue, 20 Jan 2015😈17:35:20 -0800", Err(INVALID)), // bad character! + ]; + + fn rfc2822_to_datetime(date: &str) -> ParseResult> { + let mut parsed = Parsed::new(); + parse(&mut parsed, date, [Item::Fixed(Fixed::RFC2822)].iter())?; + parsed.to_datetime() + } + + // Test against test data above + for &(date, checkdate) in testdates.iter() { + #[cfg(feature = "std")] + eprintln!("Test input: {:?}\n Expect: {:?}", date, checkdate); + let dt = rfc2822_to_datetime(date); // parse a date + if dt != checkdate { + // check for expected result + panic!( + "Date conversion failed for {}\nReceived: {:?}\nExpected: {:?}", + date, dt, checkdate + ); + } } } -} -#[cfg(test)] -#[test] -fn parse_rfc850() { - use {TimeZone, Utc}; + #[test] + fn parse_rfc850() { + static RFC850_FMT: &str = "%A, %d-%b-%y %T GMT"; - static RFC850_FMT: &'static str = "%A, %d-%b-%y %T GMT"; + let dt = Utc.with_ymd_and_hms(1994, 11, 6, 8, 49, 37).unwrap(); - let dt_str = "Sunday, 06-Nov-94 08:49:37 GMT"; - let dt = Utc.ymd(1994, 11, 6).and_hms(8, 49, 37); + // Check that the format is what we expect + #[cfg(feature = "alloc")] + assert_eq!(dt.format(RFC850_FMT).to_string(), "Sunday, 06-Nov-94 08:49:37 GMT"); - // Check that the format is what we expect - assert_eq!(dt.format(RFC850_FMT).to_string(), dt_str); + // Check that it parses correctly + assert_eq!( + NaiveDateTime::parse_from_str("Sunday, 06-Nov-94 08:49:37 GMT", RFC850_FMT), + Ok(dt.naive_utc()) + ); - // Check that it parses correctly - assert_eq!(Ok(dt), Utc.datetime_from_str("Sunday, 06-Nov-94 08:49:37 GMT", RFC850_FMT)); + // Check that the rest of the weekdays parse correctly (this test originally failed because + // Sunday parsed incorrectly). + let testdates = [ + ( + Utc.with_ymd_and_hms(1994, 11, 7, 8, 49, 37).unwrap(), + "Monday, 07-Nov-94 08:49:37 GMT", + ), + ( + Utc.with_ymd_and_hms(1994, 11, 8, 8, 49, 37).unwrap(), + "Tuesday, 08-Nov-94 08:49:37 GMT", + ), + ( + Utc.with_ymd_and_hms(1994, 11, 9, 8, 49, 37).unwrap(), + "Wednesday, 09-Nov-94 08:49:37 GMT", + ), + ( + Utc.with_ymd_and_hms(1994, 11, 10, 8, 49, 37).unwrap(), + "Thursday, 10-Nov-94 08:49:37 GMT", + ), + ( + Utc.with_ymd_and_hms(1994, 11, 11, 8, 49, 37).unwrap(), + "Friday, 11-Nov-94 08:49:37 GMT", + ), + ( + Utc.with_ymd_and_hms(1994, 11, 12, 8, 49, 37).unwrap(), + "Saturday, 12-Nov-94 08:49:37 GMT", + ), + ]; - // Check that the rest of the weekdays parse correctly (this test originally failed because - // Sunday parsed incorrectly). - let testdates = [ - (Utc.ymd(1994, 11, 7).and_hms(8, 49, 37), "Monday, 07-Nov-94 08:49:37 GMT"), - (Utc.ymd(1994, 11, 8).and_hms(8, 49, 37), "Tuesday, 08-Nov-94 08:49:37 GMT"), - (Utc.ymd(1994, 11, 9).and_hms(8, 49, 37), "Wednesday, 09-Nov-94 08:49:37 GMT"), - (Utc.ymd(1994, 11, 10).and_hms(8, 49, 37), "Thursday, 10-Nov-94 08:49:37 GMT"), - (Utc.ymd(1994, 11, 11).and_hms(8, 49, 37), "Friday, 11-Nov-94 08:49:37 GMT"), - (Utc.ymd(1994, 11, 12).and_hms(8, 49, 37), "Saturday, 12-Nov-94 08:49:37 GMT"), - ]; + for val in &testdates { + assert_eq!(NaiveDateTime::parse_from_str(val.1, RFC850_FMT), Ok(val.0.naive_utc())); + } - for val in &testdates { - assert_eq!(Ok(val.0), Utc.datetime_from_str(val.1, RFC850_FMT)); - } -} + let test_dates_fail = [ + "Saturday, 12-Nov-94 08:49:37", + "Saturday, 12-Nov-94 08:49:37 Z", + "Saturday, 12-Nov-94 08:49:37 GMTTTT", + "Saturday, 12-Nov-94 08:49:37 gmt", + "Saturday, 12-Nov-94 08:49:37 +08:00", + "Caturday, 12-Nov-94 08:49:37 GMT", + "Saturday, 99-Nov-94 08:49:37 GMT", + "Saturday, 12-Nov-2000 08:49:37 GMT", + "Saturday, 12-Mop-94 08:49:37 GMT", + "Saturday, 12-Nov-94 28:49:37 GMT", + "Saturday, 12-Nov-94 08:99:37 GMT", + "Saturday, 12-Nov-94 08:49:99 GMT", + ]; -#[cfg(test)] -#[test] -fn test_rfc3339() { - use super::*; - use offset::FixedOffset; - use DateTime; - - // Test data - (input, Ok(expected result after parse and format) or Err(error code)) - let testdates = [ - ("2015-01-20T17:35:20-08:00", Ok("2015-01-20T17:35:20-08:00")), // normal case - ("1944-06-06T04:04:00Z", Ok("1944-06-06T04:04:00+00:00")), // D-day - ("2001-09-11T09:45:00-08:00", Ok("2001-09-11T09:45:00-08:00")), - ("2015-01-20T17:35:20.001-08:00", Ok("2015-01-20T17:35:20.001-08:00")), - ("2015-01-20T17:35:20.000031-08:00", Ok("2015-01-20T17:35:20.000031-08:00")), - ("2015-01-20T17:35:20.000000004-08:00", Ok("2015-01-20T17:35:20.000000004-08:00")), - ("2015-01-20T17:35:20.000000000452-08:00", Ok("2015-01-20T17:35:20-08:00")), // too small - ("2015-02-30T17:35:20-08:00", Err(OUT_OF_RANGE)), // bad day of month - ("2015-01-20T25:35:20-08:00", Err(OUT_OF_RANGE)), // bad hour - ("2015-01-20T17:65:20-08:00", Err(OUT_OF_RANGE)), // bad minute - ("2015-01-20T17:35:90-08:00", Err(OUT_OF_RANGE)), // bad second - ("2015-01-20T17:35:20-24:00", Err(OUT_OF_RANGE)), // bad offset - ]; - - fn rfc3339_to_datetime(date: &str) -> ParseResult> { - let mut parsed = Parsed::new(); - parse(&mut parsed, date, [Item::Fixed(Fixed::RFC3339)].iter())?; - parsed.to_datetime() - } - - fn fmt_rfc3339_datetime(dt: DateTime) -> String { - dt.format_with_items([Item::Fixed(Fixed::RFC3339)].iter()).to_string() - } - - // Test against test data above - for &(date, checkdate) in testdates.iter() { - let d = rfc3339_to_datetime(date); // parse a date - let dt = match d { - // did we get a value? - Ok(dt) => Ok(fmt_rfc3339_datetime(dt)), // yes, go on - Err(e) => Err(e), // otherwise keep an error for the comparison - }; - if dt != checkdate.map(|s| s.to_string()) { - // check for expected result - panic!( - "Date conversion failed for {}\nReceived: {:?}\nExpected: {:?}", - date, dt, checkdate - ); + for val in &test_dates_fail { + assert!(NaiveDateTime::parse_from_str(val, RFC850_FMT).is_err()); } } + + #[test] + fn test_rfc3339() { + let ymd_hmsn = |y, m, d, h, n, s, nano, off| { + FixedOffset::east_opt(off * 60 * 60) + .unwrap() + .with_ymd_and_hms(y, m, d, h, n, s) + .unwrap() + .with_nanosecond(nano) + .unwrap() + }; + + // Test data - (input, Ok(expected result) or Err(error code)) + let testdates = [ + ("2015-01-20T17:35:20-08:00", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), // normal case + ("2015-01-20T17:35:20−08:00", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), // normal case with MINUS SIGN (U+2212) + ("1944-06-06T04:04:00Z", Ok(ymd_hmsn(1944, 6, 6, 4, 4, 0, 0, 0))), // D-day + ("2001-09-11T09:45:00-08:00", Ok(ymd_hmsn(2001, 9, 11, 9, 45, 0, 0, -8))), + ("2015-01-20T17:35:20.001-08:00", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 1_000_000, -8))), + ("2015-01-20T17:35:20.001−08:00", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 1_000_000, -8))), // with MINUS SIGN (U+2212) + ("2015-01-20T17:35:20.000031-08:00", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 31_000, -8))), + ("2015-01-20T17:35:20.000000004-08:00", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 4, -8))), + ("2015-01-20T17:35:20.000000004−08:00", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 4, -8))), // with MINUS SIGN (U+2212) + ( + "2015-01-20T17:35:20.000000000452-08:00", + Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8)), + ), // too small + ( + "2015-01-20T17:35:20.000000000452−08:00", + Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8)), + ), // too small with MINUS SIGN (U+2212) + ("2015-01-20 17:35:20-08:00", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), // without 'T' + ("2015/01/20T17:35:20.001-08:00", Err(INVALID)), // wrong separator char YMD + ("2015-01-20T17-35-20.001-08:00", Err(INVALID)), // wrong separator char HMS + ("-01-20T17:35:20-08:00", Err(INVALID)), // missing year + ("99-01-20T17:35:20-08:00", Err(INVALID)), // bad year format + ("99999-01-20T17:35:20-08:00", Err(INVALID)), // bad year value + ("-2000-01-20T17:35:20-08:00", Err(INVALID)), // bad year value + ("2015-02-30T17:35:20-08:00", Err(OUT_OF_RANGE)), // bad day of month value + ("2015-01-20T25:35:20-08:00", Err(OUT_OF_RANGE)), // bad hour value + ("2015-01-20T17:65:20-08:00", Err(OUT_OF_RANGE)), // bad minute value + ("2015-01-20T17:35:90-08:00", Err(OUT_OF_RANGE)), // bad second value + ("2015-01-20T17:35:20-24:00", Err(OUT_OF_RANGE)), // bad offset value + ("15-01-20T17:35:20-08:00", Err(INVALID)), // bad year format + ("15-01-20T17:35:20-08:00:00", Err(INVALID)), // bad year format, bad offset format + ("2015-01-20T17:35:2008:00", Err(INVALID)), // missing offset sign + ("2015-01-20T17:35:20 08:00", Err(INVALID)), // missing offset sign + ("2015-01-20T17:35:20Zulu", Err(TOO_LONG)), // bad offset format + ("2015-01-20T17:35:20 Zulu", Err(INVALID)), // bad offset format + ("2015-01-20T17:35:20GMT", Err(INVALID)), // bad offset format + ("2015-01-20T17:35:20 GMT", Err(INVALID)), // bad offset format + ("2015-01-20T17:35:20+GMT", Err(INVALID)), // bad offset format + ("2015-01-20T17:35:20++08:00", Err(INVALID)), // bad offset format + ("2015-01-20T17:35:20--08:00", Err(INVALID)), // bad offset format + ("2015-01-20T17:35:20−−08:00", Err(INVALID)), // bad offset format with MINUS SIGN (U+2212) + ("2015-01-20T17:35:20±08:00", Err(INVALID)), // bad offset sign + ("2015-01-20T17:35:20-08-00", Err(INVALID)), // bad offset separator + ("2015-01-20T17:35:20-08;00", Err(INVALID)), // bad offset separator + ("2015-01-20T17:35:20-0800", Err(INVALID)), // bad offset separator + ("2015-01-20T17:35:20-08:0", Err(TOO_SHORT)), // bad offset minutes + ("2015-01-20T17:35:20-08:AA", Err(INVALID)), // bad offset minutes + ("2015-01-20T17:35:20-08:ZZ", Err(INVALID)), // bad offset minutes + ("2015-01-20T17:35:20.001-08 : 00", Err(INVALID)), // bad offset separator + ("2015-01-20T17:35:20-08:00:00", Err(TOO_LONG)), // bad offset format + ("2015-01-20T17:35:20+08:", Err(TOO_SHORT)), // bad offset format + ("2015-01-20T17:35:20-08:", Err(TOO_SHORT)), // bad offset format + ("2015-01-20T17:35:20−08:", Err(TOO_SHORT)), // bad offset format with MINUS SIGN (U+2212) + ("2015-01-20T17:35:20-08", Err(TOO_SHORT)), // bad offset format + ("2015-01-20T", Err(TOO_SHORT)), // missing HMS + ("2015-01-20T00:00:1", Err(TOO_SHORT)), // missing complete S + ("2015-01-20T00:00:1-08:00", Err(INVALID)), // missing complete S + ]; + + // Test against test data above + for &(date, checkdate) in testdates.iter() { + let dt = DateTime::::parse_from_rfc3339(date); + if dt != checkdate { + // check for expected result + panic!( + "Date conversion failed for {}\nReceived: {:?}\nExpected: {:?}", + date, dt, checkdate + ); + } + } + } + + #[test] + fn test_issue_1010() { + let dt = crate::NaiveDateTime::parse_from_str( + "\u{c}SUN\u{e}\u{3000}\0m@J\u{3000}\0\u{3000}\0m\u{c}!\u{c}\u{b}\u{c}\u{c}\u{c}\u{c}%A\u{c}\u{b}\0SU\u{c}\u{c}", + "\u{c}\u{c}%A\u{c}\u{b}\0SUN\u{c}\u{c}\u{c}SUNN\u{c}\u{c}\u{c}SUN\u{c}\u{c}!\u{c}\u{b}\u{c}\u{c}\u{c}\u{c}%A\u{c}\u{b}%a", + ); + assert_eq!(dt, Err(ParseError(ParseErrorKind::Invalid))); + } } diff --git a/third_party/rust/chrono/src/format/parsed.rs b/third_party/rust/chrono/src/format/parsed.rs index b8ed2d90f23..83975cb44b2 100644 --- a/third_party/rust/chrono/src/format/parsed.rs +++ b/third_party/rust/chrono/src/format/parsed.rs @@ -4,109 +4,172 @@ //! A collection of parsed date and time items. //! They can be constructed incrementally while being checked for consistency. -use num_traits::ToPrimitive; -use oldtime::Duration as OldDuration; +use super::{IMPOSSIBLE, NOT_ENOUGH, OUT_OF_RANGE, ParseResult}; +use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; +use crate::offset::{FixedOffset, MappedLocalTime, Offset, TimeZone}; +use crate::{DateTime, Datelike, TimeDelta, Timelike, Weekday}; -use super::{ParseResult, IMPOSSIBLE, NOT_ENOUGH, OUT_OF_RANGE}; -use div::div_rem; -use naive::{NaiveDate, NaiveDateTime, NaiveTime}; -use offset::{FixedOffset, LocalResult, Offset, TimeZone}; -use DateTime; -use Weekday; -use {Datelike, Timelike}; - -/// Parsed parts of date and time. There are two classes of methods: +/// A type to hold parsed fields of date and time that can check all fields are consistent. /// -/// - `set_*` methods try to set given field(s) while checking for the consistency. -/// It may or may not check for the range constraint immediately (for efficiency reasons). +/// There are three classes of methods: +/// +/// - `set_*` methods to set fields you have available. They do a basic range check, and if the +/// same field is set more than once it is checked for consistency. /// /// - `to_*` methods try to make a concrete date and time value out of set fields. -/// It fully checks any remaining out-of-range conditions and inconsistent/impossible fields. -#[allow(missing_copy_implementations)] -#[derive(Clone, PartialEq, Debug)] +/// They fully check that all fields are consistent and whether the date/datetime exists. +/// +/// - Methods to inspect the parsed fields. +/// +/// `Parsed` is used internally by all parsing functions in chrono. It is a public type so that it +/// can be used to write custom parsers that reuse the resolving algorithm, or to inspect the +/// results of a string parsed with chrono without converting it to concrete types. +/// +/// # Resolving algorithm +/// +/// Resolving date/time parts is littered with lots of corner cases, which is why common date/time +/// parsers do not implement it correctly. +/// +/// Chrono provides a complete resolution algorithm that checks all fields for consistency via the +/// `Parsed` type. +/// +/// As an easy example, consider RFC 2822. The [RFC 2822 date and time format] has a day of the week +/// part, which should be consistent with the other date parts. But a `strptime`-based parse would +/// happily accept inconsistent input: +/// +/// ```python +/// >>> import time +/// >>> time.strptime('Wed, 31 Dec 2014 04:26:40 +0000', +/// '%a, %d %b %Y %H:%M:%S +0000') +/// time.struct_time(tm_year=2014, tm_mon=12, tm_mday=31, +/// tm_hour=4, tm_min=26, tm_sec=40, +/// tm_wday=2, tm_yday=365, tm_isdst=-1) +/// >>> time.strptime('Thu, 31 Dec 2014 04:26:40 +0000', +/// '%a, %d %b %Y %H:%M:%S +0000') +/// time.struct_time(tm_year=2014, tm_mon=12, tm_mday=31, +/// tm_hour=4, tm_min=26, tm_sec=40, +/// tm_wday=3, tm_yday=365, tm_isdst=-1) +/// ``` +/// +/// [RFC 2822 date and time format]: https://tools.ietf.org/html/rfc2822#section-3.3 +/// +/// # Example +/// +/// Let's see how `Parsed` correctly detects the second RFC 2822 string from before is inconsistent. +/// +/// ``` +/// # #[cfg(feature = "alloc")] { +/// use chrono::format::{ParseErrorKind, Parsed}; +/// use chrono::Weekday; +/// +/// let mut parsed = Parsed::new(); +/// parsed.set_weekday(Weekday::Wed)?; +/// parsed.set_day(31)?; +/// parsed.set_month(12)?; +/// parsed.set_year(2014)?; +/// parsed.set_hour(4)?; +/// parsed.set_minute(26)?; +/// parsed.set_second(40)?; +/// parsed.set_offset(0)?; +/// let dt = parsed.to_datetime()?; +/// assert_eq!(dt.to_rfc2822(), "Wed, 31 Dec 2014 04:26:40 +0000"); +/// +/// let mut parsed = Parsed::new(); +/// parsed.set_weekday(Weekday::Thu)?; // changed to the wrong day +/// parsed.set_day(31)?; +/// parsed.set_month(12)?; +/// parsed.set_year(2014)?; +/// parsed.set_hour(4)?; +/// parsed.set_minute(26)?; +/// parsed.set_second(40)?; +/// parsed.set_offset(0)?; +/// let result = parsed.to_datetime(); +/// +/// assert!(result.is_err()); +/// if let Err(error) = result { +/// assert_eq!(error.kind(), ParseErrorKind::Impossible); +/// } +/// # } +/// # Ok::<(), chrono::ParseError>(()) +/// ``` +/// +/// The same using chrono's build-in parser for RFC 2822 (the [RFC2822 formatting item]) and +/// [`format::parse()`] showing how to inspect a field on failure. +/// +/// [RFC2822 formatting item]: crate::format::Fixed::RFC2822 +/// [`format::parse()`]: crate::format::parse() +/// +/// ``` +/// # #[cfg(feature = "alloc")] { +/// use chrono::format::{parse, Fixed, Item, Parsed}; +/// use chrono::Weekday; +/// +/// let rfc_2822 = [Item::Fixed(Fixed::RFC2822)]; +/// +/// let mut parsed = Parsed::new(); +/// parse(&mut parsed, "Wed, 31 Dec 2014 04:26:40 +0000", rfc_2822.iter())?; +/// let dt = parsed.to_datetime()?; +/// +/// assert_eq!(dt.to_rfc2822(), "Wed, 31 Dec 2014 04:26:40 +0000"); +/// +/// let mut parsed = Parsed::new(); +/// parse(&mut parsed, "Thu, 31 Dec 2014 04:26:40 +0000", rfc_2822.iter())?; +/// let result = parsed.to_datetime(); +/// +/// assert!(result.is_err()); +/// if result.is_err() { +/// // What is the weekday? +/// assert_eq!(parsed.weekday(), Some(Weekday::Thu)); +/// } +/// # } +/// # Ok::<(), chrono::ParseError>(()) +/// ``` +#[allow(clippy::manual_non_exhaustive)] +#[derive(Clone, PartialEq, Eq, Debug, Default, Hash)] pub struct Parsed { - /// Year. - /// - /// This can be negative unlike [`year_div_100`](#structfield.year_div_100) - /// and [`year_mod_100`](#structfield.year_mod_100) fields. + #[doc(hidden)] pub year: Option, - - /// Year divided by 100. Implies that the year is >= 1 BCE when set. - /// - /// Due to the common usage, if this field is missing but - /// [`year_mod_100`](#structfield.year_mod_100) is present, - /// it is inferred to 19 when `year_mod_100 >= 70` and 20 otherwise. + #[doc(hidden)] pub year_div_100: Option, - - /// Year modulo 100. Implies that the year is >= 1 BCE when set. + #[doc(hidden)] pub year_mod_100: Option, - - /// Year in the [ISO week date](../naive/struct.NaiveDate.html#week-date). - /// - /// This can be negative unlike [`isoyear_div_100`](#structfield.isoyear_div_100) and - /// [`isoyear_mod_100`](#structfield.isoyear_mod_100) fields. + #[doc(hidden)] pub isoyear: Option, - - /// Year in the [ISO week date](../naive/struct.NaiveDate.html#week-date), divided by 100. - /// Implies that the year is >= 1 BCE when set. - /// - /// Due to the common usage, if this field is missing but - /// [`isoyear_mod_100`](#structfield.isoyear_mod_100) is present, - /// it is inferred to 19 when `isoyear_mod_100 >= 70` and 20 otherwise. + #[doc(hidden)] pub isoyear_div_100: Option, - - /// Year in the [ISO week date](../naive/struct.NaiveDate.html#week-date), modulo 100. - /// Implies that the year is >= 1 BCE when set. + #[doc(hidden)] pub isoyear_mod_100: Option, - - /// Month (1--12). + #[doc(hidden)] + pub quarter: Option, + #[doc(hidden)] pub month: Option, - - /// Week number, where the week 1 starts at the first Sunday of January - /// (0--53, 1--53 or 1--52 depending on the year). + #[doc(hidden)] pub week_from_sun: Option, - - /// Week number, where the week 1 starts at the first Monday of January - /// (0--53, 1--53 or 1--52 depending on the year). + #[doc(hidden)] pub week_from_mon: Option, - - /// [ISO week number](../naive/struct.NaiveDate.html#week-date) - /// (1--52 or 1--53 depending on the year). + #[doc(hidden)] pub isoweek: Option, - - /// Day of the week. + #[doc(hidden)] pub weekday: Option, - - /// Day of the year (1--365 or 1--366 depending on the year). + #[doc(hidden)] pub ordinal: Option, - - /// Day of the month (1--28, 1--29, 1--30 or 1--31 depending on the month). + #[doc(hidden)] pub day: Option, - - /// Hour number divided by 12 (0--1). 0 indicates AM and 1 indicates PM. + #[doc(hidden)] pub hour_div_12: Option, - - /// Hour number modulo 12 (0--11). + #[doc(hidden)] pub hour_mod_12: Option, - - /// Minute number (0--59). + #[doc(hidden)] pub minute: Option, - - /// Second number (0--60, accounting for leap seconds). + #[doc(hidden)] pub second: Option, - - /// The number of nanoseconds since the whole second (0--999,999,999). + #[doc(hidden)] pub nanosecond: Option, - - /// The number of non-leap seconds since the midnight UTC on January 1, 1970. - /// - /// This can be off by one if [`second`](#structfield.second) is 60 (a leap second). + #[doc(hidden)] pub timestamp: Option, - - /// Offset from the local time to UTC, in seconds. + #[doc(hidden)] pub offset: Option, - - /// A dummy field to make this type not fully destructible (required for API stability). + #[doc(hidden)] _dummy: (), } @@ -114,197 +177,387 @@ pub struct Parsed { /// and if it is empty, set `old` to `new` as well. #[inline] fn set_if_consistent(old: &mut Option, new: T) -> ParseResult<()> { - if let Some(ref old) = *old { - if *old == new { + match old { + Some(old) if *old != new => Err(IMPOSSIBLE), + _ => { + *old = Some(new); Ok(()) - } else { - Err(IMPOSSIBLE) - } - } else { - *old = Some(new); - Ok(()) - } -} - -impl Default for Parsed { - fn default() -> Parsed { - Parsed { - year: None, - year_div_100: None, - year_mod_100: None, - isoyear: None, - isoyear_div_100: None, - isoyear_mod_100: None, - month: None, - week_from_sun: None, - week_from_mon: None, - isoweek: None, - weekday: None, - ordinal: None, - day: None, - hour_div_12: None, - hour_mod_12: None, - minute: None, - second: None, - nanosecond: None, - timestamp: None, - offset: None, - _dummy: (), } } } impl Parsed { /// Returns the initial value of parsed parts. + #[must_use] pub fn new() -> Parsed { Parsed::default() } - /// Tries to set the [`year`](#structfield.year) field from given value. + /// Set the [`year`](Parsed::year) field to the given value. + /// + /// The value can be negative, unlike the [`year_div_100`](Parsed::year_div_100) and + /// [`year_mod_100`](Parsed::year_mod_100) fields. + /// + /// # Errors + /// + /// Returns `OUT_OF_RANGE` if `value` is outside the range of an `i32`. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_year(&mut self, value: i64) -> ParseResult<()> { - set_if_consistent(&mut self.year, value.to_i32().ok_or(OUT_OF_RANGE)?) + set_if_consistent(&mut self.year, i32::try_from(value).map_err(|_| OUT_OF_RANGE)?) } - /// Tries to set the [`year_div_100`](#structfield.year_div_100) field from given value. + /// Set the [`year_div_100`](Parsed::year_div_100) field to the given value. + /// + /// # Errors + /// + /// Returns `OUT_OF_RANGE` if `value` is negative or if it is greater than `i32::MAX`. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_year_div_100(&mut self, value: i64) -> ParseResult<()> { - if value < 0 { + if !(0..=i32::MAX as i64).contains(&value) { return Err(OUT_OF_RANGE); } - set_if_consistent(&mut self.year_div_100, value.to_i32().ok_or(OUT_OF_RANGE)?) + set_if_consistent(&mut self.year_div_100, value as i32) } - /// Tries to set the [`year_mod_100`](#structfield.year_mod_100) field from given value. + /// Set the [`year_mod_100`](Parsed::year_mod_100) field to the given value. + /// + /// When set it implies that the year is not negative. + /// + /// If this field is set while the [`year_div_100`](Parsed::year_div_100) field is missing (and + /// the full [`year`](Parsed::year) field is also not set), it assumes a default value for the + /// [`year_div_100`](Parsed::year_div_100) field. + /// The default is 19 when `year_mod_100 >= 70` and 20 otherwise. + /// + /// # Errors + /// + /// Returns `OUT_OF_RANGE` if `value` is negative or if it is greater than 99. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_year_mod_100(&mut self, value: i64) -> ParseResult<()> { - if value < 0 { + if !(0..100).contains(&value) { return Err(OUT_OF_RANGE); } - set_if_consistent(&mut self.year_mod_100, value.to_i32().ok_or(OUT_OF_RANGE)?) + set_if_consistent(&mut self.year_mod_100, value as i32) } - /// Tries to set the [`isoyear`](#structfield.isoyear) field from given value. + /// Set the [`isoyear`](Parsed::isoyear) field, that is part of an [ISO 8601 week date], to the + /// given value. + /// + /// The value can be negative, unlike the [`isoyear_div_100`](Parsed::isoyear_div_100) and + /// [`isoyear_mod_100`](Parsed::isoyear_mod_100) fields. + /// + /// [ISO 8601 week date]: crate::NaiveDate#week-date + /// + /// # Errors + /// + /// Returns `OUT_OF_RANGE` if `value` is outside the range of an `i32`. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_isoyear(&mut self, value: i64) -> ParseResult<()> { - set_if_consistent(&mut self.isoyear, value.to_i32().ok_or(OUT_OF_RANGE)?) + set_if_consistent(&mut self.isoyear, i32::try_from(value).map_err(|_| OUT_OF_RANGE)?) } - /// Tries to set the [`isoyear_div_100`](#structfield.isoyear_div_100) field from given value. + /// Set the [`isoyear_div_100`](Parsed::isoyear_div_100) field, that is part of an + /// [ISO 8601 week date], to the given value. + /// + /// [ISO 8601 week date]: crate::NaiveDate#week-date + /// + /// # Errors + /// + /// Returns `OUT_OF_RANGE` if `value` is negative or if it is greater than `i32::MAX`. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_isoyear_div_100(&mut self, value: i64) -> ParseResult<()> { - if value < 0 { + if !(0..=i32::MAX as i64).contains(&value) { return Err(OUT_OF_RANGE); } - set_if_consistent(&mut self.isoyear_div_100, value.to_i32().ok_or(OUT_OF_RANGE)?) + set_if_consistent(&mut self.isoyear_div_100, value as i32) } - /// Tries to set the [`isoyear_mod_100`](#structfield.isoyear_mod_100) field from given value. + /// Set the [`isoyear_mod_100`](Parsed::isoyear_mod_100) field, that is part of an + /// [ISO 8601 week date], to the given value. + /// + /// When set it implies that the year is not negative. + /// + /// If this field is set while the [`isoyear_div_100`](Parsed::isoyear_div_100) field is missing + /// (and the full [`isoyear`](Parsed::isoyear) field is also not set), it assumes a default + /// value for the [`isoyear_div_100`](Parsed::isoyear_div_100) field. + /// The default is 19 when `year_mod_100 >= 70` and 20 otherwise. + /// + /// [ISO 8601 week date]: crate::NaiveDate#week-date + /// + /// # Errors + /// + /// Returns `OUT_OF_RANGE` if `value` is negative or if it is greater than 99. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_isoyear_mod_100(&mut self, value: i64) -> ParseResult<()> { - if value < 0 { + if !(0..100).contains(&value) { return Err(OUT_OF_RANGE); } - set_if_consistent(&mut self.isoyear_mod_100, value.to_i32().ok_or(OUT_OF_RANGE)?) + set_if_consistent(&mut self.isoyear_mod_100, value as i32) } - /// Tries to set the [`month`](#structfield.month) field from given value. + /// Set the [`quarter`](Parsed::quarter) field to the given value. + /// + /// Quarter 1 starts in January. + /// + /// # Errors + /// + /// Returns `OUT_OF_RANGE` if `value` is not in the range 1-4. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. + #[inline] + pub fn set_quarter(&mut self, value: i64) -> ParseResult<()> { + if !(1..=4).contains(&value) { + return Err(OUT_OF_RANGE); + } + set_if_consistent(&mut self.quarter, value as u32) + } + + /// Set the [`month`](Parsed::month) field to the given value. + /// + /// # Errors + /// + /// Returns `OUT_OF_RANGE` if `value` is not in the range 1-12. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_month(&mut self, value: i64) -> ParseResult<()> { - set_if_consistent(&mut self.month, value.to_u32().ok_or(OUT_OF_RANGE)?) + if !(1..=12).contains(&value) { + return Err(OUT_OF_RANGE); + } + set_if_consistent(&mut self.month, value as u32) } - /// Tries to set the [`week_from_sun`](#structfield.week_from_sun) field from given value. + /// Set the [`week_from_sun`](Parsed::week_from_sun) week number field to the given value. + /// + /// Week 1 starts at the first Sunday of January. + /// + /// # Errors + /// + /// Returns `OUT_OF_RANGE` if `value` is not in the range 0-53. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_week_from_sun(&mut self, value: i64) -> ParseResult<()> { - set_if_consistent(&mut self.week_from_sun, value.to_u32().ok_or(OUT_OF_RANGE)?) + if !(0..=53).contains(&value) { + return Err(OUT_OF_RANGE); + } + set_if_consistent(&mut self.week_from_sun, value as u32) } - /// Tries to set the [`week_from_mon`](#structfield.week_from_mon) field from given value. + /// Set the [`week_from_mon`](Parsed::week_from_mon) week number field to the given value. + /// Set the 'week number starting with Monday' field to the given value. + /// + /// Week 1 starts at the first Monday of January. + /// + /// # Errors + /// + /// Returns `OUT_OF_RANGE` if `value` is not in the range 0-53. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_week_from_mon(&mut self, value: i64) -> ParseResult<()> { - set_if_consistent(&mut self.week_from_mon, value.to_u32().ok_or(OUT_OF_RANGE)?) + if !(0..=53).contains(&value) { + return Err(OUT_OF_RANGE); + } + set_if_consistent(&mut self.week_from_mon, value as u32) } - /// Tries to set the [`isoweek`](#structfield.isoweek) field from given value. + /// Set the [`isoweek`](Parsed::isoweek) field for an [ISO 8601 week date] to the given value. + /// + /// [ISO 8601 week date]: crate::NaiveDate#week-date + /// + /// # Errors + /// + /// Returns `OUT_OF_RANGE` if `value` is not in the range 1-53. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_isoweek(&mut self, value: i64) -> ParseResult<()> { - set_if_consistent(&mut self.isoweek, value.to_u32().ok_or(OUT_OF_RANGE)?) + if !(1..=53).contains(&value) { + return Err(OUT_OF_RANGE); + } + set_if_consistent(&mut self.isoweek, value as u32) } - /// Tries to set the [`weekday`](#structfield.weekday) field from given value. + /// Set the [`weekday`](Parsed::weekday) field to the given value. + /// + /// # Errors + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_weekday(&mut self, value: Weekday) -> ParseResult<()> { set_if_consistent(&mut self.weekday, value) } - /// Tries to set the [`ordinal`](#structfield.ordinal) field from given value. + /// Set the [`ordinal`](Parsed::ordinal) (day of the year) field to the given value. + /// + /// # Errors + /// + /// Returns `OUT_OF_RANGE` if `value` is not in the range 1-366. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_ordinal(&mut self, value: i64) -> ParseResult<()> { - set_if_consistent(&mut self.ordinal, value.to_u32().ok_or(OUT_OF_RANGE)?) - } - - /// Tries to set the [`day`](#structfield.day) field from given value. - #[inline] - pub fn set_day(&mut self, value: i64) -> ParseResult<()> { - set_if_consistent(&mut self.day, value.to_u32().ok_or(OUT_OF_RANGE)?) - } - - /// Tries to set the [`hour_div_12`](#structfield.hour_div_12) field from given value. - /// (`false` for AM, `true` for PM) - #[inline] - pub fn set_ampm(&mut self, value: bool) -> ParseResult<()> { - set_if_consistent(&mut self.hour_div_12, if value { 1 } else { 0 }) - } - - /// Tries to set the [`hour_mod_12`](#structfield.hour_mod_12) field from - /// given hour number in 12-hour clocks. - #[inline] - pub fn set_hour12(&mut self, value: i64) -> ParseResult<()> { - if value < 1 || value > 12 { + if !(1..=366).contains(&value) { return Err(OUT_OF_RANGE); } - set_if_consistent(&mut self.hour_mod_12, value as u32 % 12) + set_if_consistent(&mut self.ordinal, value as u32) } - /// Tries to set both [`hour_div_12`](#structfield.hour_div_12) and - /// [`hour_mod_12`](#structfield.hour_mod_12) fields from given value. + /// Set the [`day`](Parsed::day) of the month field to the given value. + /// + /// # Errors + /// + /// Returns `OUT_OF_RANGE` if `value` is not in the range 1-31. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. + #[inline] + pub fn set_day(&mut self, value: i64) -> ParseResult<()> { + if !(1..=31).contains(&value) { + return Err(OUT_OF_RANGE); + } + set_if_consistent(&mut self.day, value as u32) + } + + /// Set the [`hour_div_12`](Parsed::hour_div_12) am/pm field to the given value. + /// + /// `false` indicates AM and `true` indicates PM. + /// + /// # Errors + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. + #[inline] + pub fn set_ampm(&mut self, value: bool) -> ParseResult<()> { + set_if_consistent(&mut self.hour_div_12, value as u32) + } + + /// Set the [`hour_mod_12`](Parsed::hour_mod_12) field, for the hour number in 12-hour clocks, + /// to the given value. + /// + /// Value must be in the canonical range of 1-12. + /// It will internally be stored as 0-11 (`value % 12`). + /// + /// # Errors + /// + /// Returns `OUT_OF_RANGE` if `value` is not in the range 1-12. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. + #[inline] + pub fn set_hour12(&mut self, mut value: i64) -> ParseResult<()> { + if !(1..=12).contains(&value) { + return Err(OUT_OF_RANGE); + } + if value == 12 { + value = 0 + } + set_if_consistent(&mut self.hour_mod_12, value as u32) + } + + /// Set the [`hour_div_12`](Parsed::hour_div_12) and [`hour_mod_12`](Parsed::hour_mod_12) + /// fields to the given value for a 24-hour clock. + /// + /// # Errors + /// + /// May return `OUT_OF_RANGE` if `value` is not in the range 0-23. + /// Currently only checks the value is not out of range for a `u32`. + /// + /// Returns `IMPOSSIBLE` one of the fields was already set to a different value. #[inline] pub fn set_hour(&mut self, value: i64) -> ParseResult<()> { - let v = value.to_u32().ok_or(OUT_OF_RANGE)?; - set_if_consistent(&mut self.hour_div_12, v / 12)?; - set_if_consistent(&mut self.hour_mod_12, v % 12)?; - Ok(()) + let (hour_div_12, hour_mod_12) = match value { + hour @ 0..=11 => (0, hour as u32), + hour @ 12..=23 => (1, hour as u32 - 12), + _ => return Err(OUT_OF_RANGE), + }; + set_if_consistent(&mut self.hour_div_12, hour_div_12)?; + set_if_consistent(&mut self.hour_mod_12, hour_mod_12) } - /// Tries to set the [`minute`](#structfield.minute) field from given value. + /// Set the [`minute`](Parsed::minute) field to the given value. + /// + /// # Errors + /// + /// Returns `OUT_OF_RANGE` if `value` is not in the range 0-59. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_minute(&mut self, value: i64) -> ParseResult<()> { - set_if_consistent(&mut self.minute, value.to_u32().ok_or(OUT_OF_RANGE)?) + if !(0..=59).contains(&value) { + return Err(OUT_OF_RANGE); + } + set_if_consistent(&mut self.minute, value as u32) } - /// Tries to set the [`second`](#structfield.second) field from given value. + /// Set the [`second`](Parsed::second) field to the given value. + /// + /// The value can be 60 in the case of a leap second. + /// + /// # Errors + /// + /// Returns `OUT_OF_RANGE` if `value` is not in the range 0-60. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_second(&mut self, value: i64) -> ParseResult<()> { - set_if_consistent(&mut self.second, value.to_u32().ok_or(OUT_OF_RANGE)?) + if !(0..=60).contains(&value) { + return Err(OUT_OF_RANGE); + } + set_if_consistent(&mut self.second, value as u32) } - /// Tries to set the [`nanosecond`](#structfield.nanosecond) field from given value. + /// Set the [`nanosecond`](Parsed::nanosecond) field to the given value. + /// + /// This is the number of nanoseconds since the whole second. + /// + /// # Errors + /// + /// Returns `OUT_OF_RANGE` if `value` is not in the range 0-999,999,999. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_nanosecond(&mut self, value: i64) -> ParseResult<()> { - set_if_consistent(&mut self.nanosecond, value.to_u32().ok_or(OUT_OF_RANGE)?) + if !(0..=999_999_999).contains(&value) { + return Err(OUT_OF_RANGE); + } + set_if_consistent(&mut self.nanosecond, value as u32) } - /// Tries to set the [`timestamp`](#structfield.timestamp) field from given value. + /// Set the [`timestamp`](Parsed::timestamp) field to the given value. + /// + /// A Unix timestamp is defined as the number of non-leap seconds since midnight UTC on + /// January 1, 1970. + /// + /// # Errors + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_timestamp(&mut self, value: i64) -> ParseResult<()> { set_if_consistent(&mut self.timestamp, value) } - /// Tries to set the [`offset`](#structfield.offset) field from given value. + /// Set the [`offset`](Parsed::offset) field to the given value. + /// + /// The offset is in seconds from local time to UTC. + /// + /// # Errors + /// + /// Returns `OUT_OF_RANGE` if `value` is outside the range of an `i32`. + /// + /// Returns `IMPOSSIBLE` if this field was already set to a different value. #[inline] pub fn set_offset(&mut self, value: i64) -> ParseResult<()> { - set_if_consistent(&mut self.offset, value.to_i32().ok_or(OUT_OF_RANGE)?) + set_if_consistent(&mut self.offset, i32::try_from(value).map_err(|_| OUT_OF_RANGE)?) } /// Returns a parsed naive date out of given fields. @@ -318,6 +571,18 @@ impl Parsed { /// /// Gregorian year and ISO week date year can have their century number (`*_div_100`) omitted, /// the two-digit year is used to guess the century number then. + /// + /// It checks all given date fields are consistent with each other. + /// + /// # Errors + /// + /// This method returns: + /// - `IMPOSSIBLE` if any of the date fields conflict. + /// - `NOT_ENOUGH` if there are not enough fields set in `Parsed` for a complete date. + /// - `OUT_OF_RANGE` + /// - if any of the date fields of `Parsed` are set to a value beyond their acceptable range. + /// - if the value would be outside the range of a [`NaiveDate`]. + /// - if the date does not exist. pub fn to_naive_date(&self) -> ParseResult { fn resolve_year( y: Option, @@ -333,11 +598,12 @@ impl Parsed { // check if present quotient and/or modulo is consistent to the full year. // since the presence of those fields means a positive full year, // we should filter a negative full year first. - (Some(y), q, r @ Some(0...99)) | (Some(y), q, r @ None) => { + (Some(y), q, r @ Some(0..=99)) | (Some(y), q, r @ None) => { if y < 0 { - return Err(OUT_OF_RANGE); + return Err(IMPOSSIBLE); } - let (q_, r_) = div_rem(y, 100); + let q_ = y / 100; + let r_ = y % 100; if q.unwrap_or(q_) == q_ && r.unwrap_or(r_) == r_ { Ok(Some(y)) } else { @@ -347,9 +613,9 @@ impl Parsed { // the full year is missing but we have quotient and modulo. // reconstruct the full year. make sure that the result is always positive. - (None, Some(q), Some(r @ 0...99)) => { + (None, Some(q), Some(r @ 0..=99)) => { if q < 0 { - return Err(OUT_OF_RANGE); + return Err(IMPOSSIBLE); } let y = q.checked_mul(100).and_then(|v| v.checked_add(r)); Ok(Some(y.ok_or(OUT_OF_RANGE)?)) @@ -357,7 +623,7 @@ impl Parsed { // we only have modulo. try to interpret a modulo as a conventional two-digit year. // note: we are affected by Rust issue #18060. avoid multiple range patterns. - (None, None, Some(r @ 0...99)) => Ok(Some(r + if r < 70 { 2000 } else { 1900 })), + (None, None, Some(r @ 0..=99)) => Ok(Some(r + if r < 70 { 2000 } else { 1900 })), // otherwise it is an out-of-bound or insufficient condition. (None, Some(_), None) => Err(NOT_ENOUGH), @@ -372,8 +638,7 @@ impl Parsed { let verify_ymd = |date: NaiveDate| { let year = date.year(); let (year_div_100, year_mod_100) = if year >= 0 { - let (q, r) = div_rem(year, 100); - (Some(q), Some(r)) + (Some(year / 100), Some(year % 100)) } else { (None, None) // they should be empty to be consistent }; @@ -393,8 +658,7 @@ impl Parsed { let isoweek = week.week(); let weekday = date.weekday(); let (isoyear_div_100, isoyear_mod_100) = if isoyear >= 0 { - let (q, r) = div_rem(isoyear, 100); - (Some(q), Some(r)) + (Some(isoyear / 100), Some(isoyear % 100)) } else { (None, None) // they should be empty to be consistent }; @@ -408,9 +672,8 @@ impl Parsed { // verify the ordinal and other (non-ISO) week dates. let verify_ordinal = |date: NaiveDate| { let ordinal = date.ordinal(); - let weekday = date.weekday(); - let week_from_sun = (ordinal as i32 - weekday.num_days_from_sunday() as i32 + 7) / 7; - let week_from_mon = (ordinal as i32 - weekday.num_days_from_monday() as i32 + 7) / 7; + let week_from_sun = date.weeks_from(Weekday::Sun); + let week_from_mon = date.weeks_from(Weekday::Mon); self.ordinal.unwrap_or(ordinal) == ordinal && self.week_from_sun.map_or(week_from_sun, |v| v as i32) == week_from_sun && self.week_from_mon.map_or(week_from_mon, |v| v as i32) == week_from_mon @@ -432,71 +695,15 @@ impl Parsed { (verify_ymd(date) && verify_isoweekdate(date) && verify_ordinal(date), date) } - ( - Some(year), - _, - &Parsed { week_from_sun: Some(week_from_sun), weekday: Some(weekday), .. }, - ) => { + (Some(year), _, &Parsed { week_from_sun: Some(week), weekday: Some(weekday), .. }) => { // year, week (starting at 1st Sunday), day of the week - let newyear = NaiveDate::from_yo_opt(year, 1).ok_or(OUT_OF_RANGE)?; - let firstweek = match newyear.weekday() { - Weekday::Sun => 0, - Weekday::Mon => 6, - Weekday::Tue => 5, - Weekday::Wed => 4, - Weekday::Thu => 3, - Weekday::Fri => 2, - Weekday::Sat => 1, - }; - - // `firstweek+1`-th day of January is the beginning of the week 1. - if week_from_sun > 53 { - return Err(OUT_OF_RANGE); - } // can it overflow? - let ndays = firstweek - + (week_from_sun as i32 - 1) * 7 - + weekday.num_days_from_sunday() as i32; - let date = newyear - .checked_add_signed(OldDuration::days(i64::from(ndays))) - .ok_or(OUT_OF_RANGE)?; - if date.year() != year { - return Err(OUT_OF_RANGE); - } // early exit for correct error - + let date = resolve_week_date(year, week, weekday, Weekday::Sun)?; (verify_ymd(date) && verify_isoweekdate(date) && verify_ordinal(date), date) } - ( - Some(year), - _, - &Parsed { week_from_mon: Some(week_from_mon), weekday: Some(weekday), .. }, - ) => { + (Some(year), _, &Parsed { week_from_mon: Some(week), weekday: Some(weekday), .. }) => { // year, week (starting at 1st Monday), day of the week - let newyear = NaiveDate::from_yo_opt(year, 1).ok_or(OUT_OF_RANGE)?; - let firstweek = match newyear.weekday() { - Weekday::Sun => 1, - Weekday::Mon => 0, - Weekday::Tue => 6, - Weekday::Wed => 5, - Weekday::Thu => 4, - Weekday::Fri => 3, - Weekday::Sat => 2, - }; - - // `firstweek+1`-th day of January is the beginning of the week 1. - if week_from_mon > 53 { - return Err(OUT_OF_RANGE); - } // can it overflow? - let ndays = firstweek - + (week_from_mon as i32 - 1) * 7 - + weekday.num_days_from_monday() as i32; - let date = newyear - .checked_add_signed(OldDuration::days(i64::from(ndays))) - .ok_or(OUT_OF_RANGE)?; - if date.year() != year { - return Err(OUT_OF_RANGE); - } // early exit for correct error - + let date = resolve_week_date(year, week, weekday, Weekday::Mon)?; (verify_ymd(date) && verify_isoweekdate(date) && verify_ordinal(date), date) } @@ -510,11 +717,15 @@ impl Parsed { (_, _, _) => return Err(NOT_ENOUGH), }; - if verified { - Ok(parsed_date) - } else { - Err(IMPOSSIBLE) + if !verified { + return Err(IMPOSSIBLE); + } else if let Some(parsed) = self.quarter { + if parsed != parsed_date.quarter() { + return Err(IMPOSSIBLE); + } } + + Ok(parsed_date) } /// Returns a parsed naive time out of given fields. @@ -526,34 +737,42 @@ impl Parsed { /// - Hour, minute, second, nanosecond. /// /// It is able to handle leap seconds when given second is 60. + /// + /// # Errors + /// + /// This method returns: + /// - `OUT_OF_RANGE` if any of the time fields of `Parsed` are set to a value beyond + /// their acceptable range. + /// - `NOT_ENOUGH` if an hour field is missing, if AM/PM is missing in a 12-hour clock, + /// if minutes are missing, or if seconds are missing while the nanosecond field is present. pub fn to_naive_time(&self) -> ParseResult { let hour_div_12 = match self.hour_div_12 { - Some(v @ 0...1) => v, + Some(v @ 0..=1) => v, Some(_) => return Err(OUT_OF_RANGE), None => return Err(NOT_ENOUGH), }; let hour_mod_12 = match self.hour_mod_12 { - Some(v @ 0...11) => v, + Some(v @ 0..=11) => v, Some(_) => return Err(OUT_OF_RANGE), None => return Err(NOT_ENOUGH), }; let hour = hour_div_12 * 12 + hour_mod_12; let minute = match self.minute { - Some(v @ 0...59) => v, + Some(v @ 0..=59) => v, Some(_) => return Err(OUT_OF_RANGE), None => return Err(NOT_ENOUGH), }; // we allow omitting seconds or nanoseconds, but they should be in the range. let (second, mut nano) = match self.second.unwrap_or(0) { - v @ 0...59 => (v, 0), + v @ 0..=59 => (v, 0), 60 => (59, 1_000_000_000), _ => return Err(OUT_OF_RANGE), }; nano += match self.nanosecond { - Some(v @ 0...999_999_999) if self.second.is_some() => v, - Some(0...999_999_999) => return Err(NOT_ENOUGH), // second is missing + Some(v @ 0..=999_999_999) if self.second.is_some() => v, + Some(0..=999_999_999) => return Err(NOT_ENOUGH), // second is missing Some(_) => return Err(OUT_OF_RANGE), None => 0, }; @@ -561,13 +780,25 @@ impl Parsed { NaiveTime::from_hms_nano_opt(hour, minute, second, nano).ok_or(OUT_OF_RANGE) } - /// Returns a parsed naive date and time out of given fields, - /// except for the [`offset`](#structfield.offset) field (assumed to have a given value). - /// This is required for parsing a local time or other known-timezone inputs. + /// Returns a parsed naive date and time out of given fields, except for the offset field. /// - /// This method is able to determine the combined date and time - /// from date and time fields or a single [`timestamp`](#structfield.timestamp) field. - /// Either way those fields have to be consistent to each other. + /// The offset is assumed to have a given value. It is not compared against the offset field set + /// in the `Parsed` type, so it is allowed to be inconsistent. + /// + /// This method is able to determine the combined date and time from date and time fields or + /// from a single timestamp field. It checks all fields are consistent with each other. + /// + /// # Errors + /// + /// This method returns: + /// - `IMPOSSIBLE` if any of the date fields conflict, or if a timestamp conflicts with any of + /// the other fields. + /// - `NOT_ENOUGH` if there are not enough fields set in `Parsed` for a complete datetime. + /// - `OUT_OF_RANGE` + /// - if any of the date or time fields of `Parsed` are set to a value beyond their acceptable + /// range. + /// - if the value would be outside the range of a [`NaiveDateTime`]. + /// - if the date does not exist. pub fn to_naive_datetime_with_offset(&self, offset: i32) -> ParseResult { let date = self.to_naive_date(); let time = self.to_naive_time(); @@ -576,7 +807,7 @@ impl Parsed { // verify the timestamp field if any // the following is safe, `timestamp` is very limited in range - let timestamp = datetime.timestamp() - i64::from(offset); + let timestamp = datetime.and_utc().timestamp() - i64::from(offset); if let Some(given_timestamp) = self.timestamp { // if `datetime` represents a leap second, it might be off by one second. if given_timestamp != timestamp @@ -601,8 +832,7 @@ impl Parsed { // reconstruct date and time fields from timestamp let ts = timestamp.checked_add(i64::from(offset)).ok_or(OUT_OF_RANGE)?; - let datetime = NaiveDateTime::from_timestamp_opt(ts, 0); - let mut datetime = datetime.ok_or(OUT_OF_RANGE)?; + let mut datetime = DateTime::from_timestamp(ts, 0).ok_or(OUT_OF_RANGE)?.naive_utc(); // fill year, ordinal, hour, minute and second fields from timestamp. // if existing fields are consistent, this will allow the full date/time reconstruction. @@ -614,7 +844,7 @@ impl Parsed { 59 => {} // `datetime` is known to be off by one second. 0 => { - datetime -= OldDuration::seconds(1); + datetime -= TimeDelta::try_seconds(1).unwrap(); } // otherwise it is impossible. _ => return Err(IMPOSSIBLE), @@ -641,36 +871,76 @@ impl Parsed { } /// Returns a parsed fixed time zone offset out of given fields. + /// + /// # Errors + /// + /// This method returns: + /// - `OUT_OF_RANGE` if the offset is out of range for a `FixedOffset`. + /// - `NOT_ENOUGH` if the offset field is not set. pub fn to_fixed_offset(&self) -> ParseResult { - self.offset.and_then(FixedOffset::east_opt).ok_or(OUT_OF_RANGE) + FixedOffset::east_opt(self.offset.ok_or(NOT_ENOUGH)?).ok_or(OUT_OF_RANGE) } /// Returns a parsed timezone-aware date and time out of given fields. /// - /// This method is able to determine the combined date and time - /// from date and time fields or a single [`timestamp`](#structfield.timestamp) field, - /// plus a time zone offset. - /// Either way those fields have to be consistent to each other. + /// This method is able to determine the combined date and time from date, time and offset + /// fields, and/or from a single timestamp field. It checks all fields are consistent with each + /// other. + /// + /// # Errors + /// + /// This method returns: + /// - `IMPOSSIBLE` if any of the date fields conflict, or if a timestamp conflicts with any of + /// the other fields. + /// - `NOT_ENOUGH` if there are not enough fields set in `Parsed` for a complete datetime + /// including offset from UTC. + /// - `OUT_OF_RANGE` + /// - if any of the fields of `Parsed` are set to a value beyond their acceptable + /// range. + /// - if the value would be outside the range of a [`NaiveDateTime`] or [`FixedOffset`]. + /// - if the date does not exist. pub fn to_datetime(&self) -> ParseResult> { - let offset = self.offset.ok_or(NOT_ENOUGH)?; + // If there is no explicit offset, consider a timestamp value as indication of a UTC value. + let offset = match (self.offset, self.timestamp) { + (Some(off), _) => off, + (None, Some(_)) => 0, // UNIX timestamp may assume 0 offset + (None, None) => return Err(NOT_ENOUGH), + }; let datetime = self.to_naive_datetime_with_offset(offset)?; let offset = FixedOffset::east_opt(offset).ok_or(OUT_OF_RANGE)?; + match offset.from_local_datetime(&datetime) { - LocalResult::None => Err(IMPOSSIBLE), - LocalResult::Single(t) => Ok(t), - LocalResult::Ambiguous(..) => Err(NOT_ENOUGH), + MappedLocalTime::None => Err(IMPOSSIBLE), + MappedLocalTime::Single(t) => Ok(t), + MappedLocalTime::Ambiguous(..) => Err(NOT_ENOUGH), } } /// Returns a parsed timezone-aware date and time out of given fields, - /// with an additional `TimeZone` used to interpret and validate the local date. + /// with an additional [`TimeZone`] used to interpret and validate the local date. /// - /// This method is able to determine the combined date and time - /// from date and time fields or a single [`timestamp`](#structfield.timestamp) field, - /// plus a time zone offset. - /// Either way those fields have to be consistent to each other. - /// If parsed fields include an UTC offset, it also has to be consistent to - /// [`offset`](#structfield.offset). + /// This method is able to determine the combined date and time from date and time, and/or from + /// a single timestamp field. It checks all fields are consistent with each other. + /// + /// If the parsed fields include an UTC offset, it also has to be consistent with the offset in + /// the provided `tz` time zone for that datetime. + /// + /// # Errors + /// + /// This method returns: + /// - `IMPOSSIBLE` + /// - if any of the date fields conflict, if a timestamp conflicts with any of the other + /// fields, or if the offset field is set but differs from the offset at that time in the + /// `tz` time zone. + /// - if the local datetime does not exists in the provided time zone (because it falls in a + /// transition due to for example DST). + /// - `NOT_ENOUGH` if there are not enough fields set in `Parsed` for a complete datetime, or if + /// the local time in the provided time zone is ambiguous (because it falls in a transition + /// due to for example DST) while there is no offset field or timestamp field set. + /// - `OUT_OF_RANGE` + /// - if the value would be outside the range of a [`NaiveDateTime`] or [`FixedOffset`]. + /// - if any of the fields of `Parsed` are set to a value beyond their acceptable range. + /// - if the date does not exist. pub fn to_datetime_with_timezone(&self, tz: &Tz) -> ParseResult> { // if we have `timestamp` specified, guess an offset from that. let mut guessed_offset = 0; @@ -678,8 +948,8 @@ impl Parsed { // make a naive `DateTime` from given timestamp and (if any) nanosecond. // an empty `nanosecond` is always equal to zero, so missing nanosecond is fine. let nanosecond = self.nanosecond.unwrap_or(0); - let dt = NaiveDateTime::from_timestamp_opt(timestamp, nanosecond); - let dt = dt.ok_or(OUT_OF_RANGE)?; + let dt = + DateTime::from_timestamp(timestamp, nanosecond).ok_or(OUT_OF_RANGE)?.naive_utc(); guessed_offset = tz.offset_from_utc_datetime(&dt).fix().local_minus_utc(); } @@ -696,15 +966,15 @@ impl Parsed { // it will be 0 otherwise, but this is fine as the algorithm ignores offset for that case. let datetime = self.to_naive_datetime_with_offset(guessed_offset)?; match tz.from_local_datetime(&datetime) { - LocalResult::None => Err(IMPOSSIBLE), - LocalResult::Single(t) => { + MappedLocalTime::None => Err(IMPOSSIBLE), + MappedLocalTime::Single(t) => { if check_offset(&t) { Ok(t) } else { Err(IMPOSSIBLE) } } - LocalResult::Ambiguous(min, max) => { + MappedLocalTime::Ambiguous(min, max) => { // try to disambiguate two possible local dates by offset. match (check_offset(&min), check_offset(&max)) { (false, false) => Err(IMPOSSIBLE), @@ -715,16 +985,219 @@ impl Parsed { } } } + + /// Get the `year` field if set. + /// + /// See also [`set_year()`](Parsed::set_year). + #[inline] + pub fn year(&self) -> Option { + self.year + } + + /// Get the `year_div_100` field if set. + /// + /// See also [`set_year_div_100()`](Parsed::set_year_div_100). + #[inline] + pub fn year_div_100(&self) -> Option { + self.year_div_100 + } + + /// Get the `year_mod_100` field if set. + /// + /// See also [`set_year_mod_100()`](Parsed::set_year_mod_100). + #[inline] + pub fn year_mod_100(&self) -> Option { + self.year_mod_100 + } + + /// Get the `isoyear` field that is part of an [ISO 8601 week date] if set. + /// + /// See also [`set_isoyear()`](Parsed::set_isoyear). + /// + /// [ISO 8601 week date]: crate::NaiveDate#week-date + #[inline] + pub fn isoyear(&self) -> Option { + self.isoyear + } + + /// Get the `isoyear_div_100` field that is part of an [ISO 8601 week date] if set. + /// + /// See also [`set_isoyear_div_100()`](Parsed::set_isoyear_div_100). + /// + /// [ISO 8601 week date]: crate::NaiveDate#week-date + #[inline] + pub fn isoyear_div_100(&self) -> Option { + self.isoyear_div_100 + } + + /// Get the `isoyear_mod_100` field that is part of an [ISO 8601 week date] if set. + /// + /// See also [`set_isoyear_mod_100()`](Parsed::set_isoyear_mod_100). + /// + /// [ISO 8601 week date]: crate::NaiveDate#week-date + #[inline] + pub fn isoyear_mod_100(&self) -> Option { + self.isoyear_mod_100 + } + + /// Get the `quarter` field if set. + /// + /// See also [`set_quarter()`](Parsed::set_quarter). + #[inline] + pub fn quarter(&self) -> Option { + self.quarter + } + + /// Get the `month` field if set. + /// + /// See also [`set_month()`](Parsed::set_month). + #[inline] + pub fn month(&self) -> Option { + self.month + } + + /// Get the `week_from_sun` field if set. + /// + /// See also [`set_week_from_sun()`](Parsed::set_week_from_sun). + #[inline] + pub fn week_from_sun(&self) -> Option { + self.week_from_sun + } + + /// Get the `week_from_mon` field if set. + /// + /// See also [`set_week_from_mon()`](Parsed::set_week_from_mon). + #[inline] + pub fn week_from_mon(&self) -> Option { + self.week_from_mon + } + + /// Get the `isoweek` field that is part of an [ISO 8601 week date] if set. + /// + /// See also [`set_isoweek()`](Parsed::set_isoweek). + /// + /// [ISO 8601 week date]: crate::NaiveDate#week-date + #[inline] + pub fn isoweek(&self) -> Option { + self.isoweek + } + + /// Get the `weekday` field if set. + /// + /// See also [`set_weekday()`](Parsed::set_weekday). + #[inline] + pub fn weekday(&self) -> Option { + self.weekday + } + + /// Get the `ordinal` (day of the year) field if set. + /// + /// See also [`set_ordinal()`](Parsed::set_ordinal). + #[inline] + pub fn ordinal(&self) -> Option { + self.ordinal + } + + /// Get the `day` of the month field if set. + /// + /// See also [`set_day()`](Parsed::set_day). + #[inline] + pub fn day(&self) -> Option { + self.day + } + + /// Get the `hour_div_12` field (am/pm) if set. + /// + /// 0 indicates AM and 1 indicates PM. + /// + /// See also [`set_ampm()`](Parsed::set_ampm) and [`set_hour()`](Parsed::set_hour). + #[inline] + pub fn hour_div_12(&self) -> Option { + self.hour_div_12 + } + + /// Get the `hour_mod_12` field if set. + /// + /// See also [`set_hour12()`](Parsed::set_hour12) and [`set_hour()`](Parsed::set_hour). + pub fn hour_mod_12(&self) -> Option { + self.hour_mod_12 + } + + /// Get the `minute` field if set. + /// + /// See also [`set_minute()`](Parsed::set_minute). + #[inline] + pub fn minute(&self) -> Option { + self.minute + } + + /// Get the `second` field if set. + /// + /// See also [`set_second()`](Parsed::set_second). + #[inline] + pub fn second(&self) -> Option { + self.second + } + + /// Get the `nanosecond` field if set. + /// + /// See also [`set_nanosecond()`](Parsed::set_nanosecond). + #[inline] + pub fn nanosecond(&self) -> Option { + self.nanosecond + } + + /// Get the `timestamp` field if set. + /// + /// See also [`set_timestamp()`](Parsed::set_timestamp). + #[inline] + pub fn timestamp(&self) -> Option { + self.timestamp + } + + /// Get the `offset` field if set. + /// + /// See also [`set_offset()`](Parsed::set_offset). + #[inline] + pub fn offset(&self) -> Option { + self.offset + } +} + +/// Create a `NaiveDate` when given a year, week, weekday, and the definition at which day of the +/// week a week starts. +/// +/// Returns `IMPOSSIBLE` if `week` is `0` or `53` and the `weekday` falls outside the year. +fn resolve_week_date( + year: i32, + week: u32, + weekday: Weekday, + week_start_day: Weekday, +) -> ParseResult { + if week > 53 { + return Err(OUT_OF_RANGE); + } + + let first_day_of_year = NaiveDate::from_yo_opt(year, 1).ok_or(OUT_OF_RANGE)?; + // Ordinal of the day at which week 1 starts. + let first_week_start = 1 + week_start_day.days_since(first_day_of_year.weekday()) as i32; + // Number of the `weekday`, which is 0 for the first day of the week. + let weekday = weekday.days_since(week_start_day) as i32; + let ordinal = first_week_start + (week as i32 - 1) * 7 + weekday; + if ordinal <= 0 { + return Err(IMPOSSIBLE); + } + first_day_of_year.with_ordinal(ordinal as u32).ok_or(IMPOSSIBLE) } #[cfg(test)] mod tests { use super::super::{IMPOSSIBLE, NOT_ENOUGH, OUT_OF_RANGE}; use super::Parsed; - use naive::{NaiveDate, NaiveTime, MAX_DATE, MIN_DATE}; - use offset::{FixedOffset, TimeZone, Utc}; - use Datelike; - use Weekday::*; + use crate::Datelike; + use crate::Weekday::*; + use crate::naive::{NaiveDate, NaiveTime}; + use crate::offset::{FixedOffset, TimeZone, Utc}; #[test] fn test_parsed_set_fields() { @@ -797,6 +1270,107 @@ mod tests { assert_eq!(p.set_timestamp(1_234_567_891), Err(IMPOSSIBLE)); } + #[test] + fn test_parsed_set_range() { + assert_eq!(Parsed::new().set_year(i32::MIN as i64 - 1), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_year(i32::MIN as i64).is_ok()); + assert!(Parsed::new().set_year(i32::MAX as i64).is_ok()); + assert_eq!(Parsed::new().set_year(i32::MAX as i64 + 1), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_year_div_100(-1), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_year_div_100(0).is_ok()); + assert!(Parsed::new().set_year_div_100(i32::MAX as i64).is_ok()); + assert_eq!(Parsed::new().set_year_div_100(i32::MAX as i64 + 1), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_year_mod_100(-1), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_year_mod_100(0).is_ok()); + assert!(Parsed::new().set_year_mod_100(99).is_ok()); + assert_eq!(Parsed::new().set_year_mod_100(100), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_isoyear(i32::MIN as i64 - 1), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_isoyear(i32::MIN as i64).is_ok()); + assert!(Parsed::new().set_isoyear(i32::MAX as i64).is_ok()); + assert_eq!(Parsed::new().set_isoyear(i32::MAX as i64 + 1), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_isoyear_div_100(-1), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_isoyear_div_100(0).is_ok()); + assert!(Parsed::new().set_isoyear_div_100(99).is_ok()); + assert_eq!(Parsed::new().set_isoyear_div_100(i32::MAX as i64 + 1), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_isoyear_mod_100(-1), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_isoyear_mod_100(0).is_ok()); + assert!(Parsed::new().set_isoyear_mod_100(99).is_ok()); + assert_eq!(Parsed::new().set_isoyear_mod_100(100), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_quarter(0), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_quarter(1).is_ok()); + assert!(Parsed::new().set_quarter(4).is_ok()); + assert_eq!(Parsed::new().set_quarter(5), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_month(0), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_month(1).is_ok()); + assert!(Parsed::new().set_month(12).is_ok()); + assert_eq!(Parsed::new().set_month(13), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_week_from_sun(-1), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_week_from_sun(0).is_ok()); + assert!(Parsed::new().set_week_from_sun(53).is_ok()); + assert_eq!(Parsed::new().set_week_from_sun(54), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_week_from_mon(-1), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_week_from_mon(0).is_ok()); + assert!(Parsed::new().set_week_from_mon(53).is_ok()); + assert_eq!(Parsed::new().set_week_from_mon(54), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_isoweek(0), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_isoweek(1).is_ok()); + assert!(Parsed::new().set_isoweek(53).is_ok()); + assert_eq!(Parsed::new().set_isoweek(54), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_ordinal(0), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_ordinal(1).is_ok()); + assert!(Parsed::new().set_ordinal(366).is_ok()); + assert_eq!(Parsed::new().set_ordinal(367), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_day(0), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_day(1).is_ok()); + assert!(Parsed::new().set_day(31).is_ok()); + assert_eq!(Parsed::new().set_day(32), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_hour12(0), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_hour12(1).is_ok()); + assert!(Parsed::new().set_hour12(12).is_ok()); + assert_eq!(Parsed::new().set_hour12(13), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_hour(-1), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_hour(0).is_ok()); + assert!(Parsed::new().set_hour(23).is_ok()); + assert_eq!(Parsed::new().set_hour(24), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_minute(-1), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_minute(0).is_ok()); + assert!(Parsed::new().set_minute(59).is_ok()); + assert_eq!(Parsed::new().set_minute(60), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_second(-1), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_second(0).is_ok()); + assert!(Parsed::new().set_second(60).is_ok()); + assert_eq!(Parsed::new().set_second(61), Err(OUT_OF_RANGE)); + + assert_eq!(Parsed::new().set_nanosecond(-1), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_nanosecond(0).is_ok()); + assert!(Parsed::new().set_nanosecond(999_999_999).is_ok()); + assert_eq!(Parsed::new().set_nanosecond(1_000_000_000), Err(OUT_OF_RANGE)); + + assert!(Parsed::new().set_timestamp(i64::MIN).is_ok()); + assert!(Parsed::new().set_timestamp(i64::MAX).is_ok()); + + assert_eq!(Parsed::new().set_offset(i32::MIN as i64 - 1), Err(OUT_OF_RANGE)); + assert!(Parsed::new().set_offset(i32::MIN as i64).is_ok()); + assert!(Parsed::new().set_offset(i32::MAX as i64).is_ok()); + assert_eq!(Parsed::new().set_offset(i32::MAX as i64 + 1), Err(OUT_OF_RANGE)); + } + #[test] fn test_parsed_to_naive_date() { macro_rules! parse { @@ -805,7 +1379,7 @@ mod tests { ) } - let ymd = |y, m, d| Ok(NaiveDate::from_ymd(y, m, d)); + let ymd = |y, m, d| Ok(NaiveDate::from_ymd_opt(y, m, d).unwrap()); // ymd: omission of fields assert_eq!(parse!(), Err(NOT_ENOUGH)); @@ -850,8 +1424,8 @@ mod tests { ); assert_eq!(parse!(year_div_100: 19, year_mod_100: -1, month: 1, day: 1), Err(OUT_OF_RANGE)); assert_eq!(parse!(year_div_100: 0, year_mod_100: 0, month: 1, day: 1), ymd(0, 1, 1)); - assert_eq!(parse!(year_div_100: -1, year_mod_100: 42, month: 1, day: 1), Err(OUT_OF_RANGE)); - let max_year = MAX_DATE.year(); + assert_eq!(parse!(year_div_100: -1, year_mod_100: 42, month: 1, day: 1), Err(IMPOSSIBLE)); + let max_year = NaiveDate::MAX.year(); assert_eq!( parse!(year_div_100: max_year / 100, year_mod_100: max_year % 100, month: 1, day: 1), @@ -886,17 +1460,28 @@ mod tests { ); assert_eq!( parse!(year: -1, year_div_100: -1, year_mod_100: 99, month: 1, day: 1), - Err(OUT_OF_RANGE) + Err(IMPOSSIBLE) ); - assert_eq!(parse!(year: -1, year_div_100: 0, month: 1, day: 1), Err(OUT_OF_RANGE)); - assert_eq!(parse!(year: -1, year_mod_100: 99, month: 1, day: 1), Err(OUT_OF_RANGE)); + assert_eq!(parse!(year: -1, year_div_100: 0, month: 1, day: 1), Err(IMPOSSIBLE)); + assert_eq!(parse!(year: -1, year_mod_100: 99, month: 1, day: 1), Err(IMPOSSIBLE)); + + // quarters + assert_eq!(parse!(year: 2000, quarter: 1), Err(NOT_ENOUGH)); + assert_eq!(parse!(year: 2000, quarter: 1, month: 1, day: 1), ymd(2000, 1, 1)); + assert_eq!(parse!(year: 2000, quarter: 2, month: 4, day: 1), ymd(2000, 4, 1)); + assert_eq!(parse!(year: 2000, quarter: 3, month: 7, day: 1), ymd(2000, 7, 1)); + assert_eq!(parse!(year: 2000, quarter: 4, month: 10, day: 1), ymd(2000, 10, 1)); + + // quarter: conflicting inputs + assert_eq!(parse!(year: 2000, quarter: 2, month: 3, day: 31), Err(IMPOSSIBLE)); + assert_eq!(parse!(year: 2000, quarter: 4, month: 3, day: 31), Err(IMPOSSIBLE)); // weekdates assert_eq!(parse!(year: 2000, week_from_mon: 0), Err(NOT_ENOUGH)); assert_eq!(parse!(year: 2000, week_from_sun: 0), Err(NOT_ENOUGH)); assert_eq!(parse!(year: 2000, weekday: Sun), Err(NOT_ENOUGH)); - assert_eq!(parse!(year: 2000, week_from_mon: 0, weekday: Fri), Err(OUT_OF_RANGE)); - assert_eq!(parse!(year: 2000, week_from_sun: 0, weekday: Fri), Err(OUT_OF_RANGE)); + assert_eq!(parse!(year: 2000, week_from_mon: 0, weekday: Fri), Err(IMPOSSIBLE)); + assert_eq!(parse!(year: 2000, week_from_sun: 0, weekday: Fri), Err(IMPOSSIBLE)); assert_eq!(parse!(year: 2000, week_from_mon: 0, weekday: Sat), ymd(2000, 1, 1)); assert_eq!(parse!(year: 2000, week_from_sun: 0, weekday: Sat), ymd(2000, 1, 1)); assert_eq!(parse!(year: 2000, week_from_mon: 0, weekday: Sun), ymd(2000, 1, 2)); @@ -910,9 +1495,9 @@ mod tests { assert_eq!(parse!(year: 2000, week_from_mon: 2, weekday: Mon), ymd(2000, 1, 10)); assert_eq!(parse!(year: 2000, week_from_sun: 52, weekday: Sat), ymd(2000, 12, 30)); assert_eq!(parse!(year: 2000, week_from_sun: 53, weekday: Sun), ymd(2000, 12, 31)); - assert_eq!(parse!(year: 2000, week_from_sun: 53, weekday: Mon), Err(OUT_OF_RANGE)); + assert_eq!(parse!(year: 2000, week_from_sun: 53, weekday: Mon), Err(IMPOSSIBLE)); assert_eq!(parse!(year: 2000, week_from_sun: 0xffffffff, weekday: Mon), Err(OUT_OF_RANGE)); - assert_eq!(parse!(year: 2006, week_from_sun: 0, weekday: Sat), Err(OUT_OF_RANGE)); + assert_eq!(parse!(year: 2006, week_from_sun: 0, weekday: Sat), Err(IMPOSSIBLE)); assert_eq!(parse!(year: 2006, week_from_sun: 1, weekday: Sun), ymd(2006, 1, 1)); // weekdates: conflicting inputs @@ -992,8 +1577,8 @@ mod tests { ) } - let hms = |h, m, s| Ok(NaiveTime::from_hms(h, m, s)); - let hmsn = |h, m, s, n| Ok(NaiveTime::from_hms_nano(h, m, s, n)); + let hms = |h, m, s| Ok(NaiveTime::from_hms_opt(h, m, s).unwrap()); + let hmsn = |h, m, s, n| Ok(NaiveTime::from_hms_nano_opt(h, m, s, n).unwrap()); // omission of fields assert_eq!(parse!(), Err(NOT_ENOUGH)); @@ -1048,9 +1633,12 @@ mod tests { ($($k:ident: $v:expr),*) => (parse!(offset = 0; $($k: $v),*)) } - let ymdhms = |y, m, d, h, n, s| Ok(NaiveDate::from_ymd(y, m, d).and_hms(h, n, s)); - let ymdhmsn = - |y, m, d, h, n, s, nano| Ok(NaiveDate::from_ymd(y, m, d).and_hms_nano(h, n, s, nano)); + let ymdhms = |y, m, d, h, n, s| { + Ok(NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap()) + }; + let ymdhmsn = |y, m, d, h, n, s, nano| { + Ok(NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_nano_opt(h, n, s, nano).unwrap()) + }; // omission of fields assert_eq!(parse!(), Err(NOT_ENOUGH)); @@ -1104,14 +1692,15 @@ mod tests { // more timestamps let max_days_from_year_1970 = - MAX_DATE.signed_duration_since(NaiveDate::from_ymd(1970, 1, 1)); - let year_0_from_year_1970 = - NaiveDate::from_ymd(0, 1, 1).signed_duration_since(NaiveDate::from_ymd(1970, 1, 1)); + NaiveDate::MAX.signed_duration_since(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()); + let year_0_from_year_1970 = NaiveDate::from_ymd_opt(0, 1, 1) + .unwrap() + .signed_duration_since(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()); let min_days_from_year_1970 = - MIN_DATE.signed_duration_since(NaiveDate::from_ymd(1970, 1, 1)); + NaiveDate::MIN.signed_duration_since(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()); assert_eq!( parse!(timestamp: min_days_from_year_1970.num_seconds()), - ymdhms(MIN_DATE.year(), 1, 1, 0, 0, 0) + ymdhms(NaiveDate::MIN.year(), 1, 1, 0, 0, 0) ); assert_eq!( parse!(timestamp: year_0_from_year_1970.num_seconds()), @@ -1119,7 +1708,7 @@ mod tests { ); assert_eq!( parse!(timestamp: max_days_from_year_1970.num_seconds() + 86399), - ymdhms(MAX_DATE.year(), 12, 31, 23, 59, 59) + ymdhms(NaiveDate::MAX.year(), 12, 31, 23, 59, 59) ); // leap seconds #1: partial fields @@ -1198,7 +1787,15 @@ mod tests { } let ymdhmsn = |y, m, d, h, n, s, nano, off| { - Ok(FixedOffset::east(off).ymd(y, m, d).and_hms_nano(h, n, s, nano)) + Ok(FixedOffset::east_opt(off) + .unwrap() + .from_local_datetime( + &NaiveDate::from_ymd_opt(y, m, d) + .unwrap() + .and_hms_nano_opt(h, n, s, nano) + .unwrap(), + ) + .unwrap()) }; assert_eq!(parse!(offset: 0), Err(NOT_ENOUGH)); @@ -1242,7 +1839,14 @@ mod tests { parse!(Utc; year: 2014, ordinal: 365, hour_div_12: 0, hour_mod_12: 4, minute: 26, second: 40, nanosecond: 12_345_678, offset: 0), - Ok(Utc.ymd(2014, 12, 31).and_hms_nano(4, 26, 40, 12_345_678)) + Ok(Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2014, 12, 31) + .unwrap() + .and_hms_nano_opt(4, 26, 40, 12_345_678) + .unwrap() + ) + .unwrap()) ); assert_eq!( parse!(Utc; @@ -1251,33 +1855,58 @@ mod tests { Err(IMPOSSIBLE) ); assert_eq!( - parse!(FixedOffset::east(32400); + parse!(FixedOffset::east_opt(32400).unwrap(); year: 2014, ordinal: 365, hour_div_12: 0, hour_mod_12: 4, minute: 26, second: 40, nanosecond: 12_345_678, offset: 0), Err(IMPOSSIBLE) ); assert_eq!( - parse!(FixedOffset::east(32400); + parse!(FixedOffset::east_opt(32400).unwrap(); year: 2014, ordinal: 365, hour_div_12: 1, hour_mod_12: 1, minute: 26, second: 40, nanosecond: 12_345_678, offset: 32400), - Ok(FixedOffset::east(32400).ymd(2014, 12, 31).and_hms_nano(13, 26, 40, 12_345_678)) + Ok(FixedOffset::east_opt(32400) + .unwrap() + .from_local_datetime( + &NaiveDate::from_ymd_opt(2014, 12, 31) + .unwrap() + .and_hms_nano_opt(13, 26, 40, 12_345_678) + .unwrap() + ) + .unwrap()) ); // single result from timestamp assert_eq!( parse!(Utc; timestamp: 1_420_000_000, offset: 0), - Ok(Utc.ymd(2014, 12, 31).and_hms(4, 26, 40)) + Ok(Utc.with_ymd_and_hms(2014, 12, 31, 4, 26, 40).unwrap()) ); assert_eq!(parse!(Utc; timestamp: 1_420_000_000, offset: 32400), Err(IMPOSSIBLE)); assert_eq!( - parse!(FixedOffset::east(32400); timestamp: 1_420_000_000, offset: 0), + parse!(FixedOffset::east_opt(32400).unwrap(); timestamp: 1_420_000_000, offset: 0), Err(IMPOSSIBLE) ); assert_eq!( - parse!(FixedOffset::east(32400); timestamp: 1_420_000_000, offset: 32400), - Ok(FixedOffset::east(32400).ymd(2014, 12, 31).and_hms(13, 26, 40)) + parse!(FixedOffset::east_opt(32400).unwrap(); timestamp: 1_420_000_000, offset: 32400), + Ok(FixedOffset::east_opt(32400) + .unwrap() + .with_ymd_and_hms(2014, 12, 31, 13, 26, 40) + .unwrap()) ); // TODO test with a variable time zone (for None and Ambiguous cases) } + + #[test] + fn issue_551() { + use crate::Weekday; + let mut parsed = Parsed::new(); + + parsed.year = Some(2002); + parsed.week_from_mon = Some(22); + parsed.weekday = Some(Weekday::Mon); + assert_eq!(NaiveDate::from_ymd_opt(2002, 6, 3).unwrap(), parsed.to_naive_date().unwrap()); + + parsed.year = Some(2001); + assert_eq!(NaiveDate::from_ymd_opt(2001, 5, 28).unwrap(), parsed.to_naive_date().unwrap()); + } } diff --git a/third_party/rust/chrono/src/format/scan.rs b/third_party/rust/chrono/src/format/scan.rs index 0efb1ee3d19..68958fd25f4 100644 --- a/third_party/rust/chrono/src/format/scan.rs +++ b/third_party/rust/chrono/src/format/scan.rs @@ -5,28 +5,8 @@ * Various scanning routines for the parser. */ -#![allow(deprecated)] - -use super::{ParseResult, INVALID, OUT_OF_RANGE, TOO_SHORT}; -use Weekday; - -/// Returns true when two slices are equal case-insensitively (in ASCII). -/// Assumes that the `pattern` is already converted to lower case. -fn equals(s: &str, pattern: &str) -> bool { - let mut xs = s.as_bytes().iter().map(|&c| match c { - b'A'...b'Z' => c + 32, - _ => c, - }); - let mut ys = pattern.as_bytes().iter().cloned(); - loop { - match (xs.next(), ys.next()) { - (None, None) => return true, - (None, _) | (_, None) => return false, - (Some(x), Some(y)) if x != y => return false, - _ => (), - } - } -} +use super::{INVALID, OUT_OF_RANGE, ParseResult, TOO_SHORT}; +use crate::Weekday; /// Tries to parse the non-negative number from `min` to `max` digits. /// @@ -34,7 +14,7 @@ fn equals(s: &str, pattern: &str) -> bool { /// More than `max` digits are consumed up to the first `max` digits. /// Any number that does not fit in `i64` is an error. #[inline] -pub fn number(s: &str, min: usize, max: usize) -> ParseResult<(&str, i64)> { +pub(super) fn number(s: &str, min: usize, max: usize) -> ParseResult<(&str, i64)> { assert!(min <= max); // We are only interested in ascii numbers, so we can work with the `str` as bytes. We stop on @@ -48,7 +28,7 @@ pub fn number(s: &str, min: usize, max: usize) -> ParseResult<(&str, i64)> { let mut n = 0i64; for (i, c) in bytes.iter().take(max).cloned().enumerate() { // cloned() = copied() - if c < b'0' || b'9' < c { + if !c.is_ascii_digit() { if i < min { return Err(INVALID); } else { @@ -62,12 +42,12 @@ pub fn number(s: &str, min: usize, max: usize) -> ParseResult<(&str, i64)> { }; } - Ok((&s[::core::cmp::min(max, bytes.len())..], n)) + Ok((&s[core::cmp::min(max, bytes.len())..], n)) } /// Tries to consume at least one digits as a fractional second. /// Returns the number of whole nanoseconds (0--999,999,999). -pub fn nanosecond(s: &str) -> ParseResult<(&str, i64)> { +pub(super) fn nanosecond(s: &str) -> ParseResult<(&str, i64)> { // record the number of digits consumed for later scaling. let origlen = s.len(); let (s, v) = number(s, 1, 9)?; @@ -79,14 +59,14 @@ pub fn nanosecond(s: &str) -> ParseResult<(&str, i64)> { let v = v.checked_mul(SCALE[consumed]).ok_or(OUT_OF_RANGE)?; // if there are more than 9 digits, skip next digits. - let s = s.trim_left_matches(|c: char| '0' <= c && c <= '9'); + let s = s.trim_start_matches(|c: char| c.is_ascii_digit()); Ok((s, v)) } /// Tries to consume a fixed number of digits as a fractional second. /// Returns the number of whole nanoseconds (0--999,999,999). -pub fn nanosecond_fixed(s: &str, digits: usize) -> ParseResult<(&str, i64)> { +pub(super) fn nanosecond_fixed(s: &str, digits: usize) -> ParseResult<(&str, i64)> { // record the number of digits consumed for later scaling. let (s, v) = number(s, digits, digits)?; @@ -99,7 +79,7 @@ pub fn nanosecond_fixed(s: &str, digits: usize) -> ParseResult<(&str, i64)> { } /// Tries to parse the month index (0 through 11) with the first three ASCII letters. -pub fn short_month0(s: &str) -> ParseResult<(&str, u8)> { +pub(super) fn short_month0(s: &str) -> ParseResult<(&str, u8)> { if s.len() < 3 { return Err(TOO_SHORT); } @@ -123,7 +103,7 @@ pub fn short_month0(s: &str) -> ParseResult<(&str, u8)> { } /// Tries to parse the weekday with the first three ASCII letters. -pub fn short_weekday(s: &str) -> ParseResult<(&str, Weekday)> { +pub(super) fn short_weekday(s: &str) -> ParseResult<(&str, Weekday)> { if s.len() < 3 { return Err(TOO_SHORT); } @@ -143,16 +123,18 @@ pub fn short_weekday(s: &str) -> ParseResult<(&str, Weekday)> { /// Tries to parse the month index (0 through 11) with short or long month names. /// It prefers long month names to short month names when both are possible. -pub fn short_or_long_month0(s: &str) -> ParseResult<(&str, u8)> { +pub(super) fn short_or_long_month0(s: &str) -> ParseResult<(&str, u8)> { // lowercased month names, minus first three chars - static LONG_MONTH_SUFFIXES: [&'static str; 12] = - ["uary", "ruary", "ch", "il", "", "e", "y", "ust", "tember", "ober", "ember", "ember"]; + static LONG_MONTH_SUFFIXES: [&[u8]; 12] = [ + b"uary", b"ruary", b"ch", b"il", b"", b"e", b"y", b"ust", b"tember", b"ober", b"ember", + b"ember", + ]; let (mut s, month0) = short_month0(s)?; // tries to consume the suffix if possible let suffix = LONG_MONTH_SUFFIXES[month0 as usize]; - if s.len() >= suffix.len() && equals(&s[..suffix.len()], suffix) { + if s.len() >= suffix.len() && s.as_bytes()[..suffix.len()].eq_ignore_ascii_case(suffix) { s = &s[suffix.len()..]; } @@ -161,16 +143,16 @@ pub fn short_or_long_month0(s: &str) -> ParseResult<(&str, u8)> { /// Tries to parse the weekday with short or long weekday names. /// It prefers long weekday names to short weekday names when both are possible. -pub fn short_or_long_weekday(s: &str) -> ParseResult<(&str, Weekday)> { +pub(super) fn short_or_long_weekday(s: &str) -> ParseResult<(&str, Weekday)> { // lowercased weekday names, minus first three chars - static LONG_WEEKDAY_SUFFIXES: [&'static str; 7] = - ["day", "sday", "nesday", "rsday", "day", "urday", "day"]; + static LONG_WEEKDAY_SUFFIXES: [&[u8]; 7] = + [b"day", b"sday", b"nesday", b"rsday", b"day", b"urday", b"day"]; let (mut s, weekday) = short_weekday(s)?; // tries to consume the suffix if possible let suffix = LONG_WEEKDAY_SUFFIXES[weekday.num_days_from_monday() as usize]; - if s.len() >= suffix.len() && equals(&s[..suffix.len()], suffix) { + if s.len() >= suffix.len() && s.as_bytes()[..suffix.len()].eq_ignore_ascii_case(suffix) { s = &s[suffix.len()..]; } @@ -178,7 +160,7 @@ pub fn short_or_long_weekday(s: &str) -> ParseResult<(&str, Weekday)> { } /// Tries to consume exactly one given character. -pub fn char(s: &str, c1: u8) -> ParseResult<&str> { +pub(super) fn char(s: &str, c1: u8) -> ParseResult<&str> { match s.as_bytes().first() { Some(&c) if c == c1 => Ok(&s[1..]), Some(_) => Err(INVALID), @@ -187,8 +169,8 @@ pub fn char(s: &str, c1: u8) -> ParseResult<&str> { } /// Tries to consume one or more whitespace. -pub fn space(s: &str) -> ParseResult<&str> { - let s_ = s.trim_left(); +pub(super) fn space(s: &str) -> ParseResult<&str> { + let s_ = s.trim_start(); if s_.len() < s.len() { Ok(s_) } else if s.is_empty() { @@ -199,48 +181,73 @@ pub fn space(s: &str) -> ParseResult<&str> { } /// Consumes any number (including zero) of colon or spaces. -pub fn colon_or_space(s: &str) -> ParseResult<&str> { - Ok(s.trim_left_matches(|c: char| c == ':' || c.is_whitespace())) +pub(crate) fn colon_or_space(s: &str) -> ParseResult<&str> { + Ok(s.trim_start_matches(|c: char| c == ':' || c.is_whitespace())) } -/// Tries to parse `[-+]\d\d` continued by `\d\d`. Return an offset in seconds if possible. +/// Parse a timezone from `s` and return the offset in seconds. /// -/// The additional `colon` may be used to parse a mandatory or optional `:` -/// between hours and minutes, and should return either a new suffix or `Err` when parsing fails. -pub fn timezone_offset(s: &str, consume_colon: F) -> ParseResult<(&str, i32)> -where - F: FnMut(&str) -> ParseResult<&str>, -{ - timezone_offset_internal(s, consume_colon, false) -} - -fn timezone_offset_internal( +/// The `consume_colon` function is used to parse a mandatory or optional `:` +/// separator between hours offset and minutes offset. +/// +/// The `allow_missing_minutes` flag allows the timezone minutes offset to be +/// missing from `s`. +/// +/// The `allow_tz_minus_sign` flag allows the timezone offset negative character +/// to also be `−` MINUS SIGN (U+2212) in addition to the typical +/// ASCII-compatible `-` HYPHEN-MINUS (U+2D). +/// This is part of [RFC 3339 & ISO 8601]. +/// +/// [RFC 3339 & ISO 8601]: https://en.wikipedia.org/w/index.php?title=ISO_8601&oldid=1114309368#Time_offsets_from_UTC +pub(crate) fn timezone_offset( mut s: &str, mut consume_colon: F, + allow_zulu: bool, allow_missing_minutes: bool, + allow_tz_minus_sign: bool, ) -> ParseResult<(&str, i32)> where F: FnMut(&str) -> ParseResult<&str>, { - fn digits(s: &str) -> ParseResult<(u8, u8)> { - let b = s.as_bytes(); - if b.len() < 2 { - Err(TOO_SHORT) - } else { - Ok((b[0], b[1])) + if allow_zulu { + if let Some(&b'Z' | &b'z') = s.as_bytes().first() { + return Ok((&s[1..], 0)); } } - let negative = match s.as_bytes().first() { - Some(&b'+') => false, - Some(&b'-') => true, + + const fn digits(s: &str) -> ParseResult<(u8, u8)> { + let b = s.as_bytes(); + if b.len() < 2 { Err(TOO_SHORT) } else { Ok((b[0], b[1])) } + } + let negative = match s.chars().next() { + Some('+') => { + // PLUS SIGN (U+2B) + s = &s['+'.len_utf8()..]; + + false + } + Some('-') => { + // HYPHEN-MINUS (U+2D) + s = &s['-'.len_utf8()..]; + + true + } + Some('−') => { + // MINUS SIGN (U+2212) + if !allow_tz_minus_sign { + return Err(INVALID); + } + s = &s['−'.len_utf8()..]; + + true + } Some(_) => return Err(INVALID), None => return Err(TOO_SHORT), }; - s = &s[1..]; // hours (00--99) let hours = match digits(s)? { - (h1 @ b'0'...b'9', h2 @ b'0'...b'9') => i32::from((h1 - b'0') * 10 + (h2 - b'0')), + (h1 @ b'0'..=b'9', h2 @ b'0'..=b'9') => i32::from((h1 - b'0') * 10 + (h2 - b'0')), _ => return Err(INVALID), }; s = &s[2..]; @@ -252,8 +259,8 @@ where // if the next two items are digits then we have to add minutes let minutes = if let Ok(ds) = digits(s) { match ds { - (m1 @ b'0'...b'5', m2 @ b'0'...b'9') => i32::from((m1 - b'0') * 10 + (m2 - b'0')), - (b'6'...b'9', b'0'...b'9') => return Err(OUT_OF_RANGE), + (m1 @ b'0'..=b'5', m2 @ b'0'..=b'9') => i32::from((m1 - b'0') * 10 + (m2 - b'0')), + (b'6'..=b'9', b'0'..=b'9') => return Err(OUT_OF_RANGE), _ => return Err(INVALID), } } else if allow_missing_minutes { @@ -263,7 +270,7 @@ where }; s = match s.len() { len if len >= 2 => &s[2..], - len if len == 0 => s, + 0 => s, _ => return Err(TOO_SHORT), }; @@ -271,80 +278,155 @@ where Ok((s, if negative { -seconds } else { seconds })) } -/// Same as `timezone_offset` but also allows for `z`/`Z` which is the same as `+00:00`. -pub fn timezone_offset_zulu(s: &str, colon: F) -> ParseResult<(&str, i32)> -where - F: FnMut(&str) -> ParseResult<&str>, -{ - let bytes = s.as_bytes(); - match bytes.first() { - Some(&b'z') | Some(&b'Z') => Ok((&s[1..], 0)), - Some(&b'u') | Some(&b'U') => { - if bytes.len() >= 3 { - let (b, c) = (bytes[1], bytes[2]); - match (b | 32, c | 32) { - (b't', b'c') => Ok((&s[3..], 0)), - _ => Err(INVALID), - } - } else { - Err(INVALID) - } - } - _ => timezone_offset(s, colon), - } -} - -/// Same as `timezone_offset` but also allows for `z`/`Z` which is the same as -/// `+00:00`, and allows missing minutes entirely. -pub fn timezone_offset_permissive(s: &str, colon: F) -> ParseResult<(&str, i32)> -where - F: FnMut(&str) -> ParseResult<&str>, -{ - match s.as_bytes().first() { - Some(&b'z') | Some(&b'Z') => Ok((&s[1..], 0)), - _ => timezone_offset_internal(s, colon, true), - } -} - /// Same as `timezone_offset` but also allows for RFC 2822 legacy timezones. /// May return `None` which indicates an insufficient offset data (i.e. `-0000`). -pub fn timezone_offset_2822(s: &str) -> ParseResult<(&str, Option)> { +/// See [RFC 2822 Section 4.3]. +/// +/// [RFC 2822 Section 4.3]: https://tools.ietf.org/html/rfc2822#section-4.3 +pub(super) fn timezone_offset_2822(s: &str) -> ParseResult<(&str, i32)> { // tries to parse legacy time zone names - let upto = s - .as_bytes() - .iter() - .position(|&c| match c { - b'a'...b'z' | b'A'...b'Z' => false, - _ => true, - }) - .unwrap_or_else(|| s.len()); + let upto = s.as_bytes().iter().position(|&c| !c.is_ascii_alphabetic()).unwrap_or(s.len()); if upto > 0 { - let name = &s[..upto]; + let name = &s.as_bytes()[..upto]; let s = &s[upto..]; - let offset_hours = |o| Ok((s, Some(o * 3600))); - if equals(name, "gmt") || equals(name, "ut") { - offset_hours(0) - } else if equals(name, "edt") { - offset_hours(-4) - } else if equals(name, "est") || equals(name, "cdt") { - offset_hours(-5) - } else if equals(name, "cst") || equals(name, "mdt") { - offset_hours(-6) - } else if equals(name, "mst") || equals(name, "pdt") { - offset_hours(-7) - } else if equals(name, "pst") { - offset_hours(-8) - } else { - Ok((s, None)) // recommended by RFC 2822: consume but treat it as -0000 + let offset_hours = |o| Ok((s, o * 3600)); + // RFC 2822 requires support for some named North America timezones, a small subset of all + // named timezones. + if name.eq_ignore_ascii_case(b"gmt") + || name.eq_ignore_ascii_case(b"ut") + || name.eq_ignore_ascii_case(b"z") + { + return offset_hours(0); + } else if name.eq_ignore_ascii_case(b"edt") { + return offset_hours(-4); + } else if name.eq_ignore_ascii_case(b"est") || name.eq_ignore_ascii_case(b"cdt") { + return offset_hours(-5); + } else if name.eq_ignore_ascii_case(b"cst") || name.eq_ignore_ascii_case(b"mdt") { + return offset_hours(-6); + } else if name.eq_ignore_ascii_case(b"mst") || name.eq_ignore_ascii_case(b"pdt") { + return offset_hours(-7); + } else if name.eq_ignore_ascii_case(b"pst") { + return offset_hours(-8); + } else if name.len() == 1 { + if let b'a'..=b'i' | b'k'..=b'y' | b'A'..=b'I' | b'K'..=b'Y' = name[0] { + // recommended by RFC 2822: consume but treat it as -0000 + return Ok((s, 0)); + } } + Err(INVALID) } else { - let (s_, offset) = timezone_offset(s, |s| Ok(s))?; - Ok((s_, Some(offset))) + timezone_offset(s, |s| Ok(s), false, false, false) } } -/// Tries to consume everyting until next whitespace-like symbol. -/// Does not provide any offset information from the consumed data. -pub fn timezone_name_skip(s: &str) -> ParseResult<(&str, ())> { - Ok((s.trim_left_matches(|c: char| !c.is_whitespace()), ())) +/// Tries to consume an RFC2822 comment including preceding ` `. +/// +/// Returns the remaining string after the closing parenthesis. +pub(super) fn comment_2822(s: &str) -> ParseResult<(&str, ())> { + use CommentState::*; + + let s = s.trim_start(); + + let mut state = Start; + for (i, c) in s.bytes().enumerate() { + state = match (state, c) { + (Start, b'(') => Next(1), + (Next(1), b')') => return Ok((&s[i + 1..], ())), + (Next(depth), b'\\') => Escape(depth), + (Next(depth), b'(') => Next(depth + 1), + (Next(depth), b')') => Next(depth - 1), + (Next(depth), _) | (Escape(depth), _) => Next(depth), + _ => return Err(INVALID), + }; + } + + Err(TOO_SHORT) +} + +enum CommentState { + Start, + Next(usize), + Escape(usize), +} + +#[cfg(test)] +mod tests { + use super::{ + comment_2822, nanosecond, nanosecond_fixed, short_or_long_month0, short_or_long_weekday, + timezone_offset_2822, + }; + use crate::Weekday; + use crate::format::{INVALID, TOO_SHORT}; + + #[test] + fn test_rfc2822_comments() { + let testdata = [ + ("", Err(TOO_SHORT)), + (" ", Err(TOO_SHORT)), + ("x", Err(INVALID)), + ("(", Err(TOO_SHORT)), + ("()", Ok("")), + (" \r\n\t()", Ok("")), + ("() ", Ok(" ")), + ("()z", Ok("z")), + ("(x)", Ok("")), + ("(())", Ok("")), + ("((()))", Ok("")), + ("(x(x(x)x)x)", Ok("")), + ("( x ( x ( x ) x ) x )", Ok("")), + (r"(\)", Err(TOO_SHORT)), + (r"(\()", Ok("")), + (r"(\))", Ok("")), + (r"(\\)", Ok("")), + ("(()())", Ok("")), + ("( x ( x ) x ( x ) x )", Ok("")), + ]; + + for (test_in, expected) in testdata.iter() { + let actual = comment_2822(test_in).map(|(s, _)| s); + assert_eq!( + *expected, actual, + "{:?} expected to produce {:?}, but produced {:?}.", + test_in, expected, actual + ); + } + } + + #[test] + fn test_timezone_offset_2822() { + assert_eq!(timezone_offset_2822("cSt").unwrap(), ("", -21600)); + assert_eq!(timezone_offset_2822("pSt").unwrap(), ("", -28800)); + assert_eq!(timezone_offset_2822("mSt").unwrap(), ("", -25200)); + assert_eq!(timezone_offset_2822("-1551").unwrap(), ("", -57060)); + assert_eq!(timezone_offset_2822("Gp"), Err(INVALID)); + } + + #[test] + fn test_short_or_long_month0() { + assert_eq!(short_or_long_month0("JUn").unwrap(), ("", 5)); + assert_eq!(short_or_long_month0("mAy").unwrap(), ("", 4)); + assert_eq!(short_or_long_month0("AuG").unwrap(), ("", 7)); + assert_eq!(short_or_long_month0("Aprâ").unwrap(), ("â", 3)); + assert_eq!(short_or_long_month0("JUl").unwrap(), ("", 6)); + assert_eq!(short_or_long_month0("mAr").unwrap(), ("", 2)); + assert_eq!(short_or_long_month0("Jan").unwrap(), ("", 0)); + } + + #[test] + fn test_short_or_long_weekday() { + assert_eq!(short_or_long_weekday("sAtu").unwrap(), ("u", Weekday::Sat)); + assert_eq!(short_or_long_weekday("thu").unwrap(), ("", Weekday::Thu)); + } + + #[test] + fn test_nanosecond_fixed() { + assert_eq!(nanosecond_fixed("", 0usize).unwrap(), ("", 0)); + assert!(nanosecond_fixed("", 1usize).is_err()); + } + + #[test] + fn test_nanosecond() { + assert_eq!(nanosecond("2Ù").unwrap(), ("Ù", 200000000)); + assert_eq!(nanosecond("8").unwrap(), ("", 800000000)); + } } diff --git a/third_party/rust/chrono/src/format/strftime.rs b/third_party/rust/chrono/src/format/strftime.rs index 93820a2329e..f0478b43aec 100644 --- a/third_party/rust/chrono/src/format/strftime.rs +++ b/third_party/rust/chrono/src/format/strftime.rs @@ -11,10 +11,11 @@ The following specifiers are available both to formatting and parsing. | Spec. | Example | Description | |-------|----------|----------------------------------------------------------------------------| | | | **DATE SPECIFIERS:** | -| `%Y` | `2001` | The full proleptic Gregorian year, zero-padded to 4 digits. [^1] | -| `%C` | `20` | The proleptic Gregorian year divided by 100, zero-padded to 2 digits. [^2] | -| `%y` | `01` | The proleptic Gregorian year modulo 100, zero-padded to 2 digits. [^2] | +| `%Y` | `2001` | The full proleptic Gregorian year, zero-padded to 4 digits. chrono supports years from -262144 to 262143. Note: years before 1 BCE or after 9999 CE, require an initial sign (+/-).| +| `%C` | `20` | The proleptic Gregorian year divided by 100, zero-padded to 2 digits. [^1] | +| `%y` | `01` | The proleptic Gregorian year modulo 100, zero-padded to 2 digits. [^1] | | | | | +| `%q` | `1` | Quarter of year (1-4) | | `%m` | `07` | Month number (01--12), zero-padded to 2 digits. | | `%b` | `Jul` | Abbreviated month name. Always 3 letters. | | `%B` | `July` | Full month name. Also accepts corresponding abbreviation in parsing. | @@ -28,12 +29,12 @@ The following specifiers are available both to formatting and parsing. | `%w` | `0` | Sunday = 0, Monday = 1, ..., Saturday = 6. | | `%u` | `7` | Monday = 1, Tuesday = 2, ..., Sunday = 7. (ISO 8601) | | | | | -| `%U` | `28` | Week number starting with Sunday (00--53), zero-padded to 2 digits. [^3] | +| `%U` | `28` | Week number starting with Sunday (00--53), zero-padded to 2 digits. [^2] | | `%W` | `27` | Same as `%U`, but week 1 starts with the first Monday in that year instead.| | | | | -| `%G` | `2001` | Same as `%Y` but uses the year number in ISO 8601 week date. [^4] | -| `%g` | `01` | Same as `%y` but uses the year number in ISO 8601 week date. [^4] | -| `%V` | `27` | Same as `%U` but uses the week number in ISO 8601 week date (01--53). [^4] | +| `%G` | `2001` | Same as `%Y` but uses the year number in ISO 8601 week date. [^3] | +| `%g` | `01` | Same as `%y` but uses the year number in ISO 8601 week date. [^3] | +| `%V` | `27` | Same as `%U` but uses the week number in ISO 8601 week date (01--53). [^3] | | | | | | `%j` | `189` | Day of the year (001--366), zero-padded to 3 digits. | | | | | @@ -52,32 +53,34 @@ The following specifiers are available both to formatting and parsing. | `%p` | `AM` | `AM` or `PM` in 12-hour clocks. | | | | | | `%M` | `34` | Minute number (00--59), zero-padded to 2 digits. | -| `%S` | `60` | Second number (00--60), zero-padded to 2 digits. [^5] | -| `%f` | `026490000` | The fractional seconds (in nanoseconds) since last whole second. [^8] | -| `%.f` | `.026490`| Similar to `.%f` but left-aligned. These all consume the leading dot. [^8] | -| `%.3f`| `.026` | Similar to `.%f` but left-aligned but fixed to a length of 3. [^8] | -| `%.6f`| `.026490` | Similar to `.%f` but left-aligned but fixed to a length of 6. [^8] | -| `%.9f`| `.026490000` | Similar to `.%f` but left-aligned but fixed to a length of 9. [^8] | -| `%3f` | `026` | Similar to `%.3f` but without the leading dot. [^8] | -| `%6f` | `026490` | Similar to `%.6f` but without the leading dot. [^8] | -| `%9f` | `026490000` | Similar to `%.9f` but without the leading dot. [^8] | +| `%S` | `60` | Second number (00--60), zero-padded to 2 digits. [^4] | +| `%f` | `26490000` | Number of nanoseconds since last whole second. [^7] | +| `%.f` | `.026490`| Decimal fraction of a second. Consumes the leading dot. [^7] | +| `%.3f`| `.026` | Decimal fraction of a second with a fixed length of 3. | +| `%.6f`| `.026490` | Decimal fraction of a second with a fixed length of 6. | +| `%.9f`| `.026490000` | Decimal fraction of a second with a fixed length of 9. | +| `%3f` | `026` | Decimal fraction of a second like `%.3f` but without the leading dot. | +| `%6f` | `026490` | Decimal fraction of a second like `%.6f` but without the leading dot. | +| `%9f` | `026490000` | Decimal fraction of a second like `%.9f` but without the leading dot. | | | | | | `%R` | `00:34` | Hour-minute format. Same as `%H:%M`. | | `%T` | `00:34:60` | Hour-minute-second format. Same as `%H:%M:%S`. | | `%X` | `00:34:60` | Locale's time representation (e.g., 23:13:48). | -| `%r` | `12:34:60 AM` | Hour-minute-second format in 12-hour clocks. Same as `%I:%M:%S %p`. | +| `%r` | `12:34:60 AM` | Locale's 12 hour clock time. (e.g., 11:11:04 PM). Falls back to `%X` if the locale does not have a 12 hour clock format. | | | | | | | | **TIME ZONE SPECIFIERS:** | -| `%Z` | `ACST` | Local time zone name. Skips all non-whitespace characters during parsing. [^9] | +| `%Z` | `ACST` | Local time zone name. Skips all non-whitespace characters during parsing. Identical to `%:z` when formatting. [^8] | | `%z` | `+0930` | Offset from the local time to UTC (with UTC being `+0000`). | | `%:z` | `+09:30` | Same as `%z` but with a colon. | +|`%::z`|`+09:30:00`| Offset from the local time to UTC with seconds. | +|`%:::z`| `+09` | Offset from the local time to UTC without minutes. | | `%#z` | `+09` | *Parsing only:* Same as `%z` but allows minutes to be missing or present. | | | | | | | | **DATE & TIME SPECIFIERS:** | |`%c`|`Sun Jul 8 00:34:60 2001`|Locale's date and time (e.g., Thu Mar 3 23:05:25 2005). | -| `%+` | `2001-07-08T00:34:60.026490+09:30` | ISO 8601 / RFC 3339 date & time format. [^6] | +| `%+` | `2001-07-08T00:34:60.026490+09:30` | ISO 8601 / RFC 3339 date & time format. [^5] | | | | | -| `%s` | `994518299` | UNIX timestamp, the number of seconds since 1970-01-01 00:00 UTC. [^7]| +| `%s` | `994518299` | UNIX timestamp, the number of seconds since 1970-01-01 00:00 UTC. [^6]| | | | | | | | **SPECIAL SPECIFIERS:** | | `%t` | | Literal tab (`\t`). | @@ -95,69 +98,57 @@ Modifier | Description Notes: -[^1]: `%Y`: - Negative years are allowed in formatting but not in parsing. - -[^2]: `%C`, `%y`: +[^1]: `%C`, `%y`: This is floor division, so 100 BCE (year number -99) will print `-1` and `99` respectively. + For `%y`, values greater or equal to 70 are interpreted as being in the 20th century, + values smaller than 70 in the 21st century. -[^3]: `%U`: +[^2]: `%U`: Week 1 starts with the first Sunday in that year. It is possible to have week 0 for days before the first Sunday. -[^4]: `%G`, `%g`, `%V`: +[^3]: `%G`, `%g`, `%V`: Week 1 is the first week with at least 4 days in that year. Week 0 does not exist, so this should be used with `%G` or `%g`. -[^5]: `%S`: +[^4]: `%S`: It accounts for leap seconds, so `60` is possible. -[^6]: `%+`: Same as `%Y-%m-%dT%H:%M:%S%.f%:z`, i.e. 0, 3, 6 or 9 fractional +[^5]: `%+`: Same as `%Y-%m-%dT%H:%M:%S%.f%:z`, i.e. 0, 3, 6 or 9 fractional digits for seconds and colons in the time zone offset.

    + This format also supports having a `Z` or `UTC` in place of `%:z`. They + are equivalent to `+00:00`. +
    +
    + Note that all `T`, `Z`, and `UTC` are parsed case-insensitively. +
    +
    The typical `strftime` implementations have different (and locale-dependent) formats for this specifier. While Chrono's format for `%+` is far more stable, it is best to avoid this specifier if you want to control the exact output. -[^7]: `%s`: +[^6]: `%s`: This is not padded and can be negative. For the purpose of Chrono, it only accounts for non-leap seconds so it slightly differs from ISO C `strftime` behavior. -[^8]: `%f`, `%.f`, `%.3f`, `%.6f`, `%.9f`, `%3f`, `%6f`, `%9f`: +[^7]: `%f`, `%.f`:
    - The default `%f` is right-aligned and always zero-padded to 9 digits - for the compatibility with glibc and others, - so it always counts the number of nanoseconds since the last whole second. - E.g. 7ms after the last second will print `007000000`, - and parsing `7000000` will yield the same. -
    -
    - The variant `%.f` is left-aligned and print 0, 3, 6 or 9 fractional digits - according to the precision. - E.g. 70ms after the last second under `%.f` will print `.070` (note: not `.07`), - and parsing `.07`, `.070000` etc. will yield the same. - Note that they can print or read nothing if the fractional part is zero or - the next character is not `.`. -
    -
    - The variant `%.3f`, `%.6f` and `%.9f` are left-aligned and print 3, 6 or 9 fractional digits - according to the number preceding `f`. - E.g. 70ms after the last second under `%.3f` will print `.070` (note: not `.07`), - and parsing `.07`, `.070000` etc. will yield the same. - Note that they can read nothing if the fractional part is zero or - the next character is not `.` however will print with the specified length. -
    -
    - The variant `%3f`, `%6f` and `%9f` are left-aligned and print 3, 6 or 9 fractional digits - according to the number preceding `f`, but without the leading dot. - E.g. 70ms after the last second under `%3f` will print `070` (note: not `07`), - and parsing `07`, `070000` etc. will yield the same. - Note that they can read nothing if the fractional part is zero. + `%f` and `%.f` are notably different formatting specifiers.
    + `%f` counts the number of nanoseconds since the last whole second, while `%.f` is a fraction of a + second.
    + Example: 7μs is formatted as `7000` with `%f`, and formatted as `.000007` with `%.f`. -[^9]: `%Z`: +[^8]: `%Z`: + Since `chrono` is not aware of timezones beyond their offsets, this specifier + **only prints the offset** when used for formatting. The timezone abbreviation + will NOT be printed. See [this issue](https://github.com/chronotope/chrono/issues/960) + for more information. +
    +
    Offset will not be populated from the parsed data, nor will it be validated. Timezone is completely ignored. Similar to the glibc `strptime` treatment of this format code. @@ -168,136 +159,311 @@ Notes: China Daylight Time. */ -#[cfg(feature = "unstable-locales")] -use super::{locales, Locale}; -use super::{Fixed, InternalFixed, InternalInternal, Item, Numeric, Pad}; +#[cfg(feature = "alloc")] +extern crate alloc; +#[cfg(any(feature = "alloc", feature = "std"))] +use super::{BAD_FORMAT, ParseError}; +use super::{Fixed, InternalInternal, Item, Numeric, Pad}; #[cfg(feature = "unstable-locales")] -type Fmt<'a> = Vec>; -#[cfg(not(feature = "unstable-locales"))] -type Fmt<'a> = &'static [Item<'static>]; - -static D_FMT: &'static [Item<'static>] = - &[num0!(Month), lit!("/"), num0!(Day), lit!("/"), num0!(YearMod100)]; -static D_T_FMT: &'static [Item<'static>] = &[ - fix!(ShortWeekdayName), - sp!(" "), - fix!(ShortMonthName), - sp!(" "), - nums!(Day), - sp!(" "), - num0!(Hour), - lit!(":"), - num0!(Minute), - lit!(":"), - num0!(Second), - sp!(" "), - num0!(Year), -]; -static T_FMT: &'static [Item<'static>] = - &[num0!(Hour), lit!(":"), num0!(Minute), lit!(":"), num0!(Second)]; +use super::{Locale, locales}; +use super::{fixed, internal_fixed, num, num0, nums}; +#[cfg(all(feature = "alloc", not(feature = "std"), not(test)))] +use alloc::vec::Vec; /// Parsing iterator for `strftime`-like format strings. +/// +/// See the [`format::strftime` module](crate::format::strftime) for supported formatting +/// specifiers. +/// +/// `StrftimeItems` is used in combination with more low-level methods such as [`format::parse()`] +/// or [`format_with_items`]. +/// +/// If formatting or parsing date and time values is not performance-critical, the methods +/// [`parse_from_str`] and [`format`] on types such as [`DateTime`](crate::DateTime) are easier to +/// use. +/// +/// [`format`]: crate::DateTime::format +/// [`format_with_items`]: crate::DateTime::format +/// [`parse_from_str`]: crate::DateTime::parse_from_str +/// [`DateTime`]: crate::DateTime +/// [`format::parse()`]: crate::format::parse() #[derive(Clone, Debug)] pub struct StrftimeItems<'a> { /// Remaining portion of the string. remainder: &'a str, /// If the current specifier is composed of multiple formatting items (e.g. `%+`), - /// parser refers to the statically reconstructed slice of them. - /// If `recons` is not empty they have to be returned earlier than the `remainder`. - recons: Fmt<'a>, - /// Date format - d_fmt: Fmt<'a>, - /// Date and time format - d_t_fmt: Fmt<'a>, - /// Time format - t_fmt: Fmt<'a>, + /// `queue` stores a slice of `Item`s that have to be returned one by one. + queue: &'static [Item<'static>], + #[cfg(feature = "unstable-locales")] + locale_str: &'a str, + #[cfg(feature = "unstable-locales")] + locale: Option, } impl<'a> StrftimeItems<'a> { - /// Creates a new parsing iterator from the `strftime`-like format string. - pub fn new(s: &'a str) -> StrftimeItems<'a> { - Self::with_remainer(s) + /// Creates a new parsing iterator from a `strftime`-like format string. + /// + /// # Errors + /// + /// While iterating [`Item::Error`] will be returned if the format string contains an invalid + /// or unrecognized formatting specifier. + /// + /// # Example + /// + /// ``` + /// use chrono::format::*; + /// + /// let strftime_parser = StrftimeItems::new("%F"); // %F: year-month-day (ISO 8601) + /// + /// const ISO8601_YMD_ITEMS: &[Item<'static>] = &[ + /// Item::Numeric(Numeric::Year, Pad::Zero), + /// Item::Literal("-"), + /// Item::Numeric(Numeric::Month, Pad::Zero), + /// Item::Literal("-"), + /// Item::Numeric(Numeric::Day, Pad::Zero), + /// ]; + /// assert!(strftime_parser.eq(ISO8601_YMD_ITEMS.iter().cloned())); + /// ``` + #[must_use] + pub const fn new(s: &'a str) -> StrftimeItems<'a> { + #[cfg(not(feature = "unstable-locales"))] + { + StrftimeItems { remainder: s, queue: &[] } + } + #[cfg(feature = "unstable-locales")] + { + StrftimeItems { remainder: s, queue: &[], locale_str: "", locale: None } + } } - /// Creates a new parsing iterator from the `strftime`-like format string. + /// Creates a new parsing iterator from a `strftime`-like format string, with some formatting + /// specifiers adjusted to match [`Locale`]. + /// + /// Note: `StrftimeItems::new_with_locale` only localizes the *format*. You usually want to + /// combine it with other locale-aware methods such as + /// [`DateTime::format_localized_with_items`] to get things like localized month or day names. + /// + /// The `%x` formatting specifier will use the local date format, `%X` the local time format, + /// and `%c` the local format for date and time. + /// `%r` will use the local 12-hour clock format (e.g., 11:11:04 PM). Not all locales have such + /// a format, in which case we fall back to a 24-hour clock (`%X`). + /// + /// See the [`format::strftime` module](crate::format::strftime) for all supported formatting + /// specifiers. + /// + /// [`DateTime::format_localized_with_items`]: crate::DateTime::format_localized_with_items + /// + /// # Errors + /// + /// While iterating [`Item::Error`] will be returned if the format string contains an invalid + /// or unrecognized formatting specifier. + /// + /// # Example + /// + /// ``` + /// # #[cfg(feature = "alloc")] { + /// use chrono::format::{Locale, StrftimeItems}; + /// use chrono::{FixedOffset, TimeZone}; + /// + /// let dt = FixedOffset::east_opt(9 * 60 * 60) + /// .unwrap() + /// .with_ymd_and_hms(2023, 7, 11, 0, 34, 59) + /// .unwrap(); + /// + /// // Note: you usually want to combine `StrftimeItems::new_with_locale` with other + /// // locale-aware methods such as `DateTime::format_localized_with_items`. + /// // We use the regular `format_with_items` to show only how the formatting changes. + /// + /// let fmtr = dt.format_with_items(StrftimeItems::new_with_locale("%x", Locale::en_US)); + /// assert_eq!(fmtr.to_string(), "07/11/2023"); + /// let fmtr = dt.format_with_items(StrftimeItems::new_with_locale("%x", Locale::ko_KR)); + /// assert_eq!(fmtr.to_string(), "2023년 07월 11일"); + /// let fmtr = dt.format_with_items(StrftimeItems::new_with_locale("%x", Locale::ja_JP)); + /// assert_eq!(fmtr.to_string(), "2023年07月11日"); + /// # } + /// ``` #[cfg(feature = "unstable-locales")] - pub fn new_with_locale(s: &'a str, locale: Locale) -> StrftimeItems<'a> { - let d_fmt = StrftimeItems::new(locales::d_fmt(locale)).collect(); - let d_t_fmt = StrftimeItems::new(locales::d_t_fmt(locale)).collect(); - let t_fmt = StrftimeItems::new(locales::t_fmt(locale)).collect(); - - StrftimeItems { - remainder: s, - recons: Vec::new(), - d_fmt: d_fmt, - d_t_fmt: d_t_fmt, - t_fmt: t_fmt, - } + #[must_use] + pub const fn new_with_locale(s: &'a str, locale: Locale) -> StrftimeItems<'a> { + StrftimeItems { remainder: s, queue: &[], locale_str: "", locale: Some(locale) } } - #[cfg(not(feature = "unstable-locales"))] - fn with_remainer(s: &'a str) -> StrftimeItems<'a> { - static FMT_NONE: &'static [Item<'static>; 0] = &[]; - - StrftimeItems { - remainder: s, - recons: FMT_NONE, - d_fmt: D_FMT, - d_t_fmt: D_T_FMT, - t_fmt: T_FMT, - } + /// Parse format string into a `Vec` of formatting [`Item`]'s. + /// + /// If you need to format or parse multiple values with the same format string, it is more + /// efficient to convert it to a `Vec` of formatting [`Item`]'s than to re-parse the format + /// string on every use. + /// + /// The `format_with_items` methods on [`DateTime`], [`NaiveDateTime`], [`NaiveDate`] and + /// [`NaiveTime`] accept the result for formatting. [`format::parse()`] can make use of it for + /// parsing. + /// + /// [`DateTime`]: crate::DateTime::format_with_items + /// [`NaiveDateTime`]: crate::NaiveDateTime::format_with_items + /// [`NaiveDate`]: crate::NaiveDate::format_with_items + /// [`NaiveTime`]: crate::NaiveTime::format_with_items + /// [`format::parse()`]: crate::format::parse() + /// + /// # Errors + /// + /// Returns an error if the format string contains an invalid or unrecognized formatting + /// specifier. + /// + /// # Example + /// + /// ``` + /// use chrono::format::{parse, Parsed, StrftimeItems}; + /// use chrono::NaiveDate; + /// + /// let fmt_items = StrftimeItems::new("%e %b %Y %k.%M").parse()?; + /// let datetime = NaiveDate::from_ymd_opt(2023, 7, 11).unwrap().and_hms_opt(9, 0, 0).unwrap(); + /// + /// // Formatting + /// assert_eq!( + /// datetime.format_with_items(fmt_items.as_slice().iter()).to_string(), + /// "11 Jul 2023 9.00" + /// ); + /// + /// // Parsing + /// let mut parsed = Parsed::new(); + /// parse(&mut parsed, "11 Jul 2023 9.00", fmt_items.as_slice().iter())?; + /// let parsed_dt = parsed.to_naive_datetime_with_offset(0)?; + /// assert_eq!(parsed_dt, datetime); + /// # Ok::<(), chrono::ParseError>(()) + /// ``` + #[cfg(any(feature = "alloc", feature = "std"))] + pub fn parse(self) -> Result>, ParseError> { + self.into_iter() + .map(|item| match item == Item::Error { + false => Ok(item), + true => Err(BAD_FORMAT), + }) + .collect() } - #[cfg(feature = "unstable-locales")] - fn with_remainer(s: &'a str) -> StrftimeItems<'a> { - StrftimeItems { - remainder: s, - recons: Vec::new(), - d_fmt: D_FMT.to_vec(), - d_t_fmt: D_T_FMT.to_vec(), - t_fmt: T_FMT.to_vec(), - } + /// Parse format string into a `Vec` of [`Item`]'s that contain no references to slices of the + /// format string. + /// + /// A `Vec` created with [`StrftimeItems::parse`] contains references to the format string, + /// binding the lifetime of the `Vec` to that string. [`StrftimeItems::parse_to_owned`] will + /// convert the references to owned types. + /// + /// # Errors + /// + /// Returns an error if the format string contains an invalid or unrecognized formatting + /// specifier. + /// + /// # Example + /// + /// ``` + /// use chrono::format::{Item, ParseError, StrftimeItems}; + /// use chrono::NaiveDate; + /// + /// fn format_items(date_fmt: &str, time_fmt: &str) -> Result>, ParseError> { + /// // `fmt_string` is dropped at the end of this function. + /// let fmt_string = format!("{} {}", date_fmt, time_fmt); + /// StrftimeItems::new(&fmt_string).parse_to_owned() + /// } + /// + /// let fmt_items = format_items("%e %b %Y", "%k.%M")?; + /// let datetime = NaiveDate::from_ymd_opt(2023, 7, 11).unwrap().and_hms_opt(9, 0, 0).unwrap(); + /// + /// assert_eq!( + /// datetime.format_with_items(fmt_items.as_slice().iter()).to_string(), + /// "11 Jul 2023 9.00" + /// ); + /// # Ok::<(), ParseError>(()) + /// ``` + #[cfg(any(feature = "alloc", feature = "std"))] + pub fn parse_to_owned(self) -> Result>, ParseError> { + self.into_iter() + .map(|item| match item == Item::Error { + false => Ok(item.to_owned()), + true => Err(BAD_FORMAT), + }) + .collect() } } -const HAVE_ALTERNATES: &'static str = "z"; +const HAVE_ALTERNATES: &str = "z"; impl<'a> Iterator for StrftimeItems<'a> { type Item = Item<'a>; fn next(&mut self) -> Option> { - // we have some reconstructed items to return - if !self.recons.is_empty() { - let item; - #[cfg(feature = "unstable-locales")] - { - item = self.recons.remove(0); - } - #[cfg(not(feature = "unstable-locales"))] - { - item = self.recons[0].clone(); - self.recons = &self.recons[1..]; - } + // We have items queued to return from a specifier composed of multiple formatting items. + if let Some((item, remainder)) = self.queue.split_first() { + self.queue = remainder; + return Some(item.clone()); + } + + // We are in the middle of parsing the localized formatting string of a specifier. + #[cfg(feature = "unstable-locales")] + if !self.locale_str.is_empty() { + let (remainder, item) = self.parse_next_item(self.locale_str)?; + self.locale_str = remainder; return Some(item); } - match self.remainder.chars().next() { + // Normal: we are parsing the formatting string. + let (remainder, item) = self.parse_next_item(self.remainder)?; + self.remainder = remainder; + Some(item) + } +} + +impl<'a> StrftimeItems<'a> { + fn parse_next_item(&mut self, mut remainder: &'a str) -> Option<(&'a str, Item<'a>)> { + use InternalInternal::*; + use Item::{Literal, Space}; + use Numeric::*; + + static D_FMT: &[Item<'static>] = + &[num0(Month), Literal("/"), num0(Day), Literal("/"), num0(YearMod100)]; + static D_T_FMT: &[Item<'static>] = &[ + fixed(Fixed::ShortWeekdayName), + Space(" "), + fixed(Fixed::ShortMonthName), + Space(" "), + nums(Day), + Space(" "), + num0(Hour), + Literal(":"), + num0(Minute), + Literal(":"), + num0(Second), + Space(" "), + num0(Year), + ]; + static T_FMT: &[Item<'static>] = + &[num0(Hour), Literal(":"), num0(Minute), Literal(":"), num0(Second)]; + static T_FMT_AMPM: &[Item<'static>] = &[ + num0(Hour12), + Literal(":"), + num0(Minute), + Literal(":"), + num0(Second), + Space(" "), + fixed(Fixed::UpperAmPm), + ]; + + match remainder.chars().next() { // we are done None => None, // the next item is a specifier Some('%') => { - self.remainder = &self.remainder[1..]; + remainder = &remainder[1..]; macro_rules! next { () => { - match self.remainder.chars().next() { + match remainder.chars().next() { Some(x) => { - self.remainder = &self.remainder[x.len_utf8()..]; + remainder = &remainder[x.len_utf8()..]; x } - None => return Some(Item::Error), // premature end of string + None => return Some((remainder, Item::Error)), // premature end of string } }; } @@ -312,338 +478,663 @@ impl<'a> Iterator for StrftimeItems<'a> { let is_alternate = spec == '#'; let spec = if pad_override.is_some() || is_alternate { next!() } else { spec }; if is_alternate && !HAVE_ALTERNATES.contains(spec) { - return Some(Item::Error); + return Some((remainder, Item::Error)); } - macro_rules! recons { + macro_rules! queue { [$head:expr, $($tail:expr),+ $(,)*] => ({ - #[cfg(feature = "unstable-locales")] - { - self.recons.clear(); - $(self.recons.push($tail);)+ - } - #[cfg(not(feature = "unstable-locales"))] - { - const RECONS: &'static [Item<'static>] = &[$($tail),+]; - self.recons = RECONS; - } + const QUEUE: &'static [Item<'static>] = &[$($tail),+]; + self.queue = QUEUE; $head }) } - - macro_rules! recons_from_slice { + #[cfg(not(feature = "unstable-locales"))] + macro_rules! queue_from_slice { ($slice:expr) => {{ - #[cfg(feature = "unstable-locales")] - { - self.recons.clear(); - self.recons.extend_from_slice(&$slice[1..]); - } - #[cfg(not(feature = "unstable-locales"))] - { - self.recons = &$slice[1..]; - } + self.queue = &$slice[1..]; $slice[0].clone() }}; } let item = match spec { - 'A' => fix!(LongWeekdayName), - 'B' => fix!(LongMonthName), - 'C' => num0!(YearDiv100), + 'A' => fixed(Fixed::LongWeekdayName), + 'B' => fixed(Fixed::LongMonthName), + 'C' => num0(YearDiv100), 'D' => { - recons![num0!(Month), lit!("/"), num0!(Day), lit!("/"), num0!(YearMod100)] + queue![num0(Month), Literal("/"), num0(Day), Literal("/"), num0(YearMod100)] } - 'F' => recons![num0!(Year), lit!("-"), num0!(Month), lit!("-"), num0!(Day)], - 'G' => num0!(IsoYear), - 'H' => num0!(Hour), - 'I' => num0!(Hour12), - 'M' => num0!(Minute), - 'P' => fix!(LowerAmPm), - 'R' => recons![num0!(Hour), lit!(":"), num0!(Minute)], - 'S' => num0!(Second), - 'T' => recons![num0!(Hour), lit!(":"), num0!(Minute), lit!(":"), num0!(Second)], - 'U' => num0!(WeekFromSun), - 'V' => num0!(IsoWeek), - 'W' => num0!(WeekFromMon), - 'X' => recons_from_slice!(self.t_fmt), - 'Y' => num0!(Year), - 'Z' => fix!(TimezoneName), - 'a' => fix!(ShortWeekdayName), - 'b' | 'h' => fix!(ShortMonthName), - 'c' => recons_from_slice!(self.d_t_fmt), - 'd' => num0!(Day), - 'e' => nums!(Day), - 'f' => num0!(Nanosecond), - 'g' => num0!(IsoYearMod100), - 'j' => num0!(Ordinal), - 'k' => nums!(Hour), - 'l' => nums!(Hour12), - 'm' => num0!(Month), - 'n' => sp!("\n"), - 'p' => fix!(UpperAmPm), - 'r' => recons![ - num0!(Hour12), - lit!(":"), - num0!(Minute), - lit!(":"), - num0!(Second), - sp!(" "), - fix!(UpperAmPm) - ], - 's' => num!(Timestamp), - 't' => sp!("\t"), - 'u' => num!(WeekdayFromMon), - 'v' => { - recons![nums!(Day), lit!("-"), fix!(ShortMonthName), lit!("-"), num0!(Year)] + 'F' => queue![num0(Year), Literal("-"), num0(Month), Literal("-"), num0(Day)], + 'G' => num0(IsoYear), + 'H' => num0(Hour), + 'I' => num0(Hour12), + 'M' => num0(Minute), + 'P' => fixed(Fixed::LowerAmPm), + 'R' => queue![num0(Hour), Literal(":"), num0(Minute)], + 'S' => num0(Second), + 'T' => { + queue![num0(Hour), Literal(":"), num0(Minute), Literal(":"), num0(Second)] } - 'w' => num!(NumDaysFromSun), - 'x' => recons_from_slice!(self.d_fmt), - 'y' => num0!(YearMod100), - 'z' => { - if is_alternate { - internal_fix!(TimezoneOffsetPermissive) + 'U' => num0(WeekFromSun), + 'V' => num0(IsoWeek), + 'W' => num0(WeekFromMon), + #[cfg(not(feature = "unstable-locales"))] + 'X' => queue_from_slice!(T_FMT), + #[cfg(feature = "unstable-locales")] + 'X' => self.switch_to_locale_str(locales::t_fmt, T_FMT), + 'Y' => num0(Year), + 'Z' => fixed(Fixed::TimezoneName), + 'a' => fixed(Fixed::ShortWeekdayName), + 'b' | 'h' => fixed(Fixed::ShortMonthName), + #[cfg(not(feature = "unstable-locales"))] + 'c' => queue_from_slice!(D_T_FMT), + #[cfg(feature = "unstable-locales")] + 'c' => self.switch_to_locale_str(locales::d_t_fmt, D_T_FMT), + 'd' => num0(Day), + 'e' => nums(Day), + 'f' => num0(Nanosecond), + 'g' => num0(IsoYearMod100), + 'j' => num0(Ordinal), + 'k' => nums(Hour), + 'l' => nums(Hour12), + 'm' => num0(Month), + 'n' => Space("\n"), + 'p' => fixed(Fixed::UpperAmPm), + 'q' => num(Quarter), + #[cfg(not(feature = "unstable-locales"))] + 'r' => queue_from_slice!(T_FMT_AMPM), + #[cfg(feature = "unstable-locales")] + 'r' => { + if self.locale.is_some() + && locales::t_fmt_ampm(self.locale.unwrap()).is_empty() + { + // 12-hour clock not supported by this locale. Switch to 24-hour format. + self.switch_to_locale_str(locales::t_fmt, T_FMT) } else { - fix!(TimezoneOffset) + self.switch_to_locale_str(locales::t_fmt_ampm, T_FMT_AMPM) + } + } + 's' => num(Timestamp), + 't' => Space("\t"), + 'u' => num(WeekdayFromMon), + 'v' => { + queue![ + nums(Day), + Literal("-"), + fixed(Fixed::ShortMonthName), + Literal("-"), + num0(Year) + ] + } + 'w' => num(NumDaysFromSun), + #[cfg(not(feature = "unstable-locales"))] + 'x' => queue_from_slice!(D_FMT), + #[cfg(feature = "unstable-locales")] + 'x' => self.switch_to_locale_str(locales::d_fmt, D_FMT), + 'y' => num0(YearMod100), + 'z' => { + if is_alternate { + internal_fixed(TimezoneOffsetPermissive) + } else { + fixed(Fixed::TimezoneOffset) + } + } + '+' => fixed(Fixed::RFC3339), + ':' => { + if remainder.starts_with("::z") { + remainder = &remainder[3..]; + fixed(Fixed::TimezoneOffsetTripleColon) + } else if remainder.starts_with(":z") { + remainder = &remainder[2..]; + fixed(Fixed::TimezoneOffsetDoubleColon) + } else if remainder.starts_with('z') { + remainder = &remainder[1..]; + fixed(Fixed::TimezoneOffsetColon) + } else { + Item::Error } } - '+' => fix!(RFC3339), - ':' => match next!() { - 'z' => fix!(TimezoneOffsetColon), - _ => Item::Error, - }, '.' => match next!() { '3' => match next!() { - 'f' => fix!(Nanosecond3), + 'f' => fixed(Fixed::Nanosecond3), _ => Item::Error, }, '6' => match next!() { - 'f' => fix!(Nanosecond6), + 'f' => fixed(Fixed::Nanosecond6), _ => Item::Error, }, '9' => match next!() { - 'f' => fix!(Nanosecond9), + 'f' => fixed(Fixed::Nanosecond9), _ => Item::Error, }, - 'f' => fix!(Nanosecond), + 'f' => fixed(Fixed::Nanosecond), _ => Item::Error, }, '3' => match next!() { - 'f' => internal_fix!(Nanosecond3NoDot), + 'f' => internal_fixed(Nanosecond3NoDot), _ => Item::Error, }, '6' => match next!() { - 'f' => internal_fix!(Nanosecond6NoDot), + 'f' => internal_fixed(Nanosecond6NoDot), _ => Item::Error, }, '9' => match next!() { - 'f' => internal_fix!(Nanosecond9NoDot), + 'f' => internal_fixed(Nanosecond9NoDot), _ => Item::Error, }, - '%' => lit!("%"), + '%' => Literal("%"), _ => Item::Error, // no such specifier }; - // adjust `item` if we have any padding modifier + // Adjust `item` if we have any padding modifier. + // Not allowed on non-numeric items or on specifiers composed out of multiple + // formatting items. if let Some(new_pad) = pad_override { match item { - Item::Numeric(ref kind, _pad) if self.recons.is_empty() => { - Some(Item::Numeric(kind.clone(), new_pad)) + Item::Numeric(ref kind, _pad) if self.queue.is_empty() => { + Some((remainder, Item::Numeric(kind.clone(), new_pad))) } - _ => Some(Item::Error), // no reconstructed or non-numeric item allowed + _ => Some((remainder, Item::Error)), } } else { - Some(item) + Some((remainder, item)) } } // the next item is space Some(c) if c.is_whitespace() => { // `%` is not a whitespace, so `c != '%'` is redundant - let nextspec = self - .remainder - .find(|c: char| !c.is_whitespace()) - .unwrap_or_else(|| self.remainder.len()); + let nextspec = + remainder.find(|c: char| !c.is_whitespace()).unwrap_or(remainder.len()); assert!(nextspec > 0); - let item = sp!(&self.remainder[..nextspec]); - self.remainder = &self.remainder[nextspec..]; - Some(item) + let item = Space(&remainder[..nextspec]); + remainder = &remainder[nextspec..]; + Some((remainder, item)) } // the next item is literal _ => { - let nextspec = self - .remainder + let nextspec = remainder .find(|c: char| c.is_whitespace() || c == '%') - .unwrap_or_else(|| self.remainder.len()); + .unwrap_or(remainder.len()); assert!(nextspec > 0); - let item = lit!(&self.remainder[..nextspec]); - self.remainder = &self.remainder[nextspec..]; - Some(item) + let item = Literal(&remainder[..nextspec]); + remainder = &remainder[nextspec..]; + Some((remainder, item)) } } } + + #[cfg(feature = "unstable-locales")] + fn switch_to_locale_str( + &mut self, + localized_fmt_str: impl Fn(Locale) -> &'static str, + fallback: &'static [Item<'static>], + ) -> Item<'a> { + if let Some(locale) = self.locale { + assert!(self.locale_str.is_empty()); + let (fmt_str, item) = self.parse_next_item(localized_fmt_str(locale)).unwrap(); + self.locale_str = fmt_str; + item + } else { + self.queue = &fallback[1..]; + fallback[0].clone() + } + } } #[cfg(test)] -#[test] -fn test_strftime_items() { - fn parse_and_collect<'a>(s: &'a str) -> Vec> { - // map any error into `[Item::Error]`. useful for easy testing. - let items = StrftimeItems::new(s); - let items = items.map(|spec| if spec == Item::Error { None } else { Some(spec) }); - items.collect::>>().unwrap_or(vec![Item::Error]) +mod tests { + use super::StrftimeItems; + use crate::format::Item::{self, Literal, Space}; + #[cfg(feature = "unstable-locales")] + use crate::format::Locale; + use crate::format::{Fixed, InternalInternal, Numeric::*}; + use crate::format::{fixed, internal_fixed, num, num0, nums}; + #[cfg(feature = "alloc")] + use crate::{DateTime, FixedOffset, NaiveDate, TimeZone, Timelike, Utc}; + + #[test] + fn test_strftime_items() { + fn parse_and_collect(s: &str) -> Vec> { + // map any error into `[Item::Error]`. useful for easy testing. + eprintln!("test_strftime_items: parse_and_collect({:?})", s); + let items = StrftimeItems::new(s); + let items = items.map(|spec| if spec == Item::Error { None } else { Some(spec) }); + items.collect::>>().unwrap_or_else(|| vec![Item::Error]) + } + + assert_eq!(parse_and_collect(""), []); + assert_eq!(parse_and_collect(" "), [Space(" ")]); + assert_eq!(parse_and_collect(" "), [Space(" ")]); + // ne! + assert_ne!(parse_and_collect(" "), [Space(" "), Space(" ")]); + // eq! + assert_eq!(parse_and_collect(" "), [Space(" ")]); + assert_eq!(parse_and_collect("a"), [Literal("a")]); + assert_eq!(parse_and_collect("ab"), [Literal("ab")]); + assert_eq!(parse_and_collect("😽"), [Literal("😽")]); + assert_eq!(parse_and_collect("a😽"), [Literal("a😽")]); + assert_eq!(parse_and_collect("😽a"), [Literal("😽a")]); + assert_eq!(parse_and_collect(" 😽"), [Space(" "), Literal("😽")]); + assert_eq!(parse_and_collect("😽 "), [Literal("😽"), Space(" ")]); + // ne! + assert_ne!(parse_and_collect("😽😽"), [Literal("😽")]); + assert_ne!(parse_and_collect("😽"), [Literal("😽😽")]); + assert_ne!(parse_and_collect("😽😽"), [Literal("😽😽"), Literal("😽")]); + // eq! + assert_eq!(parse_and_collect("😽😽"), [Literal("😽😽")]); + assert_eq!(parse_and_collect(" \t\n\r "), [Space(" \t\n\r ")]); + assert_eq!(parse_and_collect("hello?"), [Literal("hello?")]); + assert_eq!( + parse_and_collect("a b\t\nc"), + [Literal("a"), Space(" "), Literal("b"), Space("\t\n"), Literal("c")] + ); + assert_eq!(parse_and_collect("100%%"), [Literal("100"), Literal("%")]); + assert_eq!( + parse_and_collect("100%% ok"), + [Literal("100"), Literal("%"), Space(" "), Literal("ok")] + ); + assert_eq!(parse_and_collect("%%PDF-1.0"), [Literal("%"), Literal("PDF-1.0")]); + assert_eq!( + parse_and_collect("%Y-%m-%d"), + [num0(Year), Literal("-"), num0(Month), Literal("-"), num0(Day)] + ); + assert_eq!(parse_and_collect("😽 "), [Literal("😽"), Space(" ")]); + assert_eq!(parse_and_collect("😽😽"), [Literal("😽😽")]); + assert_eq!(parse_and_collect("😽😽😽"), [Literal("😽😽😽")]); + assert_eq!(parse_and_collect("😽😽 😽"), [Literal("😽😽"), Space(" "), Literal("😽")]); + assert_eq!(parse_and_collect("😽😽a 😽"), [Literal("😽😽a"), Space(" "), Literal("😽")]); + assert_eq!(parse_and_collect("😽😽a b😽"), [Literal("😽😽a"), Space(" "), Literal("b😽")]); + assert_eq!( + parse_and_collect("😽😽a b😽c"), + [Literal("😽😽a"), Space(" "), Literal("b😽c")] + ); + assert_eq!(parse_and_collect("😽😽 "), [Literal("😽😽"), Space(" ")]); + assert_eq!(parse_and_collect("😽😽 😽"), [Literal("😽😽"), Space(" "), Literal("😽")]); + assert_eq!(parse_and_collect(" 😽"), [Space(" "), Literal("😽")]); + assert_eq!(parse_and_collect(" 😽 "), [Space(" "), Literal("😽"), Space(" ")]); + assert_eq!( + parse_and_collect(" 😽 😽"), + [Space(" "), Literal("😽"), Space(" "), Literal("😽")] + ); + assert_eq!( + parse_and_collect(" 😽 😽 "), + [Space(" "), Literal("😽"), Space(" "), Literal("😽"), Space(" ")] + ); + assert_eq!( + parse_and_collect(" 😽 😽 "), + [Space(" "), Literal("😽"), Space(" "), Literal("😽"), Space(" ")] + ); + assert_eq!( + parse_and_collect(" 😽 😽😽 "), + [Space(" "), Literal("😽"), Space(" "), Literal("😽😽"), Space(" ")] + ); + assert_eq!(parse_and_collect(" 😽😽"), [Space(" "), Literal("😽😽")]); + assert_eq!(parse_and_collect(" 😽😽 "), [Space(" "), Literal("😽😽"), Space(" ")]); + assert_eq!( + parse_and_collect(" 😽😽 "), + [Space(" "), Literal("😽😽"), Space(" ")] + ); + assert_eq!( + parse_and_collect(" 😽😽 "), + [Space(" "), Literal("😽😽"), Space(" ")] + ); + assert_eq!(parse_and_collect(" 😽😽 "), [Space(" "), Literal("😽😽"), Space(" ")]); + assert_eq!( + parse_and_collect(" 😽 😽😽 "), + [Space(" "), Literal("😽"), Space(" "), Literal("😽😽"), Space(" ")] + ); + assert_eq!( + parse_and_collect(" 😽 😽はい😽 ハンバーガー"), + [ + Space(" "), + Literal("😽"), + Space(" "), + Literal("😽はい😽"), + Space(" "), + Literal("ハンバーガー") + ] + ); + assert_eq!( + parse_and_collect("%%😽%%😽"), + [Literal("%"), Literal("😽"), Literal("%"), Literal("😽")] + ); + assert_eq!(parse_and_collect("%Y--%m"), [num0(Year), Literal("--"), num0(Month)]); + assert_eq!(parse_and_collect("[%F]"), parse_and_collect("[%Y-%m-%d]")); + assert_eq!(parse_and_collect("100%%😽"), [Literal("100"), Literal("%"), Literal("😽")]); + assert_eq!( + parse_and_collect("100%%😽%%a"), + [Literal("100"), Literal("%"), Literal("😽"), Literal("%"), Literal("a")] + ); + assert_eq!(parse_and_collect("😽100%%"), [Literal("😽100"), Literal("%")]); + assert_eq!(parse_and_collect("%m %d"), [num0(Month), Space(" "), num0(Day)]); + assert_eq!(parse_and_collect("%"), [Item::Error]); + assert_eq!(parse_and_collect("%%"), [Literal("%")]); + assert_eq!(parse_and_collect("%%%"), [Item::Error]); + assert_eq!(parse_and_collect("%a"), [fixed(Fixed::ShortWeekdayName)]); + assert_eq!(parse_and_collect("%aa"), [fixed(Fixed::ShortWeekdayName), Literal("a")]); + assert_eq!(parse_and_collect("%%a%"), [Item::Error]); + assert_eq!(parse_and_collect("%😽"), [Item::Error]); + assert_eq!(parse_and_collect("%😽😽"), [Item::Error]); + assert_eq!(parse_and_collect("%%%%"), [Literal("%"), Literal("%")]); + assert_eq!( + parse_and_collect("%%%%ハンバーガー"), + [Literal("%"), Literal("%"), Literal("ハンバーガー")] + ); + assert_eq!(parse_and_collect("foo%?"), [Item::Error]); + assert_eq!(parse_and_collect("bar%42"), [Item::Error]); + assert_eq!(parse_and_collect("quux% +"), [Item::Error]); + assert_eq!(parse_and_collect("%.Z"), [Item::Error]); + assert_eq!(parse_and_collect("%:Z"), [Item::Error]); + assert_eq!(parse_and_collect("%-Z"), [Item::Error]); + assert_eq!(parse_and_collect("%0Z"), [Item::Error]); + assert_eq!(parse_and_collect("%_Z"), [Item::Error]); + assert_eq!(parse_and_collect("%.j"), [Item::Error]); + assert_eq!(parse_and_collect("%:j"), [Item::Error]); + assert_eq!(parse_and_collect("%-j"), [num(Ordinal)]); + assert_eq!(parse_and_collect("%0j"), [num0(Ordinal)]); + assert_eq!(parse_and_collect("%_j"), [nums(Ordinal)]); + assert_eq!(parse_and_collect("%.e"), [Item::Error]); + assert_eq!(parse_and_collect("%:e"), [Item::Error]); + assert_eq!(parse_and_collect("%-e"), [num(Day)]); + assert_eq!(parse_and_collect("%0e"), [num0(Day)]); + assert_eq!(parse_and_collect("%_e"), [nums(Day)]); + assert_eq!(parse_and_collect("%z"), [fixed(Fixed::TimezoneOffset)]); + assert_eq!(parse_and_collect("%:z"), [fixed(Fixed::TimezoneOffsetColon)]); + assert_eq!(parse_and_collect("%Z"), [fixed(Fixed::TimezoneName)]); + assert_eq!(parse_and_collect("%ZZZZ"), [fixed(Fixed::TimezoneName), Literal("ZZZ")]); + assert_eq!(parse_and_collect("%Z😽"), [fixed(Fixed::TimezoneName), Literal("😽")]); + assert_eq!( + parse_and_collect("%#z"), + [internal_fixed(InternalInternal::TimezoneOffsetPermissive)] + ); + assert_eq!(parse_and_collect("%#m"), [Item::Error]); } - assert_eq!(parse_and_collect(""), []); - assert_eq!(parse_and_collect(" \t\n\r "), [sp!(" \t\n\r ")]); - assert_eq!(parse_and_collect("hello?"), [lit!("hello?")]); - assert_eq!( - parse_and_collect("a b\t\nc"), - [lit!("a"), sp!(" "), lit!("b"), sp!("\t\n"), lit!("c")] - ); - assert_eq!(parse_and_collect("100%%"), [lit!("100"), lit!("%")]); - assert_eq!(parse_and_collect("100%% ok"), [lit!("100"), lit!("%"), sp!(" "), lit!("ok")]); - assert_eq!(parse_and_collect("%%PDF-1.0"), [lit!("%"), lit!("PDF-1.0")]); - assert_eq!( - parse_and_collect("%Y-%m-%d"), - [num0!(Year), lit!("-"), num0!(Month), lit!("-"), num0!(Day)] - ); - assert_eq!(parse_and_collect("[%F]"), parse_and_collect("[%Y-%m-%d]")); - assert_eq!(parse_and_collect("%m %d"), [num0!(Month), sp!(" "), num0!(Day)]); - assert_eq!(parse_and_collect("%"), [Item::Error]); - assert_eq!(parse_and_collect("%%"), [lit!("%")]); - assert_eq!(parse_and_collect("%%%"), [Item::Error]); - assert_eq!(parse_and_collect("%%%%"), [lit!("%"), lit!("%")]); - assert_eq!(parse_and_collect("foo%?"), [Item::Error]); - assert_eq!(parse_and_collect("bar%42"), [Item::Error]); - assert_eq!(parse_and_collect("quux% +"), [Item::Error]); - assert_eq!(parse_and_collect("%.Z"), [Item::Error]); - assert_eq!(parse_and_collect("%:Z"), [Item::Error]); - assert_eq!(parse_and_collect("%-Z"), [Item::Error]); - assert_eq!(parse_and_collect("%0Z"), [Item::Error]); - assert_eq!(parse_and_collect("%_Z"), [Item::Error]); - assert_eq!(parse_and_collect("%.j"), [Item::Error]); - assert_eq!(parse_and_collect("%:j"), [Item::Error]); - assert_eq!(parse_and_collect("%-j"), [num!(Ordinal)]); - assert_eq!(parse_and_collect("%0j"), [num0!(Ordinal)]); - assert_eq!(parse_and_collect("%_j"), [nums!(Ordinal)]); - assert_eq!(parse_and_collect("%.e"), [Item::Error]); - assert_eq!(parse_and_collect("%:e"), [Item::Error]); - assert_eq!(parse_and_collect("%-e"), [num!(Day)]); - assert_eq!(parse_and_collect("%0e"), [num0!(Day)]); - assert_eq!(parse_and_collect("%_e"), [nums!(Day)]); - assert_eq!(parse_and_collect("%z"), [fix!(TimezoneOffset)]); - assert_eq!(parse_and_collect("%#z"), [internal_fix!(TimezoneOffsetPermissive)]); - assert_eq!(parse_and_collect("%#m"), [Item::Error]); -} - -#[cfg(test)] -#[test] -fn test_strftime_docs() { - use {FixedOffset, TimeZone, Timelike}; - - let dt = FixedOffset::east(34200).ymd(2001, 7, 8).and_hms_nano(0, 34, 59, 1_026_490_708); - - // date specifiers - assert_eq!(dt.format("%Y").to_string(), "2001"); - assert_eq!(dt.format("%C").to_string(), "20"); - assert_eq!(dt.format("%y").to_string(), "01"); - assert_eq!(dt.format("%m").to_string(), "07"); - assert_eq!(dt.format("%b").to_string(), "Jul"); - assert_eq!(dt.format("%B").to_string(), "July"); - assert_eq!(dt.format("%h").to_string(), "Jul"); - assert_eq!(dt.format("%d").to_string(), "08"); - assert_eq!(dt.format("%e").to_string(), " 8"); - assert_eq!(dt.format("%e").to_string(), dt.format("%_d").to_string()); - assert_eq!(dt.format("%a").to_string(), "Sun"); - assert_eq!(dt.format("%A").to_string(), "Sunday"); - assert_eq!(dt.format("%w").to_string(), "0"); - assert_eq!(dt.format("%u").to_string(), "7"); - assert_eq!(dt.format("%U").to_string(), "28"); - assert_eq!(dt.format("%W").to_string(), "27"); - assert_eq!(dt.format("%G").to_string(), "2001"); - assert_eq!(dt.format("%g").to_string(), "01"); - assert_eq!(dt.format("%V").to_string(), "27"); - assert_eq!(dt.format("%j").to_string(), "189"); - assert_eq!(dt.format("%D").to_string(), "07/08/01"); - assert_eq!(dt.format("%x").to_string(), "07/08/01"); - assert_eq!(dt.format("%F").to_string(), "2001-07-08"); - assert_eq!(dt.format("%v").to_string(), " 8-Jul-2001"); - - // time specifiers - assert_eq!(dt.format("%H").to_string(), "00"); - assert_eq!(dt.format("%k").to_string(), " 0"); - assert_eq!(dt.format("%k").to_string(), dt.format("%_H").to_string()); - assert_eq!(dt.format("%I").to_string(), "12"); - assert_eq!(dt.format("%l").to_string(), "12"); - assert_eq!(dt.format("%l").to_string(), dt.format("%_I").to_string()); - assert_eq!(dt.format("%P").to_string(), "am"); - assert_eq!(dt.format("%p").to_string(), "AM"); - assert_eq!(dt.format("%M").to_string(), "34"); - assert_eq!(dt.format("%S").to_string(), "60"); - assert_eq!(dt.format("%f").to_string(), "026490708"); - assert_eq!(dt.format("%.f").to_string(), ".026490708"); - assert_eq!(dt.with_nanosecond(1_026_490_000).unwrap().format("%.f").to_string(), ".026490"); - assert_eq!(dt.format("%.3f").to_string(), ".026"); - assert_eq!(dt.format("%.6f").to_string(), ".026490"); - assert_eq!(dt.format("%.9f").to_string(), ".026490708"); - assert_eq!(dt.format("%3f").to_string(), "026"); - assert_eq!(dt.format("%6f").to_string(), "026490"); - assert_eq!(dt.format("%9f").to_string(), "026490708"); - assert_eq!(dt.format("%R").to_string(), "00:34"); - assert_eq!(dt.format("%T").to_string(), "00:34:60"); - assert_eq!(dt.format("%X").to_string(), "00:34:60"); - assert_eq!(dt.format("%r").to_string(), "12:34:60 AM"); - - // time zone specifiers - //assert_eq!(dt.format("%Z").to_string(), "ACST"); - assert_eq!(dt.format("%z").to_string(), "+0930"); - assert_eq!(dt.format("%:z").to_string(), "+09:30"); - - // date & time specifiers - assert_eq!(dt.format("%c").to_string(), "Sun Jul 8 00:34:60 2001"); - assert_eq!(dt.format("%+").to_string(), "2001-07-08T00:34:60.026490708+09:30"); - assert_eq!( - dt.with_nanosecond(1_026_490_000).unwrap().format("%+").to_string(), - "2001-07-08T00:34:60.026490+09:30" - ); - assert_eq!(dt.format("%s").to_string(), "994518299"); - - // special specifiers - assert_eq!(dt.format("%t").to_string(), "\t"); - assert_eq!(dt.format("%n").to_string(), "\n"); - assert_eq!(dt.format("%%").to_string(), "%"); -} - -#[cfg(feature = "unstable-locales")] -#[test] -fn test_strftime_docs_localized() { - use {FixedOffset, TimeZone}; - - let dt = FixedOffset::east(34200).ymd(2001, 7, 8).and_hms_nano(0, 34, 59, 1_026_490_708); - - // date specifiers - assert_eq!(dt.format_localized("%b", Locale::fr_BE).to_string(), "jui"); - assert_eq!(dt.format_localized("%B", Locale::fr_BE).to_string(), "juillet"); - assert_eq!(dt.format_localized("%h", Locale::fr_BE).to_string(), "jui"); - assert_eq!(dt.format_localized("%a", Locale::fr_BE).to_string(), "dim"); - assert_eq!(dt.format_localized("%A", Locale::fr_BE).to_string(), "dimanche"); - assert_eq!(dt.format_localized("%D", Locale::fr_BE).to_string(), "07/08/01"); - assert_eq!(dt.format_localized("%x", Locale::fr_BE).to_string(), "08/07/01"); - assert_eq!(dt.format_localized("%F", Locale::fr_BE).to_string(), "2001-07-08"); - assert_eq!(dt.format_localized("%v", Locale::fr_BE).to_string(), " 8-jui-2001"); - - // time specifiers - assert_eq!(dt.format_localized("%P", Locale::fr_BE).to_string(), ""); - assert_eq!(dt.format_localized("%p", Locale::fr_BE).to_string(), ""); - assert_eq!(dt.format_localized("%R", Locale::fr_BE).to_string(), "00:34"); - assert_eq!(dt.format_localized("%T", Locale::fr_BE).to_string(), "00:34:60"); - assert_eq!(dt.format_localized("%X", Locale::fr_BE).to_string(), "00:34:60"); - assert_eq!(dt.format_localized("%r", Locale::fr_BE).to_string(), "12:34:60 "); - - // date & time specifiers - assert_eq!( - dt.format_localized("%c", Locale::fr_BE).to_string(), - "dim 08 jui 2001 00:34:60 +09:30" - ); + #[test] + #[cfg(feature = "alloc")] + fn test_strftime_docs() { + let dt = FixedOffset::east_opt(34200) + .unwrap() + .from_local_datetime( + &NaiveDate::from_ymd_opt(2001, 7, 8) + .unwrap() + .and_hms_nano_opt(0, 34, 59, 1_026_490_708) + .unwrap(), + ) + .unwrap(); + + // date specifiers + assert_eq!(dt.format("%Y").to_string(), "2001"); + assert_eq!(dt.format("%C").to_string(), "20"); + assert_eq!(dt.format("%y").to_string(), "01"); + assert_eq!(dt.format("%q").to_string(), "3"); + assert_eq!(dt.format("%m").to_string(), "07"); + assert_eq!(dt.format("%b").to_string(), "Jul"); + assert_eq!(dt.format("%B").to_string(), "July"); + assert_eq!(dt.format("%h").to_string(), "Jul"); + assert_eq!(dt.format("%d").to_string(), "08"); + assert_eq!(dt.format("%e").to_string(), " 8"); + assert_eq!(dt.format("%e").to_string(), dt.format("%_d").to_string()); + assert_eq!(dt.format("%a").to_string(), "Sun"); + assert_eq!(dt.format("%A").to_string(), "Sunday"); + assert_eq!(dt.format("%w").to_string(), "0"); + assert_eq!(dt.format("%u").to_string(), "7"); + assert_eq!(dt.format("%U").to_string(), "27"); + assert_eq!(dt.format("%W").to_string(), "27"); + assert_eq!(dt.format("%G").to_string(), "2001"); + assert_eq!(dt.format("%g").to_string(), "01"); + assert_eq!(dt.format("%V").to_string(), "27"); + assert_eq!(dt.format("%j").to_string(), "189"); + assert_eq!(dt.format("%D").to_string(), "07/08/01"); + assert_eq!(dt.format("%x").to_string(), "07/08/01"); + assert_eq!(dt.format("%F").to_string(), "2001-07-08"); + assert_eq!(dt.format("%v").to_string(), " 8-Jul-2001"); + + // time specifiers + assert_eq!(dt.format("%H").to_string(), "00"); + assert_eq!(dt.format("%k").to_string(), " 0"); + assert_eq!(dt.format("%k").to_string(), dt.format("%_H").to_string()); + assert_eq!(dt.format("%I").to_string(), "12"); + assert_eq!(dt.format("%l").to_string(), "12"); + assert_eq!(dt.format("%l").to_string(), dt.format("%_I").to_string()); + assert_eq!(dt.format("%P").to_string(), "am"); + assert_eq!(dt.format("%p").to_string(), "AM"); + assert_eq!(dt.format("%M").to_string(), "34"); + assert_eq!(dt.format("%S").to_string(), "60"); + assert_eq!(dt.format("%f").to_string(), "026490708"); + assert_eq!(dt.format("%.f").to_string(), ".026490708"); + assert_eq!(dt.with_nanosecond(1_026_490_000).unwrap().format("%.f").to_string(), ".026490"); + assert_eq!(dt.format("%.3f").to_string(), ".026"); + assert_eq!(dt.format("%.6f").to_string(), ".026490"); + assert_eq!(dt.format("%.9f").to_string(), ".026490708"); + assert_eq!(dt.format("%3f").to_string(), "026"); + assert_eq!(dt.format("%6f").to_string(), "026490"); + assert_eq!(dt.format("%9f").to_string(), "026490708"); + assert_eq!(dt.format("%R").to_string(), "00:34"); + assert_eq!(dt.format("%T").to_string(), "00:34:60"); + assert_eq!(dt.format("%X").to_string(), "00:34:60"); + assert_eq!(dt.format("%r").to_string(), "12:34:60 AM"); + + // time zone specifiers + //assert_eq!(dt.format("%Z").to_string(), "ACST"); + assert_eq!(dt.format("%z").to_string(), "+0930"); + assert_eq!(dt.format("%:z").to_string(), "+09:30"); + assert_eq!(dt.format("%::z").to_string(), "+09:30:00"); + assert_eq!(dt.format("%:::z").to_string(), "+09"); + + // date & time specifiers + assert_eq!(dt.format("%c").to_string(), "Sun Jul 8 00:34:60 2001"); + assert_eq!(dt.format("%+").to_string(), "2001-07-08T00:34:60.026490708+09:30"); + + assert_eq!( + dt.with_timezone(&Utc).format("%+").to_string(), + "2001-07-07T15:04:60.026490708+00:00" + ); + assert_eq!( + dt.with_timezone(&Utc), + DateTime::parse_from_str("2001-07-07T15:04:60.026490708Z", "%+").unwrap() + ); + assert_eq!( + dt.with_timezone(&Utc), + DateTime::parse_from_str("2001-07-07T15:04:60.026490708UTC", "%+").unwrap() + ); + assert_eq!( + dt.with_timezone(&Utc), + DateTime::parse_from_str("2001-07-07t15:04:60.026490708utc", "%+").unwrap() + ); + + assert_eq!( + dt.with_nanosecond(1_026_490_000).unwrap().format("%+").to_string(), + "2001-07-08T00:34:60.026490+09:30" + ); + assert_eq!(dt.format("%s").to_string(), "994518299"); + + // special specifiers + assert_eq!(dt.format("%t").to_string(), "\t"); + assert_eq!(dt.format("%n").to_string(), "\n"); + assert_eq!(dt.format("%%").to_string(), "%"); + + // complex format specifiers + assert_eq!(dt.format(" %Y%d%m%%%%%t%H%M%S\t").to_string(), " 20010807%%\t003460\t"); + assert_eq!( + dt.format(" %Y%d%m%%%%%t%H:%P:%M%S%:::z\t").to_string(), + " 20010807%%\t00:am:3460+09\t" + ); + } + + #[test] + #[cfg(all(feature = "unstable-locales", feature = "alloc"))] + fn test_strftime_docs_localized() { + let dt = FixedOffset::east_opt(34200) + .unwrap() + .with_ymd_and_hms(2001, 7, 8, 0, 34, 59) + .unwrap() + .with_nanosecond(1_026_490_708) + .unwrap(); + + // date specifiers + assert_eq!(dt.format_localized("%b", Locale::fr_BE).to_string(), "jui"); + assert_eq!(dt.format_localized("%B", Locale::fr_BE).to_string(), "juillet"); + assert_eq!(dt.format_localized("%h", Locale::fr_BE).to_string(), "jui"); + assert_eq!(dt.format_localized("%a", Locale::fr_BE).to_string(), "dim"); + assert_eq!(dt.format_localized("%A", Locale::fr_BE).to_string(), "dimanche"); + assert_eq!(dt.format_localized("%D", Locale::fr_BE).to_string(), "07/08/01"); + assert_eq!(dt.format_localized("%x", Locale::fr_BE).to_string(), "08/07/01"); + assert_eq!(dt.format_localized("%F", Locale::fr_BE).to_string(), "2001-07-08"); + assert_eq!(dt.format_localized("%v", Locale::fr_BE).to_string(), " 8-jui-2001"); + + // time specifiers + assert_eq!(dt.format_localized("%P", Locale::fr_BE).to_string(), ""); + assert_eq!(dt.format_localized("%p", Locale::fr_BE).to_string(), ""); + assert_eq!(dt.format_localized("%R", Locale::fr_BE).to_string(), "00:34"); + assert_eq!(dt.format_localized("%T", Locale::fr_BE).to_string(), "00:34:60"); + assert_eq!(dt.format_localized("%X", Locale::fr_BE).to_string(), "00:34:60"); + assert_eq!(dt.format_localized("%r", Locale::fr_BE).to_string(), "00:34:60"); + + // date & time specifiers + assert_eq!( + dt.format_localized("%c", Locale::fr_BE).to_string(), + "dim 08 jui 2001 00:34:60 +09:30" + ); + + let nd = NaiveDate::from_ymd_opt(2001, 7, 8).unwrap(); + + // date specifiers + assert_eq!(nd.format_localized("%b", Locale::de_DE).to_string(), "Jul"); + assert_eq!(nd.format_localized("%B", Locale::de_DE).to_string(), "Juli"); + assert_eq!(nd.format_localized("%h", Locale::de_DE).to_string(), "Jul"); + assert_eq!(nd.format_localized("%a", Locale::de_DE).to_string(), "So"); + assert_eq!(nd.format_localized("%A", Locale::de_DE).to_string(), "Sonntag"); + assert_eq!(nd.format_localized("%D", Locale::de_DE).to_string(), "07/08/01"); + assert_eq!(nd.format_localized("%x", Locale::de_DE).to_string(), "08.07.2001"); + assert_eq!(nd.format_localized("%F", Locale::de_DE).to_string(), "2001-07-08"); + assert_eq!(nd.format_localized("%v", Locale::de_DE).to_string(), " 8-Jul-2001"); + } + + /// Ensure parsing a timestamp with the parse-only stftime formatter "%#z" does + /// not cause a panic. + /// + /// See . + #[test] + #[cfg(feature = "alloc")] + fn test_parse_only_timezone_offset_permissive_no_panic() { + use crate::NaiveDate; + use crate::{FixedOffset, TimeZone}; + use std::fmt::Write; + + let dt = FixedOffset::east_opt(34200) + .unwrap() + .from_local_datetime( + &NaiveDate::from_ymd_opt(2001, 7, 8) + .unwrap() + .and_hms_nano_opt(0, 34, 59, 1_026_490_708) + .unwrap(), + ) + .unwrap(); + + let mut buf = String::new(); + let _ = write!(buf, "{}", dt.format("%#z")).expect_err("parse-only formatter should fail"); + } + + #[test] + #[cfg(all(feature = "unstable-locales", feature = "alloc"))] + fn test_strftime_localized_korean() { + let dt = FixedOffset::east_opt(34200) + .unwrap() + .with_ymd_and_hms(2001, 7, 8, 0, 34, 59) + .unwrap() + .with_nanosecond(1_026_490_708) + .unwrap(); + + // date specifiers + assert_eq!(dt.format_localized("%b", Locale::ko_KR).to_string(), " 7월"); + assert_eq!(dt.format_localized("%B", Locale::ko_KR).to_string(), "7월"); + assert_eq!(dt.format_localized("%h", Locale::ko_KR).to_string(), " 7월"); + assert_eq!(dt.format_localized("%a", Locale::ko_KR).to_string(), "일"); + assert_eq!(dt.format_localized("%A", Locale::ko_KR).to_string(), "일요일"); + assert_eq!(dt.format_localized("%D", Locale::ko_KR).to_string(), "07/08/01"); + assert_eq!(dt.format_localized("%x", Locale::ko_KR).to_string(), "2001년 07월 08일"); + assert_eq!(dt.format_localized("%F", Locale::ko_KR).to_string(), "2001-07-08"); + assert_eq!(dt.format_localized("%v", Locale::ko_KR).to_string(), " 8- 7월-2001"); + assert_eq!(dt.format_localized("%r", Locale::ko_KR).to_string(), "오전 12시 34분 60초"); + + // date & time specifiers + assert_eq!( + dt.format_localized("%c", Locale::ko_KR).to_string(), + "2001년 07월 08일 (일) 오전 12시 34분 60초" + ); + } + + #[test] + #[cfg(all(feature = "unstable-locales", feature = "alloc"))] + fn test_strftime_localized_japanese() { + let dt = FixedOffset::east_opt(34200) + .unwrap() + .with_ymd_and_hms(2001, 7, 8, 0, 34, 59) + .unwrap() + .with_nanosecond(1_026_490_708) + .unwrap(); + + // date specifiers + assert_eq!(dt.format_localized("%b", Locale::ja_JP).to_string(), " 7月"); + assert_eq!(dt.format_localized("%B", Locale::ja_JP).to_string(), "7月"); + assert_eq!(dt.format_localized("%h", Locale::ja_JP).to_string(), " 7月"); + assert_eq!(dt.format_localized("%a", Locale::ja_JP).to_string(), "日"); + assert_eq!(dt.format_localized("%A", Locale::ja_JP).to_string(), "日曜日"); + assert_eq!(dt.format_localized("%D", Locale::ja_JP).to_string(), "07/08/01"); + assert_eq!(dt.format_localized("%x", Locale::ja_JP).to_string(), "2001年07月08日"); + assert_eq!(dt.format_localized("%F", Locale::ja_JP).to_string(), "2001-07-08"); + assert_eq!(dt.format_localized("%v", Locale::ja_JP).to_string(), " 8- 7月-2001"); + assert_eq!(dt.format_localized("%r", Locale::ja_JP).to_string(), "午前12時34分60秒"); + + // date & time specifiers + assert_eq!( + dt.format_localized("%c", Locale::ja_JP).to_string(), + "2001年07月08日 00時34分60秒" + ); + } + + #[test] + #[cfg(all(feature = "unstable-locales", feature = "alloc"))] + fn test_strftime_localized_time() { + let dt1 = Utc.with_ymd_and_hms(2024, 2, 9, 6, 54, 32).unwrap(); + let dt2 = Utc.with_ymd_and_hms(2024, 2, 9, 18, 54, 32).unwrap(); + // Some of these locales gave issues before pure-rust-locales 0.8.0 with chrono 0.4.27+ + assert_eq!(dt1.format_localized("%X", Locale::nl_NL).to_string(), "06:54:32"); + assert_eq!(dt2.format_localized("%X", Locale::nl_NL).to_string(), "18:54:32"); + assert_eq!(dt1.format_localized("%X", Locale::en_US).to_string(), "06:54:32 AM"); + assert_eq!(dt2.format_localized("%X", Locale::en_US).to_string(), "06:54:32 PM"); + assert_eq!(dt1.format_localized("%X", Locale::hy_AM).to_string(), "06:54:32"); + assert_eq!(dt2.format_localized("%X", Locale::hy_AM).to_string(), "18:54:32"); + assert_eq!(dt1.format_localized("%X", Locale::chr_US).to_string(), "06:54:32 ᏌᎾᎴ"); + assert_eq!(dt2.format_localized("%X", Locale::chr_US).to_string(), "06:54:32 ᏒᎯᏱᎢᏗᏢ"); + } + + #[test] + #[cfg(all(feature = "unstable-locales", target_pointer_width = "64"))] + fn test_type_sizes() { + use core::mem::size_of; + assert_eq!(size_of::(), 24); + assert_eq!(size_of::(), 56); + assert_eq!(size_of::(), 2); + } + + #[test] + #[cfg(all(feature = "unstable-locales", target_pointer_width = "32"))] + fn test_type_sizes() { + use core::mem::size_of; + assert_eq!(size_of::(), 12); + assert_eq!(size_of::(), 28); + assert_eq!(size_of::(), 2); + } + + #[test] + #[cfg(any(feature = "alloc", feature = "std"))] + fn test_strftime_parse() { + let fmt_str = StrftimeItems::new("%Y-%m-%dT%H:%M:%S%z"); + let fmt_items = fmt_str.parse().unwrap(); + let dt = Utc.with_ymd_and_hms(2014, 5, 7, 12, 34, 56).unwrap(); + assert_eq!(&dt.format_with_items(fmt_items.iter()).to_string(), "2014-05-07T12:34:56+0000"); + } } diff --git a/third_party/rust/chrono/src/lib.rs b/third_party/rust/chrono/src/lib.rs index 9d66ae32499..a7c603a3e9c 100644 --- a/third_party/rust/chrono/src/lib.rs +++ b/third_party/rust/chrono/src/lib.rs @@ -1,186 +1,220 @@ -// This is a part of Chrono. -// See README.md and LICENSE.txt for details. - //! # Chrono: Date and Time for Rust //! -//! It aims to be a feature-complete superset of -//! the [time](https://github.com/rust-lang-deprecated/time) library. -//! In particular, +//! Chrono aims to provide all functionality needed to do correct operations on dates and times in +//! the [proleptic Gregorian calendar]: //! -//! * Chrono strictly adheres to ISO 8601. -//! * Chrono is timezone-aware by default, with separate timezone-naive types. -//! * Chrono is space-optimal and (while not being the primary goal) reasonably efficient. +//! * The [`DateTime`] type is timezone-aware by default, with separate timezone-naive types. +//! * Operations that may produce an invalid or ambiguous date and time return `Option` or +//! [`MappedLocalTime`]. +//! * Configurable parsing and formatting with a `strftime` inspired date and time formatting +//! syntax. +//! * The [`Local`] timezone works with the current timezone of the OS. +//! * Types and operations are implemented to be reasonably efficient. //! -//! There were several previous attempts to bring a good date and time library to Rust, -//! which Chrono builds upon and should acknowledge: +//! Timezone data is not shipped with chrono by default to limit binary sizes. Use the companion +//! crate [Chrono-TZ] or [`tzfile`] for full timezone support. //! -//! * [Initial research on -//! the wiki](https://github.com/rust-lang/rust-wiki-backup/blob/master/Lib-datetime.md) -//! * Dietrich Epp's [datetime-rs](https://github.com/depp/datetime-rs) -//! * Luis de Bethencourt's [rust-datetime](https://github.com/luisbg/rust-datetime) -//! -//! Any significant changes to Chrono are documented in -//! the [`CHANGELOG.md`](https://github.com/chronotope/chrono/blob/main/CHANGELOG.md) file. -//! -//! ## Usage -//! -//! Put this in your `Cargo.toml`: -//! -//! ```toml -//! [dependencies] -//! chrono = "0.4" -//! ``` +//! [proleptic Gregorian calendar]: https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar +//! [Chrono-TZ]: https://crates.io/crates/chrono-tz +//! [`tzfile`]: https://crates.io/crates/tzfile //! //! ### Features //! -//! Chrono supports various runtime environments and operating systems, and has -//! several features that may be enabled or disabled. +//! Chrono supports various runtime environments and operating systems, and has several features +//! that may be enabled or disabled. //! //! Default features: //! -//! - `alloc`: Enable features that depend on allocation (primarily string formatting) -//! - `std`: Enables functionality that depends on the standard library. This -//! is a superset of `alloc` and adds interoperation with standard library types -//! and traits. -//! - `clock`: enables reading the system time (`now`), independent of whether -//! `std::time::SystemTime` is present, depends on having a libc. +//! - `alloc`: Enable features that depend on allocation (primarily string formatting). +//! - `std`: Enables functionality that depends on the standard library. This is a superset of +//! `alloc` and adds interoperation with standard library types and traits. +//! - `clock`: Enables reading the local timezone (`Local`). This is a superset of `now`. +//! - `now`: Enables reading the system time (`now`). +//! - `wasmbind`: Interface with the JS Date API for the `wasm32` target. //! //! Optional features: //! -//! - `wasmbind`: Enable integration with [wasm-bindgen][] and its `js-sys` project -//! - [`serde`][]: Enable serialization/deserialization via serde. -//! - `unstable-locales`: Enable localization. This adds various methods with a -//! `_localized` suffix. The implementation and API may change or even be -//! removed in a patch release. Feedback welcome. +//! - `serde`: Enable serialization/deserialization via [serde]. +//! - `rkyv`: Deprecated, use the `rkyv-*` features. +//! - `rkyv-16`: Enable serialization/deserialization via [rkyv], +//! using 16-bit integers for integral `*size` types. +//! - `rkyv-32`: Enable serialization/deserialization via [rkyv], +//! using 32-bit integers for integral `*size` types. +//! - `rkyv-64`: Enable serialization/deserialization via [rkyv], +//! using 64-bit integers for integral `*size` types. +//! - `rkyv-validation`: Enable rkyv validation support using `bytecheck`. +//! - `arbitrary`: Construct arbitrary instances of a type with the Arbitrary crate. +//! - `unstable-locales`: Enable localization. This adds various methods with a `_localized` suffix. +//! The implementation and API may change or even be removed in a patch release. Feedback welcome. +//! - `oldtime`: This feature no longer has any effect; it used to offer compatibility with the +//! `time` 0.1 crate. //! -//! [`serde`]: https://github.com/serde-rs/serde -//! [wasm-bindgen]: https://github.com/rustwasm/wasm-bindgen +//! Note: The `rkyv{,-16,-32,-64}` features are mutually exclusive. //! -//! See the [cargo docs][] for examples of specifying features. +//! See the [cargo docs] for examples of specifying features. //! +//! [serde]: https://github.com/serde-rs/serde +//! [rkyv]: https://github.com/rkyv/rkyv //! [cargo docs]: https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#choosing-features //! //! ## Overview //! -//! ### Duration +//! ### Time delta / Duration //! -//! Chrono currently uses its own [`Duration`] type to represent the magnitude -//! of a time span. Since this has the same name as the newer, standard type for -//! duration, the reference will refer this type as `OldDuration`. +//! Chrono has a [`TimeDelta`] type to represent the magnitude of a time span. This is an "accurate" +//! duration represented as seconds and nanoseconds, and does not represent "nominal" components +//! such as days or months. //! -//! Note that this is an "accurate" duration represented as seconds and -//! nanoseconds and does not represent "nominal" components such as days or -//! months. +//! The [`TimeDelta`] type was previously named `Duration` (and is still available as a type alias +//! with that name). A notable difference with the similar [`core::time::Duration`] is that it is a +//! signed value instead of unsigned. //! -//! When the `oldtime` feature is enabled, [`Duration`] is an alias for the -//! [`time::Duration`](https://docs.rs/time/0.1.40/time/struct.Duration.html) -//! type from v0.1 of the time crate. time v0.1 is deprecated, so new code -//! should disable the `oldtime` feature and use the `chrono::Duration` type -//! instead. The `oldtime` feature is enabled by default for backwards -//! compatibility, but future versions of Chrono are likely to remove the -//! feature entirely. -//! -//! Chrono does not yet natively support -//! the standard [`Duration`](https://doc.rust-lang.org/std/time/struct.Duration.html) type, -//! but it will be supported in the future. -//! Meanwhile you can convert between two types with -//! [`Duration::from_std`](https://docs.rs/time/0.1.40/time/struct.Duration.html#method.from_std) -//! and -//! [`Duration::to_std`](https://docs.rs/time/0.1.40/time/struct.Duration.html#method.to_std) +//! Chrono currently only supports a small number of operations with [`core::time::Duration`]. +//! You can convert between both types with the [`TimeDelta::from_std`] and [`TimeDelta::to_std`] //! methods. //! //! ### Date and Time //! -//! Chrono provides a -//! [**`DateTime`**](./struct.DateTime.html) -//! type to represent a date and a time in a timezone. +//! Chrono provides a [`DateTime`] type to represent a date and a time in a timezone. //! -//! For more abstract moment-in-time tracking such as internal timekeeping -//! that is unconcerned with timezones, consider -//! [`time::SystemTime`](https://doc.rust-lang.org/std/time/struct.SystemTime.html), -//! which tracks your system clock, or -//! [`time::Instant`](https://doc.rust-lang.org/std/time/struct.Instant.html), which -//! is an opaque but monotonically-increasing representation of a moment in time. +//! For more abstract moment-in-time tracking such as internal timekeeping that is unconcerned with +//! timezones, consider [`std::time::SystemTime`], which tracks your system clock, or +//! [`std::time::Instant`], which is an opaque but monotonically-increasing representation of a +//! moment in time. //! -//! `DateTime` is timezone-aware and must be constructed from -//! the [**`TimeZone`**](./offset/trait.TimeZone.html) object, -//! which defines how the local date is converted to and back from the UTC date. -//! There are three well-known `TimeZone` implementations: +//! [`DateTime`] is timezone-aware and must be constructed from a [`TimeZone`] object, which defines +//! how the local date is converted to and back from the UTC date. +//! There are three well-known [`TimeZone`] implementations: //! -//! * [**`Utc`**](./offset/struct.Utc.html) specifies the UTC time zone. It is most efficient. +//! * [`Utc`] specifies the UTC time zone. It is most efficient. //! -//! * [**`Local`**](./offset/struct.Local.html) specifies the system local time zone. +//! * [`Local`] specifies the system local time zone. //! -//! * [**`FixedOffset`**](./offset/struct.FixedOffset.html) specifies -//! an arbitrary, fixed time zone such as UTC+09:00 or UTC-10:30. -//! This often results from the parsed textual date and time. -//! Since it stores the most information and does not depend on the system environment, -//! you would want to normalize other `TimeZone`s into this type. +//! * [`FixedOffset`] specifies an arbitrary, fixed time zone such as UTC+09:00 or UTC-10:30. +//! This often results from the parsed textual date and time. Since it stores the most information +//! and does not depend on the system environment, you would want to normalize other `TimeZone`s +//! into this type. //! -//! `DateTime`s with different `TimeZone` types are distinct and do not mix, -//! but can be converted to each other using -//! the [`DateTime::with_timezone`](./struct.DateTime.html#method.with_timezone) method. +//! [`DateTime`]s with different [`TimeZone`] types are distinct and do not mix, but can be +//! converted to each other using the [`DateTime::with_timezone`] method. //! -//! You can get the current date and time in the UTC time zone -//! ([`Utc::now()`](./offset/struct.Utc.html#method.now)) -//! or in the local time zone -//! ([`Local::now()`](./offset/struct.Local.html#method.now)). +//! You can get the current date and time in the UTC time zone ([`Utc::now()`]) or in the local time +//! zone ([`Local::now()`]). //! -//! ```rust +//! ``` +//! # #[cfg(feature = "now")] { //! use chrono::prelude::*; //! -//! let utc: DateTime = Utc::now(); // e.g. `2014-11-28T12:45:59.324310806Z` -//! let local: DateTime = Local::now(); // e.g. `2014-11-28T21:45:59.324310806+09:00` -//! # let _ = utc; let _ = local; +//! let utc: DateTime = Utc::now(); // e.g. `2014-11-28T12:45:59.324310806Z` +//! # let _ = utc; +//! # } //! ``` //! -//! Alternatively, you can create your own date and time. -//! This is a bit verbose due to Rust's lack of function and method overloading, -//! but in turn we get a rich combination of initialization methods. -//! -//! ```rust +//! ``` +//! # #[cfg(feature = "clock")] { //! use chrono::prelude::*; -//! use chrono::offset::LocalResult; //! -//! let dt = Utc.ymd(2014, 7, 8).and_hms(9, 10, 11); // `2014-07-08T09:10:11Z` +//! let local: DateTime = Local::now(); // e.g. `2014-11-28T21:45:59.324310806+09:00` +//! # let _ = local; +//! # } +//! ``` +//! +//! Alternatively, you can create your own date and time. This is a bit verbose due to Rust's lack +//! of function and method overloading, but in turn we get a rich combination of initialization +//! methods. +//! +//! ``` +//! use chrono::offset::MappedLocalTime; +//! use chrono::prelude::*; +//! +//! # fn doctest() -> Option<()> { +//! +//! let dt = Utc.with_ymd_and_hms(2014, 7, 8, 9, 10, 11).unwrap(); // `2014-07-08T09:10:11Z` +//! assert_eq!( +//! dt, +//! NaiveDate::from_ymd_opt(2014, 7, 8)? +//! .and_hms_opt(9, 10, 11)? +//! .and_utc() +//! ); +//! //! // July 8 is 188th day of the year 2014 (`o` for "ordinal") -//! assert_eq!(dt, Utc.yo(2014, 189).and_hms(9, 10, 11)); +//! assert_eq!(dt, NaiveDate::from_yo_opt(2014, 189)?.and_hms_opt(9, 10, 11)?.and_utc()); //! // July 8 is Tuesday in ISO week 28 of the year 2014. -//! assert_eq!(dt, Utc.isoywd(2014, 28, Weekday::Tue).and_hms(9, 10, 11)); +//! assert_eq!( +//! dt, +//! NaiveDate::from_isoywd_opt(2014, 28, Weekday::Tue)?.and_hms_opt(9, 10, 11)?.and_utc() +//! ); //! -//! let dt = Utc.ymd(2014, 7, 8).and_hms_milli(9, 10, 11, 12); // `2014-07-08T09:10:11.012Z` -//! assert_eq!(dt, Utc.ymd(2014, 7, 8).and_hms_micro(9, 10, 11, 12_000)); -//! assert_eq!(dt, Utc.ymd(2014, 7, 8).and_hms_nano(9, 10, 11, 12_000_000)); +//! let dt = NaiveDate::from_ymd_opt(2014, 7, 8)? +//! .and_hms_milli_opt(9, 10, 11, 12)? +//! .and_utc(); // `2014-07-08T09:10:11.012Z` +//! assert_eq!( +//! dt, +//! NaiveDate::from_ymd_opt(2014, 7, 8)? +//! .and_hms_micro_opt(9, 10, 11, 12_000)? +//! .and_utc() +//! ); +//! assert_eq!( +//! dt, +//! NaiveDate::from_ymd_opt(2014, 7, 8)? +//! .and_hms_nano_opt(9, 10, 11, 12_000_000)? +//! .and_utc() +//! ); //! //! // dynamic verification -//! assert_eq!(Utc.ymd_opt(2014, 7, 8).and_hms_opt(21, 15, 33), -//! LocalResult::Single(Utc.ymd(2014, 7, 8).and_hms(21, 15, 33))); -//! assert_eq!(Utc.ymd_opt(2014, 7, 8).and_hms_opt(80, 15, 33), LocalResult::None); -//! assert_eq!(Utc.ymd_opt(2014, 7, 38).and_hms_opt(21, 15, 33), LocalResult::None); +//! assert_eq!( +//! Utc.with_ymd_and_hms(2014, 7, 8, 21, 15, 33), +//! MappedLocalTime::Single( +//! NaiveDate::from_ymd_opt(2014, 7, 8)?.and_hms_opt(21, 15, 33)?.and_utc() +//! ) +//! ); +//! assert_eq!(Utc.with_ymd_and_hms(2014, 7, 8, 80, 15, 33), MappedLocalTime::None); +//! assert_eq!(Utc.with_ymd_and_hms(2014, 7, 38, 21, 15, 33), MappedLocalTime::None); //! +//! # #[cfg(feature = "clock")] { //! // other time zone objects can be used to construct a local datetime. //! // obviously, `local_dt` is normally different from `dt`, but `fixed_dt` should be identical. -//! let local_dt = Local.ymd(2014, 7, 8).and_hms_milli(9, 10, 11, 12); -//! let fixed_dt = FixedOffset::east(9 * 3600).ymd(2014, 7, 8).and_hms_milli(18, 10, 11, 12); +//! let local_dt = Local +//! .from_local_datetime( +//! &NaiveDate::from_ymd_opt(2014, 7, 8).unwrap().and_hms_milli_opt(9, 10, 11, 12).unwrap(), +//! ) +//! .unwrap(); +//! let fixed_dt = FixedOffset::east_opt(9 * 3600) +//! .unwrap() +//! .from_local_datetime( +//! &NaiveDate::from_ymd_opt(2014, 7, 8) +//! .unwrap() +//! .and_hms_milli_opt(18, 10, 11, 12) +//! .unwrap(), +//! ) +//! .unwrap(); //! assert_eq!(dt, fixed_dt); //! # let _ = local_dt; +//! # } +//! # Some(()) +//! # } +//! # doctest().unwrap(); //! ``` //! -//! Various properties are available to the date and time, and can be altered individually. -//! Most of them are defined in the traits [`Datelike`](./trait.Datelike.html) and -//! [`Timelike`](./trait.Timelike.html) which you should `use` before. +//! Various properties are available to the date and time, and can be altered individually. Most of +//! them are defined in the traits [`Datelike`] and [`Timelike`] which you should `use` before. //! Addition and subtraction is also supported. //! The following illustrates most supported operations to the date and time: //! //! ```rust -//! # extern crate chrono; -//! -//! # fn main() { //! use chrono::prelude::*; -//! use chrono::Duration; +//! use chrono::TimeDelta; //! //! // assume this returned `2014-11-28T21:45:59.324310806+09:00`: -//! let dt = FixedOffset::east(9*3600).ymd(2014, 11, 28).and_hms_nano(21, 45, 59, 324310806); +//! let dt = FixedOffset::east_opt(9 * 3600) +//! .unwrap() +//! .from_local_datetime( +//! &NaiveDate::from_ymd_opt(2014, 11, 28) +//! .unwrap() +//! .and_hms_nano_opt(21, 45, 59, 324310806) +//! .unwrap(), +//! ) +//! .unwrap(); //! //! // property accessors //! assert_eq!((dt.year(), dt.month(), dt.day()), (2014, 11, 28)); @@ -193,8 +227,15 @@ //! //! // time zone accessor and manipulation //! assert_eq!(dt.offset().fix().local_minus_utc(), 9 * 3600); -//! assert_eq!(dt.timezone(), FixedOffset::east(9 * 3600)); -//! assert_eq!(dt.with_timezone(&Utc), Utc.ymd(2014, 11, 28).and_hms_nano(12, 45, 59, 324310806)); +//! assert_eq!(dt.timezone(), FixedOffset::east_opt(9 * 3600).unwrap()); +//! assert_eq!( +//! dt.with_timezone(&Utc), +//! NaiveDate::from_ymd_opt(2014, 11, 28) +//! .unwrap() +//! .and_hms_nano_opt(12, 45, 59, 324310806) +//! .unwrap() +//! .and_utc() +//! ); //! //! // a sample of property manipulations (validates dynamically) //! assert_eq!(dt.with_day(29).unwrap().weekday(), Weekday::Sat); // 2014-11-29 is Saturday @@ -202,91 +243,98 @@ //! assert_eq!(dt.with_year(-300).unwrap().num_days_from_ce(), -109606); // November 29, 301 BCE //! //! // arithmetic operations -//! let dt1 = Utc.ymd(2014, 11, 14).and_hms(8, 9, 10); -//! let dt2 = Utc.ymd(2014, 11, 14).and_hms(10, 9, 8); -//! assert_eq!(dt1.signed_duration_since(dt2), Duration::seconds(-2 * 3600 + 2)); -//! assert_eq!(dt2.signed_duration_since(dt1), Duration::seconds(2 * 3600 - 2)); -//! assert_eq!(Utc.ymd(1970, 1, 1).and_hms(0, 0, 0) + Duration::seconds(1_000_000_000), -//! Utc.ymd(2001, 9, 9).and_hms(1, 46, 40)); -//! assert_eq!(Utc.ymd(1970, 1, 1).and_hms(0, 0, 0) - Duration::seconds(1_000_000_000), -//! Utc.ymd(1938, 4, 24).and_hms(22, 13, 20)); -//! # } +//! let dt1 = Utc.with_ymd_and_hms(2014, 11, 14, 8, 9, 10).unwrap(); +//! let dt2 = Utc.with_ymd_and_hms(2014, 11, 14, 10, 9, 8).unwrap(); +//! assert_eq!(dt1.signed_duration_since(dt2), TimeDelta::try_seconds(-2 * 3600 + 2).unwrap()); +//! assert_eq!(dt2.signed_duration_since(dt1), TimeDelta::try_seconds(2 * 3600 - 2).unwrap()); +//! assert_eq!( +//! Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap() +//! + TimeDelta::try_seconds(1_000_000_000).unwrap(), +//! Utc.with_ymd_and_hms(2001, 9, 9, 1, 46, 40).unwrap() +//! ); +//! assert_eq!( +//! Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap() +//! - TimeDelta::try_seconds(1_000_000_000).unwrap(), +//! Utc.with_ymd_and_hms(1938, 4, 24, 22, 13, 20).unwrap() +//! ); //! ``` //! //! ### Formatting and Parsing //! -//! Formatting is done via the [`format`](./struct.DateTime.html#method.format) method, -//! which format is equivalent to the familiar `strftime` format. +//! Formatting is done via the [`format`](DateTime::format()) method, which format is equivalent to +//! the familiar `strftime` format. //! -//! See [`format::strftime`](./format/strftime/index.html#specifiers) -//! documentation for full syntax and list of specifiers. +//! See [`format::strftime`](format::strftime#specifiers) documentation for full syntax and list of +//! specifiers. //! //! The default `to_string` method and `{:?}` specifier also give a reasonable representation. -//! Chrono also provides [`to_rfc2822`](./struct.DateTime.html#method.to_rfc2822) and -//! [`to_rfc3339`](./struct.DateTime.html#method.to_rfc3339) methods -//! for well-known formats. +//! Chrono also provides [`to_rfc2822`](DateTime::to_rfc2822) and +//! [`to_rfc3339`](DateTime::to_rfc3339) methods for well-known formats. //! -//! Chrono now also provides date formatting in almost any language without the -//! help of an additional C library. This functionality is under the feature -//! `unstable-locales`: +//! Chrono now also provides date formatting in almost any language without the help of an +//! additional C library. This functionality is under the feature `unstable-locales`: //! -//! ```text -//! chrono { version = "0.4", features = ["unstable-locales"] +//! ```toml +//! chrono = { version = "0.4", features = ["unstable-locales"] } //! ``` //! //! The `unstable-locales` feature requires and implies at least the `alloc` feature. //! //! ```rust +//! # #[allow(unused_imports)] //! use chrono::prelude::*; //! -//! let dt = Utc.ymd(2014, 11, 28).and_hms(12, 0, 9); +//! # #[cfg(all(feature = "unstable-locales", feature = "alloc"))] +//! # fn test() { +//! let dt = Utc.with_ymd_and_hms(2014, 11, 28, 12, 0, 9).unwrap(); //! assert_eq!(dt.format("%Y-%m-%d %H:%M:%S").to_string(), "2014-11-28 12:00:09"); //! assert_eq!(dt.format("%a %b %e %T %Y").to_string(), "Fri Nov 28 12:00:09 2014"); -//! assert_eq!(dt.format_localized("%A %e %B %Y, %T", Locale::fr_BE).to_string(), "vendredi 28 novembre 2014, 12:00:09"); -//! assert_eq!(dt.format("%a %b %e %T %Y").to_string(), dt.format("%c").to_string()); +//! assert_eq!( +//! dt.format_localized("%A %e %B %Y, %T", Locale::fr_BE).to_string(), +//! "vendredi 28 novembre 2014, 12:00:09" +//! ); //! +//! assert_eq!(dt.format("%a %b %e %T %Y").to_string(), dt.format("%c").to_string()); //! assert_eq!(dt.to_string(), "2014-11-28 12:00:09 UTC"); //! assert_eq!(dt.to_rfc2822(), "Fri, 28 Nov 2014 12:00:09 +0000"); //! assert_eq!(dt.to_rfc3339(), "2014-11-28T12:00:09+00:00"); //! assert_eq!(format!("{:?}", dt), "2014-11-28T12:00:09Z"); //! //! // Note that milli/nanoseconds are only printed if they are non-zero -//! let dt_nano = Utc.ymd(2014, 11, 28).and_hms_nano(12, 0, 9, 1); +//! let dt_nano = NaiveDate::from_ymd_opt(2014, 11, 28) +//! .unwrap() +//! .and_hms_nano_opt(12, 0, 9, 1) +//! .unwrap() +//! .and_utc(); //! assert_eq!(format!("{:?}", dt_nano), "2014-11-28T12:00:09.000000001Z"); +//! # } +//! # #[cfg(not(all(feature = "unstable-locales", feature = "alloc")))] +//! # fn test() {} +//! # if cfg!(all(feature = "unstable-locales", feature = "alloc")) { +//! # test(); +//! # } //! ``` //! -//! Parsing can be done with three methods: +//! Parsing can be done with two methods: //! -//! 1. The standard [`FromStr`](https://doc.rust-lang.org/std/str/trait.FromStr.html) trait -//! (and [`parse`](https://doc.rust-lang.org/std/primitive.str.html#method.parse) method -//! on a string) can be used for parsing `DateTime`, `DateTime` and -//! `DateTime` values. This parses what the `{:?}` -//! ([`std::fmt::Debug`](https://doc.rust-lang.org/std/fmt/trait.Debug.html)) -//! format specifier prints, and requires the offset to be present. +//! 1. The standard [`FromStr`](std::str::FromStr) trait (and [`parse`](str::parse) method on a +//! string) can be used for parsing `DateTime`, `DateTime` and +//! `DateTime` values. This parses what the `{:?}` ([`std::fmt::Debug`] format specifier +//! prints, and requires the offset to be present. //! -//! 2. [`DateTime::parse_from_str`](./struct.DateTime.html#method.parse_from_str) parses -//! a date and time with offsets and returns `DateTime`. -//! This should be used when the offset is a part of input and the caller cannot guess that. -//! It *cannot* be used when the offset can be missing. -//! [`DateTime::parse_from_rfc2822`](./struct.DateTime.html#method.parse_from_rfc2822) -//! and -//! [`DateTime::parse_from_rfc3339`](./struct.DateTime.html#method.parse_from_rfc3339) -//! are similar but for well-known formats. +//! 2. [`DateTime::parse_from_str`] parses a date and time with offsets and returns +//! `DateTime`. This should be used when the offset is a part of input and the +//! caller cannot guess that. It *cannot* be used when the offset can be missing. +//! [`DateTime::parse_from_rfc2822`] and [`DateTime::parse_from_rfc3339`] are similar but for +//! well-known formats. //! -//! 3. [`Offset::datetime_from_str`](./offset/trait.TimeZone.html#method.datetime_from_str) is -//! similar but returns `DateTime` of given offset. -//! When the explicit offset is missing from the input, it simply uses given offset. -//! It issues an error when the input contains an explicit offset different -//! from the current offset. -//! -//! More detailed control over the parsing process is available via -//! [`format`](./format/index.html) module. +//! More detailed control over the parsing process is available via [`format`](mod@format) module. //! //! ```rust //! use chrono::prelude::*; //! -//! let dt = Utc.ymd(2014, 11, 28).and_hms(12, 0, 9); -//! let fixed_dt = dt.with_timezone(&FixedOffset::east(9*3600)); +//! let dt = Utc.with_ymd_and_hms(2014, 11, 28, 12, 0, 9).unwrap(); +//! let fixed_dt = dt.with_timezone(&FixedOffset::east_opt(9 * 3600).unwrap()); //! //! // method 1 //! assert_eq!("2014-11-28T12:00:09Z".parse::>(), Ok(dt.clone())); @@ -294,239 +342,354 @@ //! assert_eq!("2014-11-28T21:00:09+09:00".parse::>(), Ok(fixed_dt.clone())); //! //! // method 2 -//! assert_eq!(DateTime::parse_from_str("2014-11-28 21:00:09 +09:00", "%Y-%m-%d %H:%M:%S %z"), -//! Ok(fixed_dt.clone())); -//! assert_eq!(DateTime::parse_from_rfc2822("Fri, 28 Nov 2014 21:00:09 +0900"), -//! Ok(fixed_dt.clone())); +//! assert_eq!( +//! DateTime::parse_from_str("2014-11-28 21:00:09 +09:00", "%Y-%m-%d %H:%M:%S %z"), +//! Ok(fixed_dt.clone()) +//! ); +//! assert_eq!( +//! DateTime::parse_from_rfc2822("Fri, 28 Nov 2014 21:00:09 +0900"), +//! Ok(fixed_dt.clone()) +//! ); //! assert_eq!(DateTime::parse_from_rfc3339("2014-11-28T21:00:09+09:00"), Ok(fixed_dt.clone())); //! -//! // method 3 -//! assert_eq!(Utc.datetime_from_str("2014-11-28 12:00:09", "%Y-%m-%d %H:%M:%S"), Ok(dt.clone())); -//! assert_eq!(Utc.datetime_from_str("Fri Nov 28 12:00:09 2014", "%a %b %e %T %Y"), Ok(dt.clone())); -//! //! // oops, the year is missing! -//! assert!(Utc.datetime_from_str("Fri Nov 28 12:00:09", "%a %b %e %T %Y").is_err()); +//! assert!(DateTime::parse_from_str("Fri Nov 28 12:00:09", "%a %b %e %T %Y").is_err()); //! // oops, the format string does not include the year at all! -//! assert!(Utc.datetime_from_str("Fri Nov 28 12:00:09", "%a %b %e %T").is_err()); +//! assert!(DateTime::parse_from_str("Fri Nov 28 12:00:09", "%a %b %e %T").is_err()); //! // oops, the weekday is incorrect! -//! assert!(Utc.datetime_from_str("Sat Nov 28 12:00:09 2014", "%a %b %e %T %Y").is_err()); +//! assert!(DateTime::parse_from_str("Sat Nov 28 12:00:09 2014", "%a %b %e %T %Y").is_err()); //! ``` //! -//! Again : See [`format::strftime`](./format/strftime/index.html#specifiers) -//! documentation for full syntax and list of specifiers. +//! Again: See [`format::strftime`](format::strftime#specifiers) documentation for full syntax and +//! list of specifiers. //! //! ### Conversion from and to EPOCH timestamps //! -//! Use [`Utc.timestamp(seconds, nanoseconds)`](./offset/trait.TimeZone.html#method.timestamp) -//! to construct a [`DateTime`](./struct.DateTime.html) from a UNIX timestamp +//! Use [`DateTime::from_timestamp(seconds, nanoseconds)`](DateTime::from_timestamp) +//! to construct a [`DateTime`] from a UNIX timestamp //! (seconds, nanoseconds that passed since January 1st 1970). //! -//! Use [`DateTime.timestamp`](./struct.DateTime.html#method.timestamp) to get the timestamp (in seconds) -//! from a [`DateTime`](./struct.DateTime.html). Additionally, you can use -//! [`DateTime.timestamp_subsec_nanos`](./struct.DateTime.html#method.timestamp_subsec_nanos) +//! Use [`DateTime.timestamp`](DateTime::timestamp) to get the timestamp (in seconds) +//! from a [`DateTime`]. Additionally, you can use +//! [`DateTime.timestamp_subsec_nanos`](DateTime::timestamp_subsec_nanos) //! to get the number of additional number of nanoseconds. //! -//! ```rust +//! ``` +//! # #[cfg(feature = "alloc")] { //! // We need the trait in scope to use Utc::timestamp(). -//! use chrono::{DateTime, TimeZone, Utc}; +//! use chrono::{DateTime, Utc}; //! //! // Construct a datetime from epoch: -//! let dt = Utc.timestamp(1_500_000_000, 0); +//! let dt: DateTime = DateTime::from_timestamp(1_500_000_000, 0).unwrap(); //! assert_eq!(dt.to_rfc2822(), "Fri, 14 Jul 2017 02:40:00 +0000"); //! //! // Get epoch value from a datetime: //! let dt = DateTime::parse_from_rfc2822("Fri, 14 Jul 2017 02:40:00 +0000").unwrap(); //! assert_eq!(dt.timestamp(), 1_500_000_000); +//! # } //! ``` //! -//! ### Individual date -//! -//! Chrono also provides an individual date type ([**`Date`**](./struct.Date.html)). -//! It also has time zones attached, and have to be constructed via time zones. -//! Most operations available to `DateTime` are also available to `Date` whenever appropriate. -//! -//! ```rust -//! use chrono::prelude::*; -//! use chrono::offset::LocalResult; -//! -//! # // these *may* fail, but only very rarely. just rerun the test if you were that unfortunate ;) -//! assert_eq!(Utc::today(), Utc::now().date()); -//! assert_eq!(Local::today(), Local::now().date()); -//! -//! assert_eq!(Utc.ymd(2014, 11, 28).weekday(), Weekday::Fri); -//! assert_eq!(Utc.ymd_opt(2014, 11, 31), LocalResult::None); -//! assert_eq!(Utc.ymd(2014, 11, 28).and_hms_milli(7, 8, 9, 10).format("%H%M%S").to_string(), -//! "070809"); -//! ``` -//! -//! There is no timezone-aware `Time` due to the lack of usefulness and also the complexity. -//! -//! `DateTime` has [`date`](./struct.DateTime.html#method.date) method -//! which returns a `Date` which represents its date component. -//! There is also a [`time`](./struct.DateTime.html#method.time) method, -//! which simply returns a naive local time described below. -//! //! ### Naive date and time //! -//! Chrono provides naive counterparts to `Date`, (non-existent) `Time` and `DateTime` -//! as [**`NaiveDate`**](./naive/struct.NaiveDate.html), -//! [**`NaiveTime`**](./naive/struct.NaiveTime.html) and -//! [**`NaiveDateTime`**](./naive/struct.NaiveDateTime.html) respectively. +//! Chrono provides naive counterparts to `Date`, (non-existent) `Time` and `DateTime` as +//! [`NaiveDate`], [`NaiveTime`] and [`NaiveDateTime`] respectively. //! -//! They have almost equivalent interfaces as their timezone-aware twins, -//! but are not associated to time zones obviously and can be quite low-level. -//! They are mostly useful for building blocks for higher-level types. +//! They have almost equivalent interfaces as their timezone-aware twins, but are not associated to +//! time zones obviously and can be quite low-level. They are mostly useful for building blocks for +//! higher-level types. //! //! Timezone-aware `DateTime` and `Date` types have two methods returning naive versions: -//! [`naive_local`](./struct.DateTime.html#method.naive_local) returns -//! a view to the naive local time, -//! and [`naive_utc`](./struct.DateTime.html#method.naive_utc) returns -//! a view to the naive UTC time. +//! [`naive_local`](DateTime::naive_local) returns a view to the naive local time, +//! and [`naive_utc`](DateTime::naive_utc) returns a view to the naive UTC time. //! //! ## Limitations //! -//! Only proleptic Gregorian calendar (i.e. extended to support older dates) is supported. -//! Be very careful if you really have to deal with pre-20C dates, they can be in Julian or others. +//! * Only the proleptic Gregorian calendar (i.e. extended to support older dates) is supported. +//! * Date types are limited to about +/- 262,000 years from the common epoch. +//! * Time types are limited to nanosecond accuracy. +//! * Leap seconds can be represented, but Chrono does not fully support them. +//! See [Leap Second Handling](NaiveTime#leap-second-handling). //! -//! Date types are limited in about +/- 262,000 years from the common epoch. -//! Time types are limited in the nanosecond accuracy. +//! ## Rust version requirements //! -//! [Leap seconds are supported in the representation but -//! Chrono doesn't try to make use of them](./naive/struct.NaiveTime.html#leap-second-handling). -//! (The main reason is that leap seconds are not really predictable.) -//! Almost *every* operation over the possible leap seconds will ignore them. -//! Consider using `NaiveDateTime` with the implicit TAI (International Atomic Time) scale -//! if you want. +//! The Minimum Supported Rust Version (MSRV) is currently **Rust 1.61.0**. //! -//! Chrono inherently does not support an inaccurate or partial date and time representation. -//! Any operation that can be ambiguous will return `None` in such cases. -//! For example, "a month later" of 2014-01-30 is not well-defined -//! and consequently `Utc.ymd(2014, 1, 30).with_month(2)` returns `None`. +//! The MSRV is explicitly tested in CI. It may be bumped in minor releases, but this is not done +//! lightly. //! -//! Non ISO week handling is not yet supported. -//! For now you can use the [chrono_ext](https://crates.io/crates/chrono_ext) -//! crate ([sources](https://github.com/bcourtine/chrono-ext/)). +//! ## Relation between chrono and time 0.1 //! -//! Advanced time zone handling is not yet supported. -//! For now you can try the [Chrono-tz](https://github.com/chronotope/chrono-tz/) crate instead. +//! Rust first had a `time` module added to `std` in its 0.7 release. It later moved to +//! `libextra`, and then to a `libtime` library shipped alongside the standard library. In 2014 +//! work on chrono started in order to provide a full-featured date and time library in Rust. +//! Some improvements from chrono made it into the standard library; notably, `chrono::Duration` +//! was included as `std::time::Duration` ([rust#15934]) in 2014. +//! +//! In preparation of Rust 1.0 at the end of 2014 `libtime` was moved out of the Rust distro and +//! into the `time` crate to eventually be redesigned ([rust#18832], [rust#18858]), like the +//! `num` and `rand` crates. Of course chrono kept its dependency on this `time` crate. `time` +//! started re-exporting `std::time::Duration` during this period. Later, the standard library was +//! changed to have a more limited unsigned `Duration` type ([rust#24920], [RFC 1040]), while the +//! `time` crate kept the full functionality with `time::Duration`. `time::Duration` had been a +//! part of chrono's public API. +//! +//! By 2016 `time` 0.1 lived under the `rust-lang-deprecated` organisation and was not actively +//! maintained ([time#136]). chrono absorbed the platform functionality and `Duration` type of the +//! `time` crate in [chrono#478] (the work started in [chrono#286]). In order to preserve +//! compatibility with downstream crates depending on `time` and `chrono` sharing a `Duration` +//! type, chrono kept depending on time 0.1. chrono offered the option to opt out of the `time` +//! dependency by disabling the `oldtime` feature (swapping it out for an effectively similar +//! chrono type). In 2019, @jhpratt took over maintenance on the `time` crate and released what +//! amounts to a new crate as `time` 0.2. +//! +//! [rust#15934]: https://github.com/rust-lang/rust/pull/15934 +//! [rust#18832]: https://github.com/rust-lang/rust/pull/18832#issuecomment-62448221 +//! [rust#18858]: https://github.com/rust-lang/rust/pull/18858 +//! [rust#24920]: https://github.com/rust-lang/rust/pull/24920 +//! [RFC 1040]: https://rust-lang.github.io/rfcs/1040-duration-reform.html +//! [time#136]: https://github.com/time-rs/time/issues/136 +//! [chrono#286]: https://github.com/chronotope/chrono/pull/286 +//! [chrono#478]: https://github.com/chronotope/chrono/pull/478 +//! +//! ## Security advisories +//! +//! In November of 2020 [CVE-2020-26235] and [RUSTSEC-2020-0071] were opened against the `time` crate. +//! @quininer had found that calls to `localtime_r` may be unsound ([chrono#499]). Eventually, almost +//! a year later, this was also made into a security advisory against chrono as [RUSTSEC-2020-0159], +//! which had platform code similar to `time`. +//! +//! On Unix-like systems a process is given a timezone id or description via the `TZ` environment +//! variable. We need this timezone data to calculate the current local time from a value that is +//! in UTC, such as the time from the system clock. `time` 0.1 and chrono used the POSIX function +//! `localtime_r` to do the conversion to local time, which reads the `TZ` variable. +//! +//! Rust assumes the environment to be writable and uses locks to access it from multiple threads. +//! Some other programming languages and libraries use similar locking strategies, but these are +//! typically not shared across languages. More importantly, POSIX declares modifying the +//! environment in a multi-threaded process as unsafe, and `getenv` in libc can't be changed to +//! take a lock because it returns a pointer to the data (see [rust#27970] for more discussion). +//! +//! Since version 4.20 chrono no longer uses `localtime_r`, instead using Rust code to query the +//! timezone (from the `TZ` variable or via `iana-time-zone` as a fallback) and work with data +//! from the system timezone database directly. The code for this was forked from the [tz-rs crate] +//! by @x-hgg-x. As such, chrono now respects the Rust lock when reading the `TZ` environment +//! variable. In general, code should avoid modifying the environment. +//! +//! [CVE-2020-26235]: https://nvd.nist.gov/vuln/detail/CVE-2020-26235 +//! [RUSTSEC-2020-0071]: https://rustsec.org/advisories/RUSTSEC-2020-0071 +//! [chrono#499]: https://github.com/chronotope/chrono/pull/499 +//! [RUSTSEC-2020-0159]: https://rustsec.org/advisories/RUSTSEC-2020-0159.html +//! [rust#27970]: https://github.com/rust-lang/rust/issues/27970 +//! [chrono#677]: https://github.com/chronotope/chrono/pull/677 +//! [tz-rs crate]: https://crates.io/crates/tz-rs +//! +//! ## Removing time 0.1 +//! +//! Because time 0.1 has been unmaintained for years, however, the security advisory mentioned +//! above has not been addressed. While chrono maintainers were careful not to break backwards +//! compatibility with the `time::Duration` type, there has been a long stream of issues from +//! users inquiring about the time 0.1 dependency with the vulnerability. We investigated the +//! potential breakage of removing the time 0.1 dependency in [chrono#1095] using a crater-like +//! experiment and determined that the potential for breaking (public) dependencies is very low. +//! We reached out to those few crates that did still depend on compatibility with time 0.1. +//! +//! As such, for chrono 0.4.30 we have decided to swap out the time 0.1 `Duration` implementation +//! for a local one that will offer a strict superset of the existing API going forward. This +//! will prevent most downstream users from being affected by the security vulnerability in time +//! 0.1 while minimizing the ecosystem impact of semver-incompatible version churn. +//! +//! [chrono#1095]: https://github.com/chronotope/chrono/pull/1095 -#![doc(html_root_url = "https://docs.rs/chrono/latest/")] -#![cfg_attr(feature = "bench", feature(test))] // lib stability features as per RFC #507 +#![doc(html_root_url = "https://docs.rs/chrono/latest/", test(attr(deny(warnings))))] #![deny(missing_docs)] #![deny(missing_debug_implementations)] -#![deny(dead_code)] -// lints are added all the time, we test on 1.13 -#![allow(unknown_lints)] +#![warn(unreachable_pub)] +#![deny(clippy::tests_outside_test_module)] #![cfg_attr(not(any(feature = "std", test)), no_std)] -#![cfg_attr(feature = "cargo-clippy", allow( - renamed_and_removed_lints, - // The explicit 'static lifetimes are still needed for rustc 1.13-16 - // backward compatibility, and this appeases clippy. If minimum rustc - // becomes 1.17, should be able to remove this, those 'static lifetimes, - // and use `static` in a lot of places `const` is used now. - redundant_static_lifetimes, - // Similarly, redundant_field_names lints on not using the - // field-init-shorthand, which was stabilized in rust 1.17. - redundant_field_names, - // Changing trivially_copy_pass_by_ref would require an incompatible version - // bump. - trivially_copy_pass_by_ref, - try_err, - // Currently deprecated, we use the separate implementation to add docs - // warning that putting a time in a hash table is probably a bad idea - derive_hash_xor_eq, -))] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] #[cfg(feature = "alloc")] extern crate alloc; -#[cfg(all(feature = "std", not(feature = "alloc")))] -extern crate std as alloc; -#[cfg(any(feature = "std", test))] -extern crate std as core; -#[cfg(feature = "oldtime")] -extern crate time as oldtime; -#[cfg(not(feature = "oldtime"))] -mod oldtime; - -#[cfg(feature = "clock")] -extern crate libc; -#[cfg(all(feature = "clock", windows))] -extern crate winapi; -#[cfg(all( - feature = "clock", - not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")) -))] -mod sys; - -extern crate num_integer; -extern crate num_traits; -#[cfg(feature = "rustc-serialize")] -extern crate rustc_serialize; -#[cfg(feature = "serde")] -extern crate serde as serdelib; -#[cfg(feature = "__doctest")] -#[cfg_attr(feature = "__doctest", cfg(doctest))] -#[macro_use] -extern crate doc_comment; -#[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind"))] -extern crate js_sys; -#[cfg(feature = "unstable-locales")] -extern crate pure_rust_locales; -#[cfg(feature = "bench")] -extern crate test; -#[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind"))] -extern crate wasm_bindgen; - -#[cfg(feature = "__doctest")] -#[cfg_attr(feature = "__doctest", cfg(doctest))] -doctest!("../README.md"); - -// this reexport is to aid the transition and should not be in the prelude! -pub use oldtime::Duration; - -pub use date::{Date, MAX_DATE, MIN_DATE}; -#[cfg(feature = "rustc-serialize")] -pub use datetime::rustc_serialize::TsSeconds; -pub use datetime::{DateTime, SecondsFormat, MAX_DATETIME, MIN_DATETIME}; -/// L10n locales. -#[cfg(feature = "unstable-locales")] -pub use format::Locale; -pub use format::{ParseError, ParseResult}; +mod time_delta; +#[cfg(feature = "std")] #[doc(no_inline)] -pub use naive::{IsoWeek, NaiveDate, NaiveDateTime, NaiveTime}; -#[cfg(feature = "clock")] -#[doc(no_inline)] -pub use offset::Local; -#[doc(no_inline)] -pub use offset::{FixedOffset, LocalResult, Offset, TimeZone, Utc}; -pub use round::{DurationRound, RoundingError, SubsecRound}; +pub use time_delta::OutOfRangeError; +pub use time_delta::TimeDelta; + +/// Alias of [`TimeDelta`]. +pub type Duration = TimeDelta; + +use core::fmt; /// A convenience module appropriate for glob imports (`use chrono::prelude::*;`). pub mod prelude { - #[doc(no_inline)] - pub use Date; + #[allow(deprecated)] + pub use crate::Date; #[cfg(feature = "clock")] - #[doc(no_inline)] - pub use Local; - #[cfg(feature = "unstable-locales")] - #[doc(no_inline)] - pub use Locale; - #[doc(no_inline)] - pub use SubsecRound; - #[doc(no_inline)] - pub use {DateTime, SecondsFormat}; - #[doc(no_inline)] - pub use {Datelike, Month, Timelike, Weekday}; - #[doc(no_inline)] - pub use {FixedOffset, Utc}; - #[doc(no_inline)] - pub use {NaiveDate, NaiveDateTime, NaiveTime}; - #[doc(no_inline)] - pub use {Offset, TimeZone}; + pub use crate::Local; + #[cfg(all(feature = "unstable-locales", feature = "alloc"))] + pub use crate::Locale; + pub use crate::SubsecRound; + pub use crate::{DateTime, SecondsFormat}; + pub use crate::{Datelike, Month, Timelike, Weekday}; + pub use crate::{FixedOffset, Utc}; + pub use crate::{NaiveDate, NaiveDateTime, NaiveTime}; + pub use crate::{Offset, TimeZone}; } -// useful throughout the codebase +mod date; +#[allow(deprecated)] +pub use date::Date; +#[doc(no_inline)] +#[allow(deprecated)] +pub use date::{MAX_DATE, MIN_DATE}; + +mod datetime; +pub use datetime::DateTime; +#[allow(deprecated)] +#[doc(no_inline)] +pub use datetime::{MAX_DATETIME, MIN_DATETIME}; + +pub mod format; +/// L10n locales. +#[cfg(feature = "unstable-locales")] +pub use format::Locale; +pub use format::{ParseError, ParseResult, SecondsFormat}; + +pub mod naive; +#[doc(inline)] +pub use naive::{Days, NaiveDate, NaiveDateTime, NaiveTime}; +pub use naive::{IsoWeek, NaiveWeek}; + +pub mod offset; +#[cfg(feature = "clock")] +#[doc(inline)] +pub use offset::Local; +#[doc(hidden)] +pub use offset::LocalResult; +pub use offset::MappedLocalTime; +#[doc(inline)] +pub use offset::{FixedOffset, Offset, TimeZone, Utc}; + +pub mod round; +pub use round::{DurationRound, RoundingError, SubsecRound}; + +mod weekday; +#[doc(no_inline)] +pub use weekday::ParseWeekdayError; +pub use weekday::Weekday; + +mod month; +#[doc(no_inline)] +pub use month::ParseMonthError; +pub use month::{Month, Months}; + +mod traits; +pub use traits::{Datelike, Timelike}; + +#[cfg(feature = "__internal_bench")] +#[doc(hidden)] +pub use naive::__BenchYearFlags; + +/// Serialization/Deserialization with serde +/// +/// The [`DateTime`] type has default implementations for (de)serializing to/from the [RFC 3339] +/// format. This module provides alternatives for serializing to timestamps. +/// +/// The alternatives are for use with serde's [`with` annotation] combined with the module name. +/// Alternatively the individual `serialize` and `deserialize` functions in each module can be used +/// with serde's [`serialize_with`] and [`deserialize_with`] annotations. +/// +/// *Available on crate feature 'serde' only.* +/// +/// [RFC 3339]: https://tools.ietf.org/html/rfc3339 +/// [`with` annotation]: https://serde.rs/field-attrs.html#with +/// [`serialize_with`]: https://serde.rs/field-attrs.html#serialize_with +/// [`deserialize_with`]: https://serde.rs/field-attrs.html#deserialize_with +#[cfg(feature = "serde")] +pub mod serde { + use core::fmt; + use serde::de; + + pub use super::datetime::serde::*; + + /// Create a custom `de::Error` with `SerdeError::InvalidTimestamp`. + pub(crate) fn invalid_ts(value: T) -> E + where + E: de::Error, + T: fmt::Display, + { + E::custom(SerdeError::InvalidTimestamp(value)) + } + + enum SerdeError { + InvalidTimestamp(T), + } + + impl fmt::Display for SerdeError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + SerdeError::InvalidTimestamp(ts) => { + write!(f, "value is not a legal timestamp: {}", ts) + } + } + } + } +} + +/// Zero-copy serialization/deserialization with rkyv. +/// +/// This module re-exports the `Archived*` versions of chrono's types. +#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] +pub mod rkyv { + pub use crate::datetime::ArchivedDateTime; + pub use crate::month::ArchivedMonth; + pub use crate::naive::date::ArchivedNaiveDate; + pub use crate::naive::datetime::ArchivedNaiveDateTime; + pub use crate::naive::isoweek::ArchivedIsoWeek; + pub use crate::naive::time::ArchivedNaiveTime; + pub use crate::offset::fixed::ArchivedFixedOffset; + #[cfg(feature = "clock")] + pub use crate::offset::local::ArchivedLocal; + pub use crate::offset::utc::ArchivedUtc; + pub use crate::time_delta::ArchivedTimeDelta; + pub use crate::weekday::ArchivedWeekday; + + /// Alias of [`ArchivedTimeDelta`] + pub type ArchivedDuration = ArchivedTimeDelta; +} + +/// Out of range error type used in various converting APIs +#[derive(Clone, Copy, Hash, PartialEq, Eq)] +pub struct OutOfRange { + _private: (), +} + +impl OutOfRange { + const fn new() -> OutOfRange { + OutOfRange { _private: () } + } +} + +impl fmt::Display for OutOfRange { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "out of range") + } +} + +impl fmt::Debug for OutOfRange { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "out of range") + } +} + +#[cfg(feature = "std")] +impl std::error::Error for OutOfRange {} + +/// Workaround because `?` is not (yet) available in const context. +#[macro_export] +#[doc(hidden)] macro_rules! try_opt { ($e:expr) => { match $e { @@ -536,1000 +699,34 @@ macro_rules! try_opt { }; } -mod div; -pub mod offset; -pub mod naive { - //! Date and time types unconcerned with timezones. - //! - //! They are primarily building blocks for other types - //! (e.g. [`TimeZone`](../offset/trait.TimeZone.html)), - //! but can be also used for the simpler date and time handling. - - mod date; - mod datetime; - mod internals; - mod isoweek; - mod time; - - pub use self::date::{NaiveDate, MAX_DATE, MIN_DATE}; - #[cfg(feature = "rustc-serialize")] - #[allow(deprecated)] - pub use self::datetime::rustc_serialize::TsSeconds; - pub use self::datetime::{NaiveDateTime, MAX_DATETIME, MIN_DATETIME}; - pub use self::isoweek::IsoWeek; - pub use self::time::NaiveTime; - - #[cfg(feature = "__internal_bench")] - #[doc(hidden)] - pub use self::internals::YearFlags as __BenchYearFlags; - - /// Serialization/Deserialization of naive types in alternate formats - /// - /// The various modules in here are intended to be used with serde's [`with` - /// annotation][1] to serialize as something other than the default [RFC - /// 3339][2] format. - /// - /// [1]: https://serde.rs/attributes.html#field-attributes - /// [2]: https://tools.ietf.org/html/rfc3339 - #[cfg(feature = "serde")] - pub mod serde { - pub use super::datetime::serde::*; - } -} -mod date; -mod datetime; -pub mod format; -mod round; - -#[cfg(feature = "__internal_bench")] -#[doc(hidden)] -pub use naive::__BenchYearFlags; - -/// Serialization/Deserialization in alternate formats -/// -/// The various modules in here are intended to be used with serde's [`with` -/// annotation][1] to serialize as something other than the default [RFC -/// 3339][2] format. -/// -/// [1]: https://serde.rs/attributes.html#field-attributes -/// [2]: https://tools.ietf.org/html/rfc3339 -#[cfg(feature = "serde")] -pub mod serde { - pub use super::datetime::serde::*; -} - -// Until rust 1.18 there is no "pub(crate)" so to share this we need it in the root - -#[cfg(feature = "serde")] -enum SerdeError { - NonExistent { timestamp: V }, - Ambiguous { timestamp: V, min: D, max: D }, -} - -/// Construct a [`SerdeError::NonExistent`] -#[cfg(feature = "serde")] -fn ne_timestamp(ts: T) -> SerdeError { - SerdeError::NonExistent:: { timestamp: ts } -} - -#[cfg(feature = "serde")] -impl fmt::Debug for SerdeError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "ChronoSerdeError({})", self) - } -} - -// impl core::error::Error for SerdeError {} -#[cfg(feature = "serde")] -impl fmt::Display for SerdeError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - &SerdeError::NonExistent { ref timestamp } => { - write!(f, "value is not a legal timestamp: {}", timestamp) - } - &SerdeError::Ambiguous { ref timestamp, ref min, ref max } => write!( - f, - "value is an ambiguous timestamp: {}, could be either of {}, {}", - timestamp, min, max - ), - } - } -} - -/// The day of week. -/// -/// The order of the days of week depends on the context. -/// (This is why this type does *not* implement `PartialOrd` or `Ord` traits.) -/// One should prefer `*_from_monday` or `*_from_sunday` methods to get the correct result. -#[derive(PartialEq, Eq, Copy, Clone, Debug, Hash)] -#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] -pub enum Weekday { - /// Monday. - Mon = 0, - /// Tuesday. - Tue = 1, - /// Wednesday. - Wed = 2, - /// Thursday. - Thu = 3, - /// Friday. - Fri = 4, - /// Saturday. - Sat = 5, - /// Sunday. - Sun = 6, -} - -impl Weekday { - /// The next day in the week. - /// - /// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` - /// ----------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- - /// `w.succ()`: | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` | `Mon` - #[inline] - pub fn succ(&self) -> Weekday { - match *self { - Weekday::Mon => Weekday::Tue, - Weekday::Tue => Weekday::Wed, - Weekday::Wed => Weekday::Thu, - Weekday::Thu => Weekday::Fri, - Weekday::Fri => Weekday::Sat, - Weekday::Sat => Weekday::Sun, - Weekday::Sun => Weekday::Mon, - } - } - - /// The previous day in the week. - /// - /// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` - /// ----------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- - /// `w.pred()`: | `Sun` | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` - #[inline] - pub fn pred(&self) -> Weekday { - match *self { - Weekday::Mon => Weekday::Sun, - Weekday::Tue => Weekday::Mon, - Weekday::Wed => Weekday::Tue, - Weekday::Thu => Weekday::Wed, - Weekday::Fri => Weekday::Thu, - Weekday::Sat => Weekday::Fri, - Weekday::Sun => Weekday::Sat, - } - } - - /// Returns a day-of-week number starting from Monday = 1. (ISO 8601 weekday number) - /// - /// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` - /// ------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- - /// `w.number_from_monday()`: | 1 | 2 | 3 | 4 | 5 | 6 | 7 - #[inline] - pub fn number_from_monday(&self) -> u32 { - match *self { - Weekday::Mon => 1, - Weekday::Tue => 2, - Weekday::Wed => 3, - Weekday::Thu => 4, - Weekday::Fri => 5, - Weekday::Sat => 6, - Weekday::Sun => 7, - } - } - - /// Returns a day-of-week number starting from Sunday = 1. - /// - /// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` - /// ------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- - /// `w.number_from_sunday()`: | 2 | 3 | 4 | 5 | 6 | 7 | 1 - #[inline] - pub fn number_from_sunday(&self) -> u32 { - match *self { - Weekday::Mon => 2, - Weekday::Tue => 3, - Weekday::Wed => 4, - Weekday::Thu => 5, - Weekday::Fri => 6, - Weekday::Sat => 7, - Weekday::Sun => 1, - } - } - - /// Returns a day-of-week number starting from Monday = 0. - /// - /// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` - /// --------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- - /// `w.num_days_from_monday()`: | 0 | 1 | 2 | 3 | 4 | 5 | 6 - #[inline] - pub fn num_days_from_monday(&self) -> u32 { - match *self { - Weekday::Mon => 0, - Weekday::Tue => 1, - Weekday::Wed => 2, - Weekday::Thu => 3, - Weekday::Fri => 4, - Weekday::Sat => 5, - Weekday::Sun => 6, - } - } - - /// Returns a day-of-week number starting from Sunday = 0. - /// - /// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` - /// --------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- - /// `w.num_days_from_sunday()`: | 1 | 2 | 3 | 4 | 5 | 6 | 0 - #[inline] - pub fn num_days_from_sunday(&self) -> u32 { - match *self { - Weekday::Mon => 1, - Weekday::Tue => 2, - Weekday::Wed => 3, - Weekday::Thu => 4, - Weekday::Fri => 5, - Weekday::Sat => 6, - Weekday::Sun => 0, - } - } -} - -impl fmt::Display for Weekday { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str(match *self { - Weekday::Mon => "Mon", - Weekday::Tue => "Tue", - Weekday::Wed => "Wed", - Weekday::Thu => "Thu", - Weekday::Fri => "Fri", - Weekday::Sat => "Sat", - Weekday::Sun => "Sun", - }) - } -} - -/// Any weekday can be represented as an integer from 0 to 6, which equals to -/// [`Weekday::num_days_from_monday`](#method.num_days_from_monday) in this implementation. -/// Do not heavily depend on this though; use explicit methods whenever possible. -impl num_traits::FromPrimitive for Weekday { - #[inline] - fn from_i64(n: i64) -> Option { - match n { - 0 => Some(Weekday::Mon), - 1 => Some(Weekday::Tue), - 2 => Some(Weekday::Wed), - 3 => Some(Weekday::Thu), - 4 => Some(Weekday::Fri), - 5 => Some(Weekday::Sat), - 6 => Some(Weekday::Sun), - _ => None, - } - } - - #[inline] - fn from_u64(n: u64) -> Option { - match n { - 0 => Some(Weekday::Mon), - 1 => Some(Weekday::Tue), - 2 => Some(Weekday::Wed), - 3 => Some(Weekday::Thu), - 4 => Some(Weekday::Fri), - 5 => Some(Weekday::Sat), - 6 => Some(Weekday::Sun), - _ => None, - } - } -} - -use core::fmt; - -/// An error resulting from reading `Weekday` value with `FromStr`. -#[derive(Clone, PartialEq)] -pub struct ParseWeekdayError { - _dummy: (), -} - -impl fmt::Debug for ParseWeekdayError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "ParseWeekdayError {{ .. }}") - } -} - -// the actual `FromStr` implementation is in the `format` module to leverage the existing code - -#[cfg(feature = "serde")] -mod weekday_serde { - use super::Weekday; - use core::fmt; - use serdelib::{de, ser}; - - impl ser::Serialize for Weekday { - fn serialize(&self, serializer: S) -> Result - where - S: ser::Serializer, - { - serializer.collect_str(&self) - } - } - - struct WeekdayVisitor; - - impl<'de> de::Visitor<'de> for WeekdayVisitor { - type Value = Weekday; - - fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Weekday") - } - - fn visit_str(self, value: &str) -> Result - where - E: de::Error, - { - value.parse().map_err(|_| E::custom("short or long weekday names expected")) - } - } - - impl<'de> de::Deserialize<'de> for Weekday { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - deserializer.deserialize_str(WeekdayVisitor) - } - } - - #[cfg(test)] - extern crate serde_json; - - #[test] - fn test_serde_serialize() { - use self::serde_json::to_string; - use Weekday::*; - - let cases: Vec<(Weekday, &str)> = vec![ - (Mon, "\"Mon\""), - (Tue, "\"Tue\""), - (Wed, "\"Wed\""), - (Thu, "\"Thu\""), - (Fri, "\"Fri\""), - (Sat, "\"Sat\""), - (Sun, "\"Sun\""), - ]; - - for (weekday, expected_str) in cases { - let string = to_string(&weekday).unwrap(); - assert_eq!(string, expected_str); - } - } - - #[test] - fn test_serde_deserialize() { - use self::serde_json::from_str; - use Weekday::*; - - let cases: Vec<(&str, Weekday)> = vec![ - ("\"mon\"", Mon), - ("\"MONDAY\"", Mon), - ("\"MonDay\"", Mon), - ("\"mOn\"", Mon), - ("\"tue\"", Tue), - ("\"tuesday\"", Tue), - ("\"wed\"", Wed), - ("\"wednesday\"", Wed), - ("\"thu\"", Thu), - ("\"thursday\"", Thu), - ("\"fri\"", Fri), - ("\"friday\"", Fri), - ("\"sat\"", Sat), - ("\"saturday\"", Sat), - ("\"sun\"", Sun), - ("\"sunday\"", Sun), - ]; - - for (str, expected_weekday) in cases { - let weekday = from_str::(str).unwrap(); - assert_eq!(weekday, expected_weekday); - } - - let errors: Vec<&str> = - vec!["\"not a weekday\"", "\"monDAYs\"", "\"mond\"", "mon", "\"thur\"", "\"thurs\""]; - - for str in errors { - from_str::(str).unwrap_err(); - } - } -} - -/// The month of the year. -/// -/// This enum is just a convenience implementation. -/// The month in dates created by DateLike objects does not return this enum. -/// -/// It is possible to convert from a date to a month independently -/// ``` -/// # extern crate num_traits; -/// use num_traits::FromPrimitive; -/// use chrono::prelude::*; -/// let date = Utc.ymd(2019, 10, 28).and_hms(9, 10, 11); -/// // `2019-10-28T09:10:11Z` -/// let month = Month::from_u32(date.month()); -/// assert_eq!(month, Some(Month::October)) -/// ``` -/// Or from a Month to an integer usable by dates -/// ``` -/// # use chrono::prelude::*; -/// let month = Month::January; -/// let dt = Utc.ymd(2019, month.number_from_month(), 28).and_hms(9, 10, 11); -/// assert_eq!((dt.year(), dt.month(), dt.day()), (2019, 1, 28)); -/// ``` -/// Allows mapping from and to month, from 1-January to 12-December. -/// Can be Serialized/Deserialized with serde -// Actual implementation is zero-indexed, API intended as 1-indexed for more intuitive behavior. -#[derive(PartialEq, Eq, Copy, Clone, Debug, Hash)] -#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] -pub enum Month { - /// January - January = 0, - /// February - February = 1, - /// March - March = 2, - /// April - April = 3, - /// May - May = 4, - /// June - June = 5, - /// July - July = 6, - /// August - August = 7, - /// September - September = 8, - /// October - October = 9, - /// November - November = 10, - /// December - December = 11, -} - -impl Month { - /// The next month. - /// - /// `m`: | `January` | `February` | `...` | `December` - /// ----------- | --------- | ---------- | --- | --------- - /// `m.succ()`: | `February` | `March` | `...` | `January` - #[inline] - pub fn succ(&self) -> Month { - match *self { - Month::January => Month::February, - Month::February => Month::March, - Month::March => Month::April, - Month::April => Month::May, - Month::May => Month::June, - Month::June => Month::July, - Month::July => Month::August, - Month::August => Month::September, - Month::September => Month::October, - Month::October => Month::November, - Month::November => Month::December, - Month::December => Month::January, - } - } - - /// The previous month. - /// - /// `m`: | `January` | `February` | `...` | `December` - /// ----------- | --------- | ---------- | --- | --------- - /// `m.succ()`: | `December` | `January` | `...` | `November` - #[inline] - pub fn pred(&self) -> Month { - match *self { - Month::January => Month::December, - Month::February => Month::January, - Month::March => Month::February, - Month::April => Month::March, - Month::May => Month::April, - Month::June => Month::May, - Month::July => Month::June, - Month::August => Month::July, - Month::September => Month::August, - Month::October => Month::September, - Month::November => Month::October, - Month::December => Month::November, - } - } - - /// Returns a month-of-year number starting from January = 1. - /// - /// `m`: | `January` | `February` | `...` | `December` - /// -------------------------| --------- | ---------- | --- | ----- - /// `m.number_from_month()`: | 1 | 2 | `...` | 12 - #[inline] - pub fn number_from_month(&self) -> u32 { - match *self { - Month::January => 1, - Month::February => 2, - Month::March => 3, - Month::April => 4, - Month::May => 5, - Month::June => 6, - Month::July => 7, - Month::August => 8, - Month::September => 9, - Month::October => 10, - Month::November => 11, - Month::December => 12, - } - } - - /// Get the name of the month - /// - /// ``` - /// use chrono::Month; - /// - /// assert_eq!(Month::January.name(), "January") - /// ``` - pub fn name(&self) -> &'static str { - match *self { - Month::January => "January", - Month::February => "February", - Month::March => "March", - Month::April => "April", - Month::May => "May", - Month::June => "June", - Month::July => "July", - Month::August => "August", - Month::September => "September", - Month::October => "October", - Month::November => "November", - Month::December => "December", - } - } -} - -impl num_traits::FromPrimitive for Month { - /// Returns an Option from a i64, assuming a 1-index, January = 1. - /// - /// `Month::from_i64(n: i64)`: | `1` | `2` | ... | `12` - /// ---------------------------| -------------------- | --------------------- | ... | ----- - /// ``: | Some(Month::January) | Some(Month::February) | ... | Some(Month::December) - - #[inline] - fn from_u64(n: u64) -> Option { - Self::from_u32(n as u32) - } - - #[inline] - fn from_i64(n: i64) -> Option { - Self::from_u32(n as u32) - } - - #[inline] - fn from_u32(n: u32) -> Option { - match n { - 1 => Some(Month::January), - 2 => Some(Month::February), - 3 => Some(Month::March), - 4 => Some(Month::April), - 5 => Some(Month::May), - 6 => Some(Month::June), - 7 => Some(Month::July), - 8 => Some(Month::August), - 9 => Some(Month::September), - 10 => Some(Month::October), - 11 => Some(Month::November), - 12 => Some(Month::December), - _ => None, - } - } -} - -/// An error resulting from reading `` value with `FromStr`. -#[derive(Clone, PartialEq)] -pub struct ParseMonthError { - _dummy: (), -} - -impl fmt::Debug for ParseMonthError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "ParseMonthError {{ .. }}") - } -} - -#[cfg(feature = "serde")] -mod month_serde { - use super::Month; - use serdelib::{de, ser}; - - use core::fmt; - - impl ser::Serialize for Month { - fn serialize(&self, serializer: S) -> Result - where - S: ser::Serializer, - { - serializer.collect_str(self.name()) - } - } - - struct MonthVisitor; - - impl<'de> de::Visitor<'de> for MonthVisitor { - type Value = Month; - - fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Month") - } - - fn visit_str(self, value: &str) -> Result - where - E: de::Error, - { - value.parse().map_err(|_| E::custom("short (3-letter) or full month names expected")) - } - } - - impl<'de> de::Deserialize<'de> for Month { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - deserializer.deserialize_str(MonthVisitor) - } - } - - #[cfg(test)] - extern crate serde_json; - - #[test] - fn test_serde_serialize() { - use self::serde_json::to_string; - use Month::*; - - let cases: Vec<(Month, &str)> = vec![ - (January, "\"January\""), - (February, "\"February\""), - (March, "\"March\""), - (April, "\"April\""), - (May, "\"May\""), - (June, "\"June\""), - (July, "\"July\""), - (August, "\"August\""), - (September, "\"September\""), - (October, "\"October\""), - (November, "\"November\""), - (December, "\"December\""), - ]; - - for (month, expected_str) in cases { - let string = to_string(&month).unwrap(); - assert_eq!(string, expected_str); - } - } - - #[test] - fn test_serde_deserialize() { - use self::serde_json::from_str; - use Month::*; - - let cases: Vec<(&str, Month)> = vec![ - ("\"january\"", January), - ("\"jan\"", January), - ("\"FeB\"", February), - ("\"MAR\"", March), - ("\"mar\"", March), - ("\"april\"", April), - ("\"may\"", May), - ("\"june\"", June), - ("\"JULY\"", July), - ("\"august\"", August), - ("\"september\"", September), - ("\"October\"", October), - ("\"November\"", November), - ("\"DECEmbEr\"", December), - ]; - - for (string, expected_month) in cases { - let month = from_str::(string).unwrap(); - assert_eq!(month, expected_month); - } - - let errors: Vec<&str> = - vec!["\"not a month\"", "\"ja\"", "\"Dece\"", "Dec", "\"Augustin\""]; - - for string in errors { - from_str::(string).unwrap_err(); - } - } -} - -/// The common set of methods for date component. -pub trait Datelike: Sized { - /// Returns the year number in the [calendar date](./naive/struct.NaiveDate.html#calendar-date). - fn year(&self) -> i32; - - /// Returns the absolute year number starting from 1 with a boolean flag, - /// which is false when the year predates the epoch (BCE/BC) and true otherwise (CE/AD). - #[inline] - fn year_ce(&self) -> (bool, u32) { - let year = self.year(); - if year < 1 { - (false, (1 - year) as u32) - } else { - (true, year as u32) - } - } - - /// Returns the month number starting from 1. - /// - /// The return value ranges from 1 to 12. - fn month(&self) -> u32; - - /// Returns the month number starting from 0. - /// - /// The return value ranges from 0 to 11. - fn month0(&self) -> u32; - - /// Returns the day of month starting from 1. - /// - /// The return value ranges from 1 to 31. (The last day of month differs by months.) - fn day(&self) -> u32; - - /// Returns the day of month starting from 0. - /// - /// The return value ranges from 0 to 30. (The last day of month differs by months.) - fn day0(&self) -> u32; - - /// Returns the day of year starting from 1. - /// - /// The return value ranges from 1 to 366. (The last day of year differs by years.) - fn ordinal(&self) -> u32; - - /// Returns the day of year starting from 0. - /// - /// The return value ranges from 0 to 365. (The last day of year differs by years.) - fn ordinal0(&self) -> u32; - - /// Returns the day of week. - fn weekday(&self) -> Weekday; - - /// Returns the ISO week. - fn iso_week(&self) -> IsoWeek; - - /// Makes a new value with the year number changed. - /// - /// Returns `None` when the resulting value would be invalid. - fn with_year(&self, year: i32) -> Option; - - /// Makes a new value with the month number (starting from 1) changed. - /// - /// Returns `None` when the resulting value would be invalid. - fn with_month(&self, month: u32) -> Option; - - /// Makes a new value with the month number (starting from 0) changed. - /// - /// Returns `None` when the resulting value would be invalid. - fn with_month0(&self, month0: u32) -> Option; - - /// Makes a new value with the day of month (starting from 1) changed. - /// - /// Returns `None` when the resulting value would be invalid. - fn with_day(&self, day: u32) -> Option; - - /// Makes a new value with the day of month (starting from 0) changed. - /// - /// Returns `None` when the resulting value would be invalid. - fn with_day0(&self, day0: u32) -> Option; - - /// Makes a new value with the day of year (starting from 1) changed. - /// - /// Returns `None` when the resulting value would be invalid. - fn with_ordinal(&self, ordinal: u32) -> Option; - - /// Makes a new value with the day of year (starting from 0) changed. - /// - /// Returns `None` when the resulting value would be invalid. - fn with_ordinal0(&self, ordinal0: u32) -> Option; - - /// Counts the days in the proleptic Gregorian calendar, with January 1, Year 1 (CE) as day 1. - /// - /// # Examples - /// - /// ``` - /// use chrono::{NaiveDate, Datelike}; - /// - /// assert_eq!(NaiveDate::from_ymd(1970, 1, 1).num_days_from_ce(), 719_163); - /// assert_eq!(NaiveDate::from_ymd(2, 1, 1).num_days_from_ce(), 366); - /// assert_eq!(NaiveDate::from_ymd(1, 1, 1).num_days_from_ce(), 1); - /// assert_eq!(NaiveDate::from_ymd(0, 1, 1).num_days_from_ce(), -365); - /// ``` - fn num_days_from_ce(&self) -> i32 { - // See test_num_days_from_ce_against_alternative_impl below for a more straightforward - // implementation. - - // we know this wouldn't overflow since year is limited to 1/2^13 of i32's full range. - let mut year = self.year() - 1; - let mut ndays = 0; - if year < 0 { - let excess = 1 + (-year) / 400; - year += excess * 400; - ndays -= excess * 146_097; - } - let div_100 = year / 100; - ndays += ((year * 1461) >> 2) - div_100 + (div_100 >> 2); - ndays + self.ordinal() as i32 - } -} - -/// The common set of methods for time component. -pub trait Timelike: Sized { - /// Returns the hour number from 0 to 23. - fn hour(&self) -> u32; - - /// Returns the hour number from 1 to 12 with a boolean flag, - /// which is false for AM and true for PM. - #[inline] - fn hour12(&self) -> (bool, u32) { - let hour = self.hour(); - let mut hour12 = hour % 12; - if hour12 == 0 { - hour12 = 12; - } - (hour >= 12, hour12) - } - - /// Returns the minute number from 0 to 59. - fn minute(&self) -> u32; - - /// Returns the second number from 0 to 59. - fn second(&self) -> u32; - - /// Returns the number of nanoseconds since the whole non-leap second. - /// The range from 1,000,000,000 to 1,999,999,999 represents - /// the [leap second](./naive/struct.NaiveTime.html#leap-second-handling). - fn nanosecond(&self) -> u32; - - /// Makes a new value with the hour number changed. - /// - /// Returns `None` when the resulting value would be invalid. - fn with_hour(&self, hour: u32) -> Option; - - /// Makes a new value with the minute number changed. - /// - /// Returns `None` when the resulting value would be invalid. - fn with_minute(&self, min: u32) -> Option; - - /// Makes a new value with the second number changed. - /// - /// Returns `None` when the resulting value would be invalid. - /// As with the [`second`](#tymethod.second) method, - /// the input range is restricted to 0 through 59. - fn with_second(&self, sec: u32) -> Option; - - /// Makes a new value with nanoseconds since the whole non-leap second changed. - /// - /// Returns `None` when the resulting value would be invalid. - /// As with the [`nanosecond`](#tymethod.nanosecond) method, - /// the input range can exceed 1,000,000,000 for leap seconds. - fn with_nanosecond(&self, nano: u32) -> Option; - - /// Returns the number of non-leap seconds past the last midnight. - #[inline] - fn num_seconds_from_midnight(&self) -> u32 { - self.hour() * 3600 + self.minute() * 60 + self.second() +/// Workaround because `.expect()` is not (yet) available in const context. +pub(crate) const fn expect(opt: Option, msg: &str) -> T { + match opt { + Some(val) => val, + None => panic!("{}", msg), } } #[cfg(test)] -extern crate num_iter; - -mod test { - #[allow(unused_imports)] - use super::*; +mod tests { + #[cfg(feature = "clock")] + use crate::{DateTime, FixedOffset, Local, NaiveDate, NaiveDateTime, NaiveTime, Utc}; #[test] - fn test_readme_doomsday() { - use num_iter::range_inclusive; + #[allow(deprecated)] + #[cfg(feature = "clock")] + fn test_type_sizes() { + use core::mem::size_of; + assert_eq!(size_of::(), 4); + assert_eq!(size_of::>(), 4); + assert_eq!(size_of::(), 8); + assert_eq!(size_of::>(), 12); + assert_eq!(size_of::(), 12); + assert_eq!(size_of::>(), 12); - for y in range_inclusive(naive::MIN_DATE.year(), naive::MAX_DATE.year()) { - // even months - let d4 = NaiveDate::from_ymd(y, 4, 4); - let d6 = NaiveDate::from_ymd(y, 6, 6); - let d8 = NaiveDate::from_ymd(y, 8, 8); - let d10 = NaiveDate::from_ymd(y, 10, 10); - let d12 = NaiveDate::from_ymd(y, 12, 12); - - // nine to five, seven-eleven - let d59 = NaiveDate::from_ymd(y, 5, 9); - let d95 = NaiveDate::from_ymd(y, 9, 5); - let d711 = NaiveDate::from_ymd(y, 7, 11); - let d117 = NaiveDate::from_ymd(y, 11, 7); - - // "March 0" - let d30 = NaiveDate::from_ymd(y, 3, 1).pred(); - - let weekday = d30.weekday(); - let other_dates = [d4, d6, d8, d10, d12, d59, d95, d711, d117]; - assert!(other_dates.iter().all(|d| d.weekday() == weekday)); - } - } - - #[test] - fn test_month_enum_primitive_parse() { - use num_traits::FromPrimitive; - - let jan_opt = Month::from_u32(1); - let feb_opt = Month::from_u64(2); - let dec_opt = Month::from_i64(12); - let no_month = Month::from_u32(13); - assert_eq!(jan_opt, Some(Month::January)); - assert_eq!(feb_opt, Some(Month::February)); - assert_eq!(dec_opt, Some(Month::December)); - assert_eq!(no_month, None); - - let date = Utc.ymd(2019, 10, 28).and_hms(9, 10, 11); - assert_eq!(Month::from_u32(date.month()), Some(Month::October)); - - let month = Month::January; - let dt = Utc.ymd(2019, month.number_from_month(), 28).and_hms(9, 10, 11); - assert_eq!((dt.year(), dt.month(), dt.day()), (2019, 1, 28)); + assert_eq!(size_of::>(), 12); + assert_eq!(size_of::>(), 16); + assert_eq!(size_of::>(), 16); + assert_eq!(size_of::>>(), 16); } } - -/// Tests `Datelike::num_days_from_ce` against an alternative implementation. -/// -/// The alternative implementation is not as short as the current one but it is simpler to -/// understand, with less unexplained magic constants. -#[test] -fn test_num_days_from_ce_against_alternative_impl() { - /// Returns the number of multiples of `div` in the range `start..end`. - /// - /// If the range `start..end` is back-to-front, i.e. `start` is greater than `end`, the - /// behaviour is defined by the following equation: - /// `in_between(start, end, div) == - in_between(end, start, div)`. - /// - /// When `div` is 1, this is equivalent to `end - start`, i.e. the length of `start..end`. - /// - /// # Panics - /// - /// Panics if `div` is not positive. - fn in_between(start: i32, end: i32, div: i32) -> i32 { - assert!(div > 0, "in_between: nonpositive div = {}", div); - let start = (start.div_euclid(div), start.rem_euclid(div)); - let end = (end.div_euclid(div), end.rem_euclid(div)); - // The lowest multiple of `div` greater than or equal to `start`, divided. - let start = start.0 + (start.1 != 0) as i32; - // The lowest multiple of `div` greater than or equal to `end`, divided. - let end = end.0 + (end.1 != 0) as i32; - end - start - } - - /// Alternative implementation to `Datelike::num_days_from_ce` - fn num_days_from_ce(date: &Date) -> i32 { - let year = date.year(); - let diff = move |div| in_between(1, year, div); - // 365 days a year, one more in leap years. In the gregorian calendar, leap years are all - // the multiples of 4 except multiples of 100 but including multiples of 400. - date.ordinal() as i32 + 365 * diff(1) + diff(4) - diff(100) + diff(400) - } - - use num_iter::range_inclusive; - - for year in range_inclusive(naive::MIN_DATE.year(), naive::MAX_DATE.year()) { - let jan1_year = NaiveDate::from_ymd(year, 1, 1); - assert_eq!( - jan1_year.num_days_from_ce(), - num_days_from_ce(&jan1_year), - "on {:?}", - jan1_year - ); - let mid_year = jan1_year + Duration::days(133); - assert_eq!(mid_year.num_days_from_ce(), num_days_from_ce(&mid_year), "on {:?}", mid_year); - } -} - -#[test] -fn test_month_enum_succ_pred() { - assert_eq!(Month::January.succ(), Month::February); - assert_eq!(Month::December.succ(), Month::January); - assert_eq!(Month::January.pred(), Month::December); - assert_eq!(Month::February.pred(), Month::January); -} diff --git a/third_party/rust/chrono/src/month.rs b/third_party/rust/chrono/src/month.rs new file mode 100644 index 00000000000..1b4dbfd7b86 --- /dev/null +++ b/third_party/rust/chrono/src/month.rs @@ -0,0 +1,472 @@ +use core::fmt; + +#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] +use rkyv::{Archive, Deserialize, Serialize}; + +use crate::OutOfRange; +use crate::naive::NaiveDate; + +/// The month of the year. +/// +/// This enum is just a convenience implementation. +/// The month in dates created by DateLike objects does not return this enum. +/// +/// It is possible to convert from a date to a month independently +/// ``` +/// use chrono::prelude::*; +/// let date = Utc.with_ymd_and_hms(2019, 10, 28, 9, 10, 11).unwrap(); +/// // `2019-10-28T09:10:11Z` +/// let month = Month::try_from(u8::try_from(date.month()).unwrap()).ok(); +/// assert_eq!(month, Some(Month::October)) +/// ``` +/// Or from a Month to an integer usable by dates +/// ``` +/// # use chrono::prelude::*; +/// let month = Month::January; +/// let dt = Utc.with_ymd_and_hms(2019, month.number_from_month(), 28, 9, 10, 11).unwrap(); +/// assert_eq!((dt.year(), dt.month(), dt.day()), (2019, 1, 28)); +/// ``` +/// Allows mapping from and to month, from 1-January to 12-December. +/// Can be Serialized/Deserialized with serde +// Actual implementation is zero-indexed, API intended as 1-indexed for more intuitive behavior. +#[derive(PartialEq, Eq, Copy, Clone, Debug, Hash, PartialOrd, Ord)] +#[cfg_attr( + any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), + derive(Archive, Deserialize, Serialize), + archive(compare(PartialEq, PartialOrd)), + archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) +)] +#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] +#[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(arbitrary::Arbitrary))] +pub enum Month { + /// January + January = 0, + /// February + February = 1, + /// March + March = 2, + /// April + April = 3, + /// May + May = 4, + /// June + June = 5, + /// July + July = 6, + /// August + August = 7, + /// September + September = 8, + /// October + October = 9, + /// November + November = 10, + /// December + December = 11, +} + +impl Month { + /// The next month. + /// + /// `m`: | `January` | `February` | `...` | `December` + /// ----------- | --------- | ---------- | --- | --------- + /// `m.succ()`: | `February` | `March` | `...` | `January` + #[inline] + #[must_use] + pub const fn succ(&self) -> Month { + match *self { + Month::January => Month::February, + Month::February => Month::March, + Month::March => Month::April, + Month::April => Month::May, + Month::May => Month::June, + Month::June => Month::July, + Month::July => Month::August, + Month::August => Month::September, + Month::September => Month::October, + Month::October => Month::November, + Month::November => Month::December, + Month::December => Month::January, + } + } + + /// The previous month. + /// + /// `m`: | `January` | `February` | `...` | `December` + /// ----------- | --------- | ---------- | --- | --------- + /// `m.pred()`: | `December` | `January` | `...` | `November` + #[inline] + #[must_use] + pub const fn pred(&self) -> Month { + match *self { + Month::January => Month::December, + Month::February => Month::January, + Month::March => Month::February, + Month::April => Month::March, + Month::May => Month::April, + Month::June => Month::May, + Month::July => Month::June, + Month::August => Month::July, + Month::September => Month::August, + Month::October => Month::September, + Month::November => Month::October, + Month::December => Month::November, + } + } + + /// Returns a month-of-year number starting from January = 1. + /// + /// `m`: | `January` | `February` | `...` | `December` + /// -------------------------| --------- | ---------- | --- | ----- + /// `m.number_from_month()`: | 1 | 2 | `...` | 12 + #[inline] + #[must_use] + pub const fn number_from_month(&self) -> u32 { + match *self { + Month::January => 1, + Month::February => 2, + Month::March => 3, + Month::April => 4, + Month::May => 5, + Month::June => 6, + Month::July => 7, + Month::August => 8, + Month::September => 9, + Month::October => 10, + Month::November => 11, + Month::December => 12, + } + } + + /// Get the name of the month + /// + /// ``` + /// use chrono::Month; + /// + /// assert_eq!(Month::January.name(), "January") + /// ``` + #[must_use] + pub const fn name(&self) -> &'static str { + match *self { + Month::January => "January", + Month::February => "February", + Month::March => "March", + Month::April => "April", + Month::May => "May", + Month::June => "June", + Month::July => "July", + Month::August => "August", + Month::September => "September", + Month::October => "October", + Month::November => "November", + Month::December => "December", + } + } + + /// Get the length in days of the month + /// + /// Yields `None` if `year` is out of range for `NaiveDate`. + pub fn num_days(&self, year: i32) -> Option { + Some(match *self { + Month::January => 31, + Month::February => match NaiveDate::from_ymd_opt(year, 2, 1)?.leap_year() { + true => 29, + false => 28, + }, + Month::March => 31, + Month::April => 30, + Month::May => 31, + Month::June => 30, + Month::July => 31, + Month::August => 31, + Month::September => 30, + Month::October => 31, + Month::November => 30, + Month::December => 31, + }) + } +} + +impl TryFrom for Month { + type Error = OutOfRange; + + fn try_from(value: u8) -> Result { + match value { + 1 => Ok(Month::January), + 2 => Ok(Month::February), + 3 => Ok(Month::March), + 4 => Ok(Month::April), + 5 => Ok(Month::May), + 6 => Ok(Month::June), + 7 => Ok(Month::July), + 8 => Ok(Month::August), + 9 => Ok(Month::September), + 10 => Ok(Month::October), + 11 => Ok(Month::November), + 12 => Ok(Month::December), + _ => Err(OutOfRange::new()), + } + } +} + +impl num_traits::FromPrimitive for Month { + /// Returns an `Option` from a i64, assuming a 1-index, January = 1. + /// + /// `Month::from_i64(n: i64)`: | `1` | `2` | ... | `12` + /// ---------------------------| -------------------- | --------------------- | ... | ----- + /// ``: | Some(Month::January) | Some(Month::February) | ... | Some(Month::December) + #[inline] + fn from_u64(n: u64) -> Option { + Self::from_u32(n as u32) + } + + #[inline] + fn from_i64(n: i64) -> Option { + Self::from_u32(n as u32) + } + + #[inline] + fn from_u32(n: u32) -> Option { + match n { + 1 => Some(Month::January), + 2 => Some(Month::February), + 3 => Some(Month::March), + 4 => Some(Month::April), + 5 => Some(Month::May), + 6 => Some(Month::June), + 7 => Some(Month::July), + 8 => Some(Month::August), + 9 => Some(Month::September), + 10 => Some(Month::October), + 11 => Some(Month::November), + 12 => Some(Month::December), + _ => None, + } + } +} + +/// A duration in calendar months +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +#[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(arbitrary::Arbitrary))] +pub struct Months(pub(crate) u32); + +impl Months { + /// Construct a new `Months` from a number of months + pub const fn new(num: u32) -> Self { + Self(num) + } + + /// Returns the total number of months in the `Months` instance. + #[inline] + pub const fn as_u32(&self) -> u32 { + self.0 + } +} + +/// An error resulting from reading `` value with `FromStr`. +#[derive(Clone, PartialEq, Eq)] +pub struct ParseMonthError { + pub(crate) _dummy: (), +} + +#[cfg(feature = "std")] +impl std::error::Error for ParseMonthError {} + +impl fmt::Display for ParseMonthError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "ParseMonthError {{ .. }}") + } +} + +impl fmt::Debug for ParseMonthError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "ParseMonthError {{ .. }}") + } +} + +#[cfg(feature = "serde")] +mod month_serde { + use super::Month; + use serde::{de, ser}; + + use core::fmt; + + impl ser::Serialize for Month { + fn serialize(&self, serializer: S) -> Result + where + S: ser::Serializer, + { + serializer.collect_str(self.name()) + } + } + + struct MonthVisitor; + + impl de::Visitor<'_> for MonthVisitor { + type Value = Month; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("Month") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + value.parse().map_err(|_| E::custom("short (3-letter) or full month names expected")) + } + } + + impl<'de> de::Deserialize<'de> for Month { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + deserializer.deserialize_str(MonthVisitor) + } + } +} + +#[cfg(test)] +mod tests { + use super::Month; + use crate::{Datelike, Months, OutOfRange, TimeZone, Utc}; + + #[test] + fn test_month_enum_try_from() { + assert_eq!(Month::try_from(1), Ok(Month::January)); + assert_eq!(Month::try_from(2), Ok(Month::February)); + assert_eq!(Month::try_from(12), Ok(Month::December)); + assert_eq!(Month::try_from(13), Err(OutOfRange::new())); + + let date = Utc.with_ymd_and_hms(2019, 10, 28, 9, 10, 11).unwrap(); + assert_eq!(Month::try_from(date.month() as u8), Ok(Month::October)); + + let month = Month::January; + let dt = Utc.with_ymd_and_hms(2019, month.number_from_month(), 28, 9, 10, 11).unwrap(); + assert_eq!((dt.year(), dt.month(), dt.day()), (2019, 1, 28)); + } + + #[test] + fn test_month_enum_primitive_parse() { + use num_traits::FromPrimitive; + + let jan_opt = Month::from_u32(1); + let feb_opt = Month::from_u64(2); + let dec_opt = Month::from_i64(12); + let no_month = Month::from_u32(13); + assert_eq!(jan_opt, Some(Month::January)); + assert_eq!(feb_opt, Some(Month::February)); + assert_eq!(dec_opt, Some(Month::December)); + assert_eq!(no_month, None); + + let date = Utc.with_ymd_and_hms(2019, 10, 28, 9, 10, 11).unwrap(); + assert_eq!(Month::from_u32(date.month()), Some(Month::October)); + + let month = Month::January; + let dt = Utc.with_ymd_and_hms(2019, month.number_from_month(), 28, 9, 10, 11).unwrap(); + assert_eq!((dt.year(), dt.month(), dt.day()), (2019, 1, 28)); + } + + #[test] + fn test_month_enum_succ_pred() { + assert_eq!(Month::January.succ(), Month::February); + assert_eq!(Month::December.succ(), Month::January); + assert_eq!(Month::January.pred(), Month::December); + assert_eq!(Month::February.pred(), Month::January); + } + + #[test] + fn test_month_partial_ord() { + assert!(Month::January <= Month::January); + assert!(Month::January < Month::February); + assert!(Month::January < Month::December); + assert!(Month::July >= Month::May); + assert!(Month::September > Month::March); + } + + #[test] + fn test_months_as_u32() { + assert_eq!(Months::new(0).as_u32(), 0); + assert_eq!(Months::new(1).as_u32(), 1); + assert_eq!(Months::new(u32::MAX).as_u32(), u32::MAX); + } + + #[test] + #[cfg(feature = "serde")] + fn test_serde_serialize() { + use Month::*; + use serde_json::to_string; + + let cases: Vec<(Month, &str)> = vec![ + (January, "\"January\""), + (February, "\"February\""), + (March, "\"March\""), + (April, "\"April\""), + (May, "\"May\""), + (June, "\"June\""), + (July, "\"July\""), + (August, "\"August\""), + (September, "\"September\""), + (October, "\"October\""), + (November, "\"November\""), + (December, "\"December\""), + ]; + + for (month, expected_str) in cases { + let string = to_string(&month).unwrap(); + assert_eq!(string, expected_str); + } + } + + #[test] + #[cfg(feature = "serde")] + fn test_serde_deserialize() { + use Month::*; + use serde_json::from_str; + + let cases: Vec<(&str, Month)> = vec![ + ("\"january\"", January), + ("\"jan\"", January), + ("\"FeB\"", February), + ("\"MAR\"", March), + ("\"mar\"", March), + ("\"april\"", April), + ("\"may\"", May), + ("\"june\"", June), + ("\"JULY\"", July), + ("\"august\"", August), + ("\"september\"", September), + ("\"October\"", October), + ("\"November\"", November), + ("\"DECEmbEr\"", December), + ]; + + for (string, expected_month) in cases { + let month = from_str::(string).unwrap(); + assert_eq!(month, expected_month); + } + + let errors: Vec<&str> = + vec!["\"not a month\"", "\"ja\"", "\"Dece\"", "Dec", "\"Augustin\""]; + + for string in errors { + from_str::(string).unwrap_err(); + } + } + + #[test] + #[cfg(feature = "rkyv-validation")] + fn test_rkyv_validation() { + let month = Month::January; + let bytes = rkyv::to_bytes::<_, 1>(&month).unwrap(); + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), month); + } + + #[test] + fn num_days() { + assert_eq!(Month::January.num_days(2020), Some(31)); + assert_eq!(Month::February.num_days(2020), Some(29)); + assert_eq!(Month::February.num_days(2019), Some(28)); + } +} diff --git a/third_party/rust/chrono/src/naive/date.rs b/third_party/rust/chrono/src/naive/date.rs deleted file mode 100644 index 3e34e20741c..00000000000 --- a/third_party/rust/chrono/src/naive/date.rs +++ /dev/null @@ -1,2392 +0,0 @@ -// This is a part of Chrono. -// See README.md and LICENSE.txt for details. - -//! ISO 8601 calendar date without timezone. - -#[cfg(any(feature = "alloc", feature = "std", test))] -use core::borrow::Borrow; -use core::ops::{Add, AddAssign, Sub, SubAssign}; -use core::{fmt, str}; -use num_traits::ToPrimitive; -use oldtime::Duration as OldDuration; - -use div::div_mod_floor; -#[cfg(any(feature = "alloc", feature = "std", test))] -use format::DelayedFormat; -use format::{parse, ParseError, ParseResult, Parsed, StrftimeItems}; -use format::{Item, Numeric, Pad}; -use naive::{IsoWeek, NaiveDateTime, NaiveTime}; -use {Datelike, Weekday}; - -use super::internals::{self, DateImpl, Mdf, Of, YearFlags}; -use super::isoweek; - -const MAX_YEAR: i32 = internals::MAX_YEAR; -const MIN_YEAR: i32 = internals::MIN_YEAR; - -// MAX_YEAR-12-31 minus 0000-01-01 -// = ((MAX_YEAR+1)-01-01 minus 0001-01-01) + (0001-01-01 minus 0000-01-01) - 1 day -// = ((MAX_YEAR+1)-01-01 minus 0001-01-01) + 365 days -// = MAX_YEAR * 365 + (# of leap years from 0001 to MAX_YEAR) + 365 days -#[cfg(test)] // only used for testing -const MAX_DAYS_FROM_YEAR_0: i32 = - MAX_YEAR * 365 + MAX_YEAR / 4 - MAX_YEAR / 100 + MAX_YEAR / 400 + 365; - -// MIN_YEAR-01-01 minus 0000-01-01 -// = (MIN_YEAR+400n+1)-01-01 minus (400n+1)-01-01 -// = ((MIN_YEAR+400n+1)-01-01 minus 0001-01-01) - ((400n+1)-01-01 minus 0001-01-01) -// = ((MIN_YEAR+400n+1)-01-01 minus 0001-01-01) - 146097n days -// -// n is set to 1000 for convenience. -#[cfg(test)] // only used for testing -const MIN_DAYS_FROM_YEAR_0: i32 = (MIN_YEAR + 400_000) * 365 + (MIN_YEAR + 400_000) / 4 - - (MIN_YEAR + 400_000) / 100 - + (MIN_YEAR + 400_000) / 400 - - 146097_000; - -#[cfg(test)] // only used for testing, but duplicated in naive::datetime -const MAX_BITS: usize = 44; - -/// ISO 8601 calendar date without timezone. -/// Allows for every [proleptic Gregorian date](#calendar-date) -/// from Jan 1, 262145 BCE to Dec 31, 262143 CE. -/// Also supports the conversion from ISO 8601 ordinal and week date. -/// -/// # Calendar Date -/// -/// The ISO 8601 **calendar date** follows the proleptic Gregorian calendar. -/// It is like a normal civil calendar but note some slight differences: -/// -/// * Dates before the Gregorian calendar's inception in 1582 are defined via the extrapolation. -/// Be careful, as historical dates are often noted in the Julian calendar and others -/// and the transition to Gregorian may differ across countries (as late as early 20C). -/// -/// (Some example: Both Shakespeare from Britain and Cervantes from Spain seemingly died -/// on the same calendar date---April 23, 1616---but in the different calendar. -/// Britain used the Julian calendar at that time, so Shakespeare's death is later.) -/// -/// * ISO 8601 calendars has the year 0, which is 1 BCE (a year before 1 CE). -/// If you need a typical BCE/BC and CE/AD notation for year numbers, -/// use the [`Datelike::year_ce`](../trait.Datelike.html#method.year_ce) method. -/// -/// # Week Date -/// -/// The ISO 8601 **week date** is a triple of year number, week number -/// and [day of the week](../enum.Weekday.html) with the following rules: -/// -/// * A week consists of Monday through Sunday, and is always numbered within some year. -/// The week number ranges from 1 to 52 or 53 depending on the year. -/// -/// * The week 1 of given year is defined as the first week containing January 4 of that year, -/// or equivalently, the first week containing four or more days in that year. -/// -/// * The year number in the week date may *not* correspond to the actual Gregorian year. -/// For example, January 3, 2016 (Sunday) was on the last (53rd) week of 2015. -/// -/// Chrono's date types default to the ISO 8601 [calendar date](#calendar-date), -/// but [`Datelike::iso_week`](../trait.Datelike.html#tymethod.iso_week) and -/// [`Datelike::weekday`](../trait.Datelike.html#tymethod.weekday) methods -/// can be used to get the corresponding week date. -/// -/// # Ordinal Date -/// -/// The ISO 8601 **ordinal date** is a pair of year number and day of the year ("ordinal"). -/// The ordinal number ranges from 1 to 365 or 366 depending on the year. -/// The year number is the same as that of the [calendar date](#calendar-date). -/// -/// This is currently the internal format of Chrono's date types. -#[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)] -pub struct NaiveDate { - ymdf: DateImpl, // (year << 13) | of -} - -/// The minimum possible `NaiveDate` (January 1, 262145 BCE). -pub const MIN_DATE: NaiveDate = NaiveDate { ymdf: (MIN_YEAR << 13) | (1 << 4) | 0o07 /*FE*/ }; -/// The maximum possible `NaiveDate` (December 31, 262143 CE). -pub const MAX_DATE: NaiveDate = NaiveDate { ymdf: (MAX_YEAR << 13) | (365 << 4) | 0o17 /*F*/ }; - -// as it is hard to verify year flags in `MIN_DATE` and `MAX_DATE`, -// we use a separate run-time test. -#[test] -fn test_date_bounds() { - let calculated_min = NaiveDate::from_ymd(MIN_YEAR, 1, 1); - let calculated_max = NaiveDate::from_ymd(MAX_YEAR, 12, 31); - assert!( - MIN_DATE == calculated_min, - "`MIN_DATE` should have a year flag {:?}", - calculated_min.of().flags() - ); - assert!( - MAX_DATE == calculated_max, - "`MAX_DATE` should have a year flag {:?}", - calculated_max.of().flags() - ); - - // let's also check that the entire range do not exceed 2^44 seconds - // (sometimes used for bounding `Duration` against overflow) - let maxsecs = MAX_DATE.signed_duration_since(MIN_DATE).num_seconds(); - let maxsecs = maxsecs + 86401; // also take care of DateTime - assert!( - maxsecs < (1 << MAX_BITS), - "The entire `NaiveDate` range somehow exceeds 2^{} seconds", - MAX_BITS - ); -} - -impl NaiveDate { - /// Makes a new `NaiveDate` from year and packed ordinal-flags, with a verification. - fn from_of(year: i32, of: Of) -> Option { - if year >= MIN_YEAR && year <= MAX_YEAR && of.valid() { - let Of(of) = of; - Some(NaiveDate { ymdf: (year << 13) | (of as DateImpl) }) - } else { - None - } - } - - /// Makes a new `NaiveDate` from year and packed month-day-flags, with a verification. - fn from_mdf(year: i32, mdf: Mdf) -> Option { - NaiveDate::from_of(year, mdf.to_of()) - } - - /// Makes a new `NaiveDate` from the [calendar date](#calendar-date) - /// (year, month and day). - /// - /// Panics on the out-of-range date, invalid month and/or day. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, Datelike, Weekday}; - /// - /// let d = NaiveDate::from_ymd(2015, 3, 14); - /// assert_eq!(d.year(), 2015); - /// assert_eq!(d.month(), 3); - /// assert_eq!(d.day(), 14); - /// assert_eq!(d.ordinal(), 73); // day of year - /// assert_eq!(d.iso_week().year(), 2015); - /// assert_eq!(d.iso_week().week(), 11); - /// assert_eq!(d.weekday(), Weekday::Sat); - /// assert_eq!(d.num_days_from_ce(), 735671); // days since January 1, 1 CE - /// ~~~~ - pub fn from_ymd(year: i32, month: u32, day: u32) -> NaiveDate { - NaiveDate::from_ymd_opt(year, month, day).expect("invalid or out-of-range date") - } - - /// Makes a new `NaiveDate` from the [calendar date](#calendar-date) - /// (year, month and day). - /// - /// Returns `None` on the out-of-range date, invalid month and/or day. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::NaiveDate; - /// - /// let from_ymd_opt = NaiveDate::from_ymd_opt; - /// - /// assert!(from_ymd_opt(2015, 3, 14).is_some()); - /// assert!(from_ymd_opt(2015, 0, 14).is_none()); - /// assert!(from_ymd_opt(2015, 2, 29).is_none()); - /// assert!(from_ymd_opt(-4, 2, 29).is_some()); // 5 BCE is a leap year - /// assert!(from_ymd_opt(400000, 1, 1).is_none()); - /// assert!(from_ymd_opt(-400000, 1, 1).is_none()); - /// ~~~~ - pub fn from_ymd_opt(year: i32, month: u32, day: u32) -> Option { - let flags = YearFlags::from_year(year); - NaiveDate::from_mdf(year, Mdf::new(month, day, flags)) - } - - /// Makes a new `NaiveDate` from the [ordinal date](#ordinal-date) - /// (year and day of the year). - /// - /// Panics on the out-of-range date and/or invalid day of year. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, Datelike, Weekday}; - /// - /// let d = NaiveDate::from_yo(2015, 73); - /// assert_eq!(d.ordinal(), 73); - /// assert_eq!(d.year(), 2015); - /// assert_eq!(d.month(), 3); - /// assert_eq!(d.day(), 14); - /// assert_eq!(d.iso_week().year(), 2015); - /// assert_eq!(d.iso_week().week(), 11); - /// assert_eq!(d.weekday(), Weekday::Sat); - /// assert_eq!(d.num_days_from_ce(), 735671); // days since January 1, 1 CE - /// ~~~~ - pub fn from_yo(year: i32, ordinal: u32) -> NaiveDate { - NaiveDate::from_yo_opt(year, ordinal).expect("invalid or out-of-range date") - } - - /// Makes a new `NaiveDate` from the [ordinal date](#ordinal-date) - /// (year and day of the year). - /// - /// Returns `None` on the out-of-range date and/or invalid day of year. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::NaiveDate; - /// - /// let from_yo_opt = NaiveDate::from_yo_opt; - /// - /// assert!(from_yo_opt(2015, 100).is_some()); - /// assert!(from_yo_opt(2015, 0).is_none()); - /// assert!(from_yo_opt(2015, 365).is_some()); - /// assert!(from_yo_opt(2015, 366).is_none()); - /// assert!(from_yo_opt(-4, 366).is_some()); // 5 BCE is a leap year - /// assert!(from_yo_opt(400000, 1).is_none()); - /// assert!(from_yo_opt(-400000, 1).is_none()); - /// ~~~~ - pub fn from_yo_opt(year: i32, ordinal: u32) -> Option { - let flags = YearFlags::from_year(year); - NaiveDate::from_of(year, Of::new(ordinal, flags)) - } - - /// Makes a new `NaiveDate` from the [ISO week date](#week-date) - /// (year, week number and day of the week). - /// The resulting `NaiveDate` may have a different year from the input year. - /// - /// Panics on the out-of-range date and/or invalid week number. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, Datelike, Weekday}; - /// - /// let d = NaiveDate::from_isoywd(2015, 11, Weekday::Sat); - /// assert_eq!(d.iso_week().year(), 2015); - /// assert_eq!(d.iso_week().week(), 11); - /// assert_eq!(d.weekday(), Weekday::Sat); - /// assert_eq!(d.year(), 2015); - /// assert_eq!(d.month(), 3); - /// assert_eq!(d.day(), 14); - /// assert_eq!(d.ordinal(), 73); // day of year - /// assert_eq!(d.num_days_from_ce(), 735671); // days since January 1, 1 CE - /// ~~~~ - pub fn from_isoywd(year: i32, week: u32, weekday: Weekday) -> NaiveDate { - NaiveDate::from_isoywd_opt(year, week, weekday).expect("invalid or out-of-range date") - } - - /// Makes a new `NaiveDate` from the [ISO week date](#week-date) - /// (year, week number and day of the week). - /// The resulting `NaiveDate` may have a different year from the input year. - /// - /// Returns `None` on the out-of-range date and/or invalid week number. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, Weekday}; - /// - /// let from_ymd = NaiveDate::from_ymd; - /// let from_isoywd_opt = NaiveDate::from_isoywd_opt; - /// - /// assert_eq!(from_isoywd_opt(2015, 0, Weekday::Sun), None); - /// assert_eq!(from_isoywd_opt(2015, 10, Weekday::Sun), Some(from_ymd(2015, 3, 8))); - /// assert_eq!(from_isoywd_opt(2015, 30, Weekday::Mon), Some(from_ymd(2015, 7, 20))); - /// assert_eq!(from_isoywd_opt(2015, 60, Weekday::Mon), None); - /// - /// assert_eq!(from_isoywd_opt(400000, 10, Weekday::Fri), None); - /// assert_eq!(from_isoywd_opt(-400000, 10, Weekday::Sat), None); - /// ~~~~ - /// - /// The year number of ISO week date may differ from that of the calendar date. - /// - /// ~~~~ - /// # use chrono::{NaiveDate, Weekday}; - /// # let from_ymd = NaiveDate::from_ymd; - /// # let from_isoywd_opt = NaiveDate::from_isoywd_opt; - /// // Mo Tu We Th Fr Sa Su - /// // 2014-W52 22 23 24 25 26 27 28 has 4+ days of new year, - /// // 2015-W01 29 30 31 1 2 3 4 <- so this is the first week - /// assert_eq!(from_isoywd_opt(2014, 52, Weekday::Sun), Some(from_ymd(2014, 12, 28))); - /// assert_eq!(from_isoywd_opt(2014, 53, Weekday::Mon), None); - /// assert_eq!(from_isoywd_opt(2015, 1, Weekday::Mon), Some(from_ymd(2014, 12, 29))); - /// - /// // 2015-W52 21 22 23 24 25 26 27 has 4+ days of old year, - /// // 2015-W53 28 29 30 31 1 2 3 <- so this is the last week - /// // 2016-W01 4 5 6 7 8 9 10 - /// assert_eq!(from_isoywd_opt(2015, 52, Weekday::Sun), Some(from_ymd(2015, 12, 27))); - /// assert_eq!(from_isoywd_opt(2015, 53, Weekday::Sun), Some(from_ymd(2016, 1, 3))); - /// assert_eq!(from_isoywd_opt(2015, 54, Weekday::Mon), None); - /// assert_eq!(from_isoywd_opt(2016, 1, Weekday::Mon), Some(from_ymd(2016, 1, 4))); - /// ~~~~ - pub fn from_isoywd_opt(year: i32, week: u32, weekday: Weekday) -> Option { - let flags = YearFlags::from_year(year); - let nweeks = flags.nisoweeks(); - if 1 <= week && week <= nweeks { - // ordinal = week ordinal - delta - let weekord = week * 7 + weekday as u32; - let delta = flags.isoweek_delta(); - if weekord <= delta { - // ordinal < 1, previous year - let prevflags = YearFlags::from_year(year - 1); - NaiveDate::from_of( - year - 1, - Of::new(weekord + prevflags.ndays() - delta, prevflags), - ) - } else { - let ordinal = weekord - delta; - let ndays = flags.ndays(); - if ordinal <= ndays { - // this year - NaiveDate::from_of(year, Of::new(ordinal, flags)) - } else { - // ordinal > ndays, next year - let nextflags = YearFlags::from_year(year + 1); - NaiveDate::from_of(year + 1, Of::new(ordinal - ndays, nextflags)) - } - } - } else { - None - } - } - - /// Makes a new `NaiveDate` from a day's number in the proleptic Gregorian calendar, with - /// January 1, 1 being day 1. - /// - /// Panics if the date is out of range. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, Datelike, Weekday}; - /// - /// let d = NaiveDate::from_num_days_from_ce(735671); - /// assert_eq!(d.num_days_from_ce(), 735671); // days since January 1, 1 CE - /// assert_eq!(d.year(), 2015); - /// assert_eq!(d.month(), 3); - /// assert_eq!(d.day(), 14); - /// assert_eq!(d.ordinal(), 73); // day of year - /// assert_eq!(d.iso_week().year(), 2015); - /// assert_eq!(d.iso_week().week(), 11); - /// assert_eq!(d.weekday(), Weekday::Sat); - /// ~~~~ - /// - /// While not directly supported by Chrono, - /// it is easy to convert from the Julian day number - /// (January 1, 4713 BCE in the *Julian* calendar being Day 0) - /// to Gregorian with this method. - /// (Note that this panics when `jd` is out of range.) - /// - /// ~~~~ - /// use chrono::NaiveDate; - /// - /// fn jd_to_date(jd: i32) -> NaiveDate { - /// // keep in mind that the Julian day number is 0-based - /// // while this method requires an 1-based number. - /// NaiveDate::from_num_days_from_ce(jd - 1721425) - /// } - /// - /// // January 1, 4713 BCE in Julian = November 24, 4714 BCE in Gregorian - /// assert_eq!(jd_to_date(0), NaiveDate::from_ymd(-4713, 11, 24)); - /// - /// assert_eq!(jd_to_date(1721426), NaiveDate::from_ymd(1, 1, 1)); - /// assert_eq!(jd_to_date(2450000), NaiveDate::from_ymd(1995, 10, 9)); - /// assert_eq!(jd_to_date(2451545), NaiveDate::from_ymd(2000, 1, 1)); - /// ~~~~ - #[inline] - pub fn from_num_days_from_ce(days: i32) -> NaiveDate { - NaiveDate::from_num_days_from_ce_opt(days).expect("out-of-range date") - } - - /// Makes a new `NaiveDate` from a day's number in the proleptic Gregorian calendar, with - /// January 1, 1 being day 1. - /// - /// Returns `None` if the date is out of range. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::NaiveDate; - /// - /// let from_ndays_opt = NaiveDate::from_num_days_from_ce_opt; - /// let from_ymd = NaiveDate::from_ymd; - /// - /// assert_eq!(from_ndays_opt(730_000), Some(from_ymd(1999, 9, 3))); - /// assert_eq!(from_ndays_opt(1), Some(from_ymd(1, 1, 1))); - /// assert_eq!(from_ndays_opt(0), Some(from_ymd(0, 12, 31))); - /// assert_eq!(from_ndays_opt(-1), Some(from_ymd(0, 12, 30))); - /// assert_eq!(from_ndays_opt(100_000_000), None); - /// assert_eq!(from_ndays_opt(-100_000_000), None); - /// ~~~~ - pub fn from_num_days_from_ce_opt(days: i32) -> Option { - let days = days + 365; // make December 31, 1 BCE equal to day 0 - let (year_div_400, cycle) = div_mod_floor(days, 146_097); - let (year_mod_400, ordinal) = internals::cycle_to_yo(cycle as u32); - let flags = YearFlags::from_year_mod_400(year_mod_400 as i32); - NaiveDate::from_of(year_div_400 * 400 + year_mod_400 as i32, Of::new(ordinal, flags)) - } - - /// Makes a new `NaiveDate` by counting the number of occurrences of a particular day-of-week - /// since the beginning of the given month. For instance, if you want the 2nd Friday of March - /// 2017, you would use `NaiveDate::from_weekday_of_month(2017, 3, Weekday::Fri, 2)`. - /// - /// # Panics - /// - /// The resulting `NaiveDate` is guaranteed to be in `month`. If `n` is larger than the number - /// of `weekday` in `month` (eg. the 6th Friday of March 2017) then this function will panic. - /// - /// `n` is 1-indexed. Passing `n=0` will cause a panic. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, Weekday}; - /// - /// let from_weekday_of_month = NaiveDate::from_weekday_of_month; - /// let from_ymd = NaiveDate::from_ymd; - /// - /// assert_eq!(from_weekday_of_month(2018, 8, Weekday::Wed, 1), from_ymd(2018, 8, 1)); - /// assert_eq!(from_weekday_of_month(2018, 8, Weekday::Fri, 1), from_ymd(2018, 8, 3)); - /// assert_eq!(from_weekday_of_month(2018, 8, Weekday::Tue, 2), from_ymd(2018, 8, 14)); - /// assert_eq!(from_weekday_of_month(2018, 8, Weekday::Fri, 4), from_ymd(2018, 8, 24)); - /// assert_eq!(from_weekday_of_month(2018, 8, Weekday::Fri, 5), from_ymd(2018, 8, 31)); - /// ~~~~ - pub fn from_weekday_of_month(year: i32, month: u32, weekday: Weekday, n: u8) -> NaiveDate { - NaiveDate::from_weekday_of_month_opt(year, month, weekday, n).expect("out-of-range date") - } - - /// Makes a new `NaiveDate` by counting the number of occurrences of a particular day-of-week - /// since the beginning of the given month. For instance, if you want the 2nd Friday of March - /// 2017, you would use `NaiveDate::from_weekday_of_month(2017, 3, Weekday::Fri, 2)`. `n` is 1-indexed. - /// - /// ~~~~ - /// use chrono::{NaiveDate, Weekday}; - /// assert_eq!(NaiveDate::from_weekday_of_month_opt(2017, 3, Weekday::Fri, 2), - /// NaiveDate::from_ymd_opt(2017, 3, 10)) - /// ~~~~ - /// - /// Returns `None` if `n` out-of-range; ie. if `n` is larger than the number of `weekday` in - /// `month` (eg. the 6th Friday of March 2017), or if `n == 0`. - pub fn from_weekday_of_month_opt( - year: i32, - month: u32, - weekday: Weekday, - n: u8, - ) -> Option { - if n == 0 { - return None; - } - let first = NaiveDate::from_ymd(year, month, 1).weekday(); - let first_to_dow = (7 + weekday.number_from_monday() - first.number_from_monday()) % 7; - let day = (u32::from(n) - 1) * 7 + first_to_dow + 1; - NaiveDate::from_ymd_opt(year, month, day) - } - - /// Parses a string with the specified format string and returns a new `NaiveDate`. - /// See the [`format::strftime` module](../format/strftime/index.html) - /// on the supported escape sequences. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::NaiveDate; - /// - /// let parse_from_str = NaiveDate::parse_from_str; - /// - /// assert_eq!(parse_from_str("2015-09-05", "%Y-%m-%d"), - /// Ok(NaiveDate::from_ymd(2015, 9, 5))); - /// assert_eq!(parse_from_str("5sep2015", "%d%b%Y"), - /// Ok(NaiveDate::from_ymd(2015, 9, 5))); - /// ~~~~ - /// - /// Time and offset is ignored for the purpose of parsing. - /// - /// ~~~~ - /// # use chrono::NaiveDate; - /// # let parse_from_str = NaiveDate::parse_from_str; - /// assert_eq!(parse_from_str("2014-5-17T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), - /// Ok(NaiveDate::from_ymd(2014, 5, 17))); - /// ~~~~ - /// - /// Out-of-bound dates or insufficient fields are errors. - /// - /// ~~~~ - /// # use chrono::NaiveDate; - /// # let parse_from_str = NaiveDate::parse_from_str; - /// assert!(parse_from_str("2015/9", "%Y/%m").is_err()); - /// assert!(parse_from_str("2015/9/31", "%Y/%m/%d").is_err()); - /// ~~~~ - /// - /// All parsed fields should be consistent to each other, otherwise it's an error. - /// - /// ~~~~ - /// # use chrono::NaiveDate; - /// # let parse_from_str = NaiveDate::parse_from_str; - /// assert!(parse_from_str("Sat, 09 Aug 2013", "%a, %d %b %Y").is_err()); - /// ~~~~ - pub fn parse_from_str(s: &str, fmt: &str) -> ParseResult { - let mut parsed = Parsed::new(); - parse(&mut parsed, s, StrftimeItems::new(fmt))?; - parsed.to_naive_date() - } - - /// Makes a new `NaiveDateTime` from the current date and given `NaiveTime`. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, NaiveTime, NaiveDateTime}; - /// - /// let d = NaiveDate::from_ymd(2015, 6, 3); - /// let t = NaiveTime::from_hms_milli(12, 34, 56, 789); - /// - /// let dt: NaiveDateTime = d.and_time(t); - /// assert_eq!(dt.date(), d); - /// assert_eq!(dt.time(), t); - /// ~~~~ - #[inline] - pub fn and_time(&self, time: NaiveTime) -> NaiveDateTime { - NaiveDateTime::new(*self, time) - } - - /// Makes a new `NaiveDateTime` from the current date, hour, minute and second. - /// - /// No [leap second](./struct.NaiveTime.html#leap-second-handling) is allowed here; - /// use `NaiveDate::and_hms_*` methods with a subsecond parameter instead. - /// - /// Panics on invalid hour, minute and/or second. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, NaiveDateTime, Datelike, Timelike, Weekday}; - /// - /// let d = NaiveDate::from_ymd(2015, 6, 3); - /// - /// let dt: NaiveDateTime = d.and_hms(12, 34, 56); - /// assert_eq!(dt.year(), 2015); - /// assert_eq!(dt.weekday(), Weekday::Wed); - /// assert_eq!(dt.second(), 56); - /// ~~~~ - #[inline] - pub fn and_hms(&self, hour: u32, min: u32, sec: u32) -> NaiveDateTime { - self.and_hms_opt(hour, min, sec).expect("invalid time") - } - - /// Makes a new `NaiveDateTime` from the current date, hour, minute and second. - /// - /// No [leap second](./struct.NaiveTime.html#leap-second-handling) is allowed here; - /// use `NaiveDate::and_hms_*_opt` methods with a subsecond parameter instead. - /// - /// Returns `None` on invalid hour, minute and/or second. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::NaiveDate; - /// - /// let d = NaiveDate::from_ymd(2015, 6, 3); - /// assert!(d.and_hms_opt(12, 34, 56).is_some()); - /// assert!(d.and_hms_opt(12, 34, 60).is_none()); // use `and_hms_milli_opt` instead - /// assert!(d.and_hms_opt(12, 60, 56).is_none()); - /// assert!(d.and_hms_opt(24, 34, 56).is_none()); - /// ~~~~ - #[inline] - pub fn and_hms_opt(&self, hour: u32, min: u32, sec: u32) -> Option { - NaiveTime::from_hms_opt(hour, min, sec).map(|time| self.and_time(time)) - } - - /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and millisecond. - /// - /// The millisecond part can exceed 1,000 - /// in order to represent the [leap second](./struct.NaiveTime.html#leap-second-handling). - /// - /// Panics on invalid hour, minute, second and/or millisecond. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, NaiveDateTime, Datelike, Timelike, Weekday}; - /// - /// let d = NaiveDate::from_ymd(2015, 6, 3); - /// - /// let dt: NaiveDateTime = d.and_hms_milli(12, 34, 56, 789); - /// assert_eq!(dt.year(), 2015); - /// assert_eq!(dt.weekday(), Weekday::Wed); - /// assert_eq!(dt.second(), 56); - /// assert_eq!(dt.nanosecond(), 789_000_000); - /// ~~~~ - #[inline] - pub fn and_hms_milli(&self, hour: u32, min: u32, sec: u32, milli: u32) -> NaiveDateTime { - self.and_hms_milli_opt(hour, min, sec, milli).expect("invalid time") - } - - /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and millisecond. - /// - /// The millisecond part can exceed 1,000 - /// in order to represent the [leap second](./struct.NaiveTime.html#leap-second-handling). - /// - /// Returns `None` on invalid hour, minute, second and/or millisecond. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::NaiveDate; - /// - /// let d = NaiveDate::from_ymd(2015, 6, 3); - /// assert!(d.and_hms_milli_opt(12, 34, 56, 789).is_some()); - /// assert!(d.and_hms_milli_opt(12, 34, 59, 1_789).is_some()); // leap second - /// assert!(d.and_hms_milli_opt(12, 34, 59, 2_789).is_none()); - /// assert!(d.and_hms_milli_opt(12, 34, 60, 789).is_none()); - /// assert!(d.and_hms_milli_opt(12, 60, 56, 789).is_none()); - /// assert!(d.and_hms_milli_opt(24, 34, 56, 789).is_none()); - /// ~~~~ - #[inline] - pub fn and_hms_milli_opt( - &self, - hour: u32, - min: u32, - sec: u32, - milli: u32, - ) -> Option { - NaiveTime::from_hms_milli_opt(hour, min, sec, milli).map(|time| self.and_time(time)) - } - - /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and microsecond. - /// - /// The microsecond part can exceed 1,000,000 - /// in order to represent the [leap second](./struct.NaiveTime.html#leap-second-handling). - /// - /// Panics on invalid hour, minute, second and/or microsecond. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, NaiveDateTime, Datelike, Timelike, Weekday}; - /// - /// let d = NaiveDate::from_ymd(2015, 6, 3); - /// - /// let dt: NaiveDateTime = d.and_hms_micro(12, 34, 56, 789_012); - /// assert_eq!(dt.year(), 2015); - /// assert_eq!(dt.weekday(), Weekday::Wed); - /// assert_eq!(dt.second(), 56); - /// assert_eq!(dt.nanosecond(), 789_012_000); - /// ~~~~ - #[inline] - pub fn and_hms_micro(&self, hour: u32, min: u32, sec: u32, micro: u32) -> NaiveDateTime { - self.and_hms_micro_opt(hour, min, sec, micro).expect("invalid time") - } - - /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and microsecond. - /// - /// The microsecond part can exceed 1,000,000 - /// in order to represent the [leap second](./struct.NaiveTime.html#leap-second-handling). - /// - /// Returns `None` on invalid hour, minute, second and/or microsecond. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::NaiveDate; - /// - /// let d = NaiveDate::from_ymd(2015, 6, 3); - /// assert!(d.and_hms_micro_opt(12, 34, 56, 789_012).is_some()); - /// assert!(d.and_hms_micro_opt(12, 34, 59, 1_789_012).is_some()); // leap second - /// assert!(d.and_hms_micro_opt(12, 34, 59, 2_789_012).is_none()); - /// assert!(d.and_hms_micro_opt(12, 34, 60, 789_012).is_none()); - /// assert!(d.and_hms_micro_opt(12, 60, 56, 789_012).is_none()); - /// assert!(d.and_hms_micro_opt(24, 34, 56, 789_012).is_none()); - /// ~~~~ - #[inline] - pub fn and_hms_micro_opt( - &self, - hour: u32, - min: u32, - sec: u32, - micro: u32, - ) -> Option { - NaiveTime::from_hms_micro_opt(hour, min, sec, micro).map(|time| self.and_time(time)) - } - - /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and nanosecond. - /// - /// The nanosecond part can exceed 1,000,000,000 - /// in order to represent the [leap second](./struct.NaiveTime.html#leap-second-handling). - /// - /// Panics on invalid hour, minute, second and/or nanosecond. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, NaiveDateTime, Datelike, Timelike, Weekday}; - /// - /// let d = NaiveDate::from_ymd(2015, 6, 3); - /// - /// let dt: NaiveDateTime = d.and_hms_nano(12, 34, 56, 789_012_345); - /// assert_eq!(dt.year(), 2015); - /// assert_eq!(dt.weekday(), Weekday::Wed); - /// assert_eq!(dt.second(), 56); - /// assert_eq!(dt.nanosecond(), 789_012_345); - /// ~~~~ - #[inline] - pub fn and_hms_nano(&self, hour: u32, min: u32, sec: u32, nano: u32) -> NaiveDateTime { - self.and_hms_nano_opt(hour, min, sec, nano).expect("invalid time") - } - - /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and nanosecond. - /// - /// The nanosecond part can exceed 1,000,000,000 - /// in order to represent the [leap second](./struct.NaiveTime.html#leap-second-handling). - /// - /// Returns `None` on invalid hour, minute, second and/or nanosecond. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::NaiveDate; - /// - /// let d = NaiveDate::from_ymd(2015, 6, 3); - /// assert!(d.and_hms_nano_opt(12, 34, 56, 789_012_345).is_some()); - /// assert!(d.and_hms_nano_opt(12, 34, 59, 1_789_012_345).is_some()); // leap second - /// assert!(d.and_hms_nano_opt(12, 34, 59, 2_789_012_345).is_none()); - /// assert!(d.and_hms_nano_opt(12, 34, 60, 789_012_345).is_none()); - /// assert!(d.and_hms_nano_opt(12, 60, 56, 789_012_345).is_none()); - /// assert!(d.and_hms_nano_opt(24, 34, 56, 789_012_345).is_none()); - /// ~~~~ - #[inline] - pub fn and_hms_nano_opt( - &self, - hour: u32, - min: u32, - sec: u32, - nano: u32, - ) -> Option { - NaiveTime::from_hms_nano_opt(hour, min, sec, nano).map(|time| self.and_time(time)) - } - - /// Returns the packed month-day-flags. - #[inline] - fn mdf(&self) -> Mdf { - self.of().to_mdf() - } - - /// Returns the packed ordinal-flags. - #[inline] - fn of(&self) -> Of { - Of((self.ymdf & 0b1_1111_1111_1111) as u32) - } - - /// Makes a new `NaiveDate` with the packed month-day-flags changed. - /// - /// Returns `None` when the resulting `NaiveDate` would be invalid. - #[inline] - fn with_mdf(&self, mdf: Mdf) -> Option { - self.with_of(mdf.to_of()) - } - - /// Makes a new `NaiveDate` with the packed ordinal-flags changed. - /// - /// Returns `None` when the resulting `NaiveDate` would be invalid. - #[inline] - fn with_of(&self, of: Of) -> Option { - if of.valid() { - let Of(of) = of; - Some(NaiveDate { ymdf: (self.ymdf & !0b1_1111_1111_1111) | of as DateImpl }) - } else { - None - } - } - - /// Makes a new `NaiveDate` for the next calendar date. - /// - /// Panics when `self` is the last representable date. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::NaiveDate; - /// - /// assert_eq!(NaiveDate::from_ymd(2015, 6, 3).succ(), NaiveDate::from_ymd(2015, 6, 4)); - /// assert_eq!(NaiveDate::from_ymd(2015, 6, 30).succ(), NaiveDate::from_ymd(2015, 7, 1)); - /// assert_eq!(NaiveDate::from_ymd(2015, 12, 31).succ(), NaiveDate::from_ymd(2016, 1, 1)); - /// ~~~~ - #[inline] - pub fn succ(&self) -> NaiveDate { - self.succ_opt().expect("out of bound") - } - - /// Makes a new `NaiveDate` for the next calendar date. - /// - /// Returns `None` when `self` is the last representable date. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::NaiveDate; - /// use chrono::naive::MAX_DATE; - /// - /// assert_eq!(NaiveDate::from_ymd(2015, 6, 3).succ_opt(), - /// Some(NaiveDate::from_ymd(2015, 6, 4))); - /// assert_eq!(MAX_DATE.succ_opt(), None); - /// ~~~~ - #[inline] - pub fn succ_opt(&self) -> Option { - self.with_of(self.of().succ()).or_else(|| NaiveDate::from_ymd_opt(self.year() + 1, 1, 1)) - } - - /// Makes a new `NaiveDate` for the previous calendar date. - /// - /// Panics when `self` is the first representable date. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::NaiveDate; - /// - /// assert_eq!(NaiveDate::from_ymd(2015, 6, 3).pred(), NaiveDate::from_ymd(2015, 6, 2)); - /// assert_eq!(NaiveDate::from_ymd(2015, 6, 1).pred(), NaiveDate::from_ymd(2015, 5, 31)); - /// assert_eq!(NaiveDate::from_ymd(2015, 1, 1).pred(), NaiveDate::from_ymd(2014, 12, 31)); - /// ~~~~ - #[inline] - pub fn pred(&self) -> NaiveDate { - self.pred_opt().expect("out of bound") - } - - /// Makes a new `NaiveDate` for the previous calendar date. - /// - /// Returns `None` when `self` is the first representable date. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::NaiveDate; - /// use chrono::naive::MIN_DATE; - /// - /// assert_eq!(NaiveDate::from_ymd(2015, 6, 3).pred_opt(), - /// Some(NaiveDate::from_ymd(2015, 6, 2))); - /// assert_eq!(MIN_DATE.pred_opt(), None); - /// ~~~~ - #[inline] - pub fn pred_opt(&self) -> Option { - self.with_of(self.of().pred()).or_else(|| NaiveDate::from_ymd_opt(self.year() - 1, 12, 31)) - } - - /// Adds the `days` part of given `Duration` to the current date. - /// - /// Returns `None` when it will result in overflow. - /// - /// # Example - /// - /// ~~~~ - /// # extern crate chrono; fn main() { - /// use chrono::{Duration, NaiveDate}; - /// use chrono::naive::MAX_DATE; - /// - /// let d = NaiveDate::from_ymd(2015, 9, 5); - /// assert_eq!(d.checked_add_signed(Duration::days(40)), - /// Some(NaiveDate::from_ymd(2015, 10, 15))); - /// assert_eq!(d.checked_add_signed(Duration::days(-40)), - /// Some(NaiveDate::from_ymd(2015, 7, 27))); - /// assert_eq!(d.checked_add_signed(Duration::days(1_000_000_000)), None); - /// assert_eq!(d.checked_add_signed(Duration::days(-1_000_000_000)), None); - /// assert_eq!(MAX_DATE.checked_add_signed(Duration::days(1)), None); - /// # } - /// ~~~~ - pub fn checked_add_signed(self, rhs: OldDuration) -> Option { - let year = self.year(); - let (mut year_div_400, year_mod_400) = div_mod_floor(year, 400); - let cycle = internals::yo_to_cycle(year_mod_400 as u32, self.of().ordinal()); - let cycle = try_opt!((cycle as i32).checked_add(try_opt!(rhs.num_days().to_i32()))); - let (cycle_div_400y, cycle) = div_mod_floor(cycle, 146_097); - year_div_400 += cycle_div_400y; - - let (year_mod_400, ordinal) = internals::cycle_to_yo(cycle as u32); - let flags = YearFlags::from_year_mod_400(year_mod_400 as i32); - NaiveDate::from_of(year_div_400 * 400 + year_mod_400 as i32, Of::new(ordinal, flags)) - } - - /// Subtracts the `days` part of given `Duration` from the current date. - /// - /// Returns `None` when it will result in overflow. - /// - /// # Example - /// - /// ~~~~ - /// # extern crate chrono; fn main() { - /// use chrono::{Duration, NaiveDate}; - /// use chrono::naive::MIN_DATE; - /// - /// let d = NaiveDate::from_ymd(2015, 9, 5); - /// assert_eq!(d.checked_sub_signed(Duration::days(40)), - /// Some(NaiveDate::from_ymd(2015, 7, 27))); - /// assert_eq!(d.checked_sub_signed(Duration::days(-40)), - /// Some(NaiveDate::from_ymd(2015, 10, 15))); - /// assert_eq!(d.checked_sub_signed(Duration::days(1_000_000_000)), None); - /// assert_eq!(d.checked_sub_signed(Duration::days(-1_000_000_000)), None); - /// assert_eq!(MIN_DATE.checked_sub_signed(Duration::days(1)), None); - /// # } - /// ~~~~ - pub fn checked_sub_signed(self, rhs: OldDuration) -> Option { - let year = self.year(); - let (mut year_div_400, year_mod_400) = div_mod_floor(year, 400); - let cycle = internals::yo_to_cycle(year_mod_400 as u32, self.of().ordinal()); - let cycle = try_opt!((cycle as i32).checked_sub(try_opt!(rhs.num_days().to_i32()))); - let (cycle_div_400y, cycle) = div_mod_floor(cycle, 146_097); - year_div_400 += cycle_div_400y; - - let (year_mod_400, ordinal) = internals::cycle_to_yo(cycle as u32); - let flags = YearFlags::from_year_mod_400(year_mod_400 as i32); - NaiveDate::from_of(year_div_400 * 400 + year_mod_400 as i32, Of::new(ordinal, flags)) - } - - /// Subtracts another `NaiveDate` from the current date. - /// Returns a `Duration` of integral numbers. - /// - /// This does not overflow or underflow at all, - /// as all possible output fits in the range of `Duration`. - /// - /// # Example - /// - /// ~~~~ - /// # extern crate chrono; fn main() { - /// use chrono::{Duration, NaiveDate}; - /// - /// let from_ymd = NaiveDate::from_ymd; - /// let since = NaiveDate::signed_duration_since; - /// - /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2014, 1, 1)), Duration::zero()); - /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2013, 12, 31)), Duration::days(1)); - /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2014, 1, 2)), Duration::days(-1)); - /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2013, 9, 23)), Duration::days(100)); - /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2013, 1, 1)), Duration::days(365)); - /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2010, 1, 1)), Duration::days(365*4 + 1)); - /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(1614, 1, 1)), Duration::days(365*400 + 97)); - /// # } - /// ~~~~ - pub fn signed_duration_since(self, rhs: NaiveDate) -> OldDuration { - let year1 = self.year(); - let year2 = rhs.year(); - let (year1_div_400, year1_mod_400) = div_mod_floor(year1, 400); - let (year2_div_400, year2_mod_400) = div_mod_floor(year2, 400); - let cycle1 = i64::from(internals::yo_to_cycle(year1_mod_400 as u32, self.of().ordinal())); - let cycle2 = i64::from(internals::yo_to_cycle(year2_mod_400 as u32, rhs.of().ordinal())); - OldDuration::days( - (i64::from(year1_div_400) - i64::from(year2_div_400)) * 146_097 + (cycle1 - cycle2), - ) - } - - /// Formats the date with the specified formatting items. - /// Otherwise it is the same as the ordinary `format` method. - /// - /// The `Iterator` of items should be `Clone`able, - /// since the resulting `DelayedFormat` value may be formatted multiple times. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::NaiveDate; - /// use chrono::format::strftime::StrftimeItems; - /// - /// let fmt = StrftimeItems::new("%Y-%m-%d"); - /// let d = NaiveDate::from_ymd(2015, 9, 5); - /// assert_eq!(d.format_with_items(fmt.clone()).to_string(), "2015-09-05"); - /// assert_eq!(d.format("%Y-%m-%d").to_string(), "2015-09-05"); - /// ~~~~ - /// - /// The resulting `DelayedFormat` can be formatted directly via the `Display` trait. - /// - /// ~~~~ - /// # use chrono::NaiveDate; - /// # use chrono::format::strftime::StrftimeItems; - /// # let fmt = StrftimeItems::new("%Y-%m-%d").clone(); - /// # let d = NaiveDate::from_ymd(2015, 9, 5); - /// assert_eq!(format!("{}", d.format_with_items(fmt)), "2015-09-05"); - /// ~~~~ - #[cfg(any(feature = "alloc", feature = "std", test))] - #[inline] - pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat - where - I: Iterator + Clone, - B: Borrow>, - { - DelayedFormat::new(Some(*self), None, items) - } - - /// Formats the date with the specified format string. - /// See the [`format::strftime` module](../format/strftime/index.html) - /// on the supported escape sequences. - /// - /// This returns a `DelayedFormat`, - /// which gets converted to a string only when actual formatting happens. - /// You may use the `to_string` method to get a `String`, - /// or just feed it into `print!` and other formatting macros. - /// (In this way it avoids the redundant memory allocation.) - /// - /// A wrong format string does *not* issue an error immediately. - /// Rather, converting or formatting the `DelayedFormat` fails. - /// You are recommended to immediately use `DelayedFormat` for this reason. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::NaiveDate; - /// - /// let d = NaiveDate::from_ymd(2015, 9, 5); - /// assert_eq!(d.format("%Y-%m-%d").to_string(), "2015-09-05"); - /// assert_eq!(d.format("%A, %-d %B, %C%y").to_string(), "Saturday, 5 September, 2015"); - /// ~~~~ - /// - /// The resulting `DelayedFormat` can be formatted directly via the `Display` trait. - /// - /// ~~~~ - /// # use chrono::NaiveDate; - /// # let d = NaiveDate::from_ymd(2015, 9, 5); - /// assert_eq!(format!("{}", d.format("%Y-%m-%d")), "2015-09-05"); - /// assert_eq!(format!("{}", d.format("%A, %-d %B, %C%y")), "Saturday, 5 September, 2015"); - /// ~~~~ - #[cfg(any(feature = "alloc", feature = "std", test))] - #[inline] - pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { - self.format_with_items(StrftimeItems::new(fmt)) - } - - /// Returns an iterator that steps by days until the last representable date. - /// - /// # Example - /// - /// ``` - /// # use chrono::NaiveDate; - /// - /// let expected = [ - /// NaiveDate::from_ymd(2016, 2, 27), - /// NaiveDate::from_ymd(2016, 2, 28), - /// NaiveDate::from_ymd(2016, 2, 29), - /// NaiveDate::from_ymd(2016, 3, 1), - /// ]; - /// - /// let mut count = 0; - /// for (idx, d) in NaiveDate::from_ymd(2016, 2, 27).iter_days().take(4).enumerate() { - /// assert_eq!(d, expected[idx]); - /// count += 1; - /// } - /// assert_eq!(count, 4); - /// ``` - #[inline] - pub fn iter_days(&self) -> NaiveDateDaysIterator { - NaiveDateDaysIterator { value: *self } - } - - /// Returns an iterator that steps by weeks until the last representable date. - /// - /// # Example - /// - /// ``` - /// # use chrono::NaiveDate; - /// - /// let expected = [ - /// NaiveDate::from_ymd(2016, 2, 27), - /// NaiveDate::from_ymd(2016, 3, 5), - /// NaiveDate::from_ymd(2016, 3, 12), - /// NaiveDate::from_ymd(2016, 3, 19), - /// ]; - /// - /// let mut count = 0; - /// for (idx, d) in NaiveDate::from_ymd(2016, 2, 27).iter_weeks().take(4).enumerate() { - /// assert_eq!(d, expected[idx]); - /// count += 1; - /// } - /// assert_eq!(count, 4); - /// ``` - #[inline] - pub fn iter_weeks(&self) -> NaiveDateWeeksIterator { - NaiveDateWeeksIterator { value: *self } - } -} - -impl Datelike for NaiveDate { - /// Returns the year number in the [calendar date](#calendar-date). - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, Datelike}; - /// - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).year(), 2015); - /// assert_eq!(NaiveDate::from_ymd(-308, 3, 14).year(), -308); // 309 BCE - /// ~~~~ - #[inline] - fn year(&self) -> i32 { - self.ymdf >> 13 - } - - /// Returns the month number starting from 1. - /// - /// The return value ranges from 1 to 12. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, Datelike}; - /// - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).month(), 9); - /// assert_eq!(NaiveDate::from_ymd(-308, 3, 14).month(), 3); - /// ~~~~ - #[inline] - fn month(&self) -> u32 { - self.mdf().month() - } - - /// Returns the month number starting from 0. - /// - /// The return value ranges from 0 to 11. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, Datelike}; - /// - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).month0(), 8); - /// assert_eq!(NaiveDate::from_ymd(-308, 3, 14).month0(), 2); - /// ~~~~ - #[inline] - fn month0(&self) -> u32 { - self.mdf().month() - 1 - } - - /// Returns the day of month starting from 1. - /// - /// The return value ranges from 1 to 31. (The last day of month differs by months.) - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, Datelike}; - /// - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).day(), 8); - /// assert_eq!(NaiveDate::from_ymd(-308, 3, 14).day(), 14); - /// ~~~~ - /// - /// Combined with [`NaiveDate::pred`](#method.pred), - /// one can determine the number of days in a particular month. - /// (Note that this panics when `year` is out of range.) - /// - /// ~~~~ - /// use chrono::{NaiveDate, Datelike}; - /// - /// fn ndays_in_month(year: i32, month: u32) -> u32 { - /// // the first day of the next month... - /// let (y, m) = if month == 12 { (year + 1, 1) } else { (year, month + 1) }; - /// let d = NaiveDate::from_ymd(y, m, 1); - /// - /// // ...is preceded by the last day of the original month - /// d.pred().day() - /// } - /// - /// assert_eq!(ndays_in_month(2015, 8), 31); - /// assert_eq!(ndays_in_month(2015, 9), 30); - /// assert_eq!(ndays_in_month(2015, 12), 31); - /// assert_eq!(ndays_in_month(2016, 2), 29); - /// assert_eq!(ndays_in_month(2017, 2), 28); - /// ~~~~ - #[inline] - fn day(&self) -> u32 { - self.mdf().day() - } - - /// Returns the day of month starting from 0. - /// - /// The return value ranges from 0 to 30. (The last day of month differs by months.) - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, Datelike}; - /// - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).day0(), 7); - /// assert_eq!(NaiveDate::from_ymd(-308, 3, 14).day0(), 13); - /// ~~~~ - #[inline] - fn day0(&self) -> u32 { - self.mdf().day() - 1 - } - - /// Returns the day of year starting from 1. - /// - /// The return value ranges from 1 to 366. (The last day of year differs by years.) - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, Datelike}; - /// - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).ordinal(), 251); - /// assert_eq!(NaiveDate::from_ymd(-308, 3, 14).ordinal(), 74); - /// ~~~~ - /// - /// Combined with [`NaiveDate::pred`](#method.pred), - /// one can determine the number of days in a particular year. - /// (Note that this panics when `year` is out of range.) - /// - /// ~~~~ - /// use chrono::{NaiveDate, Datelike}; - /// - /// fn ndays_in_year(year: i32) -> u32 { - /// // the first day of the next year... - /// let d = NaiveDate::from_ymd(year + 1, 1, 1); - /// - /// // ...is preceded by the last day of the original year - /// d.pred().ordinal() - /// } - /// - /// assert_eq!(ndays_in_year(2015), 365); - /// assert_eq!(ndays_in_year(2016), 366); - /// assert_eq!(ndays_in_year(2017), 365); - /// assert_eq!(ndays_in_year(2000), 366); - /// assert_eq!(ndays_in_year(2100), 365); - /// ~~~~ - #[inline] - fn ordinal(&self) -> u32 { - self.of().ordinal() - } - - /// Returns the day of year starting from 0. - /// - /// The return value ranges from 0 to 365. (The last day of year differs by years.) - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, Datelike}; - /// - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).ordinal0(), 250); - /// assert_eq!(NaiveDate::from_ymd(-308, 3, 14).ordinal0(), 73); - /// ~~~~ - #[inline] - fn ordinal0(&self) -> u32 { - self.of().ordinal() - 1 - } - - /// Returns the day of week. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, Datelike, Weekday}; - /// - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).weekday(), Weekday::Tue); - /// assert_eq!(NaiveDate::from_ymd(-308, 3, 14).weekday(), Weekday::Fri); - /// ~~~~ - #[inline] - fn weekday(&self) -> Weekday { - self.of().weekday() - } - - #[inline] - fn iso_week(&self) -> IsoWeek { - isoweek::iso_week_from_yof(self.year(), self.of()) - } - - /// Makes a new `NaiveDate` with the year number changed. - /// - /// Returns `None` when the resulting `NaiveDate` would be invalid. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, Datelike}; - /// - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).with_year(2016), - /// Some(NaiveDate::from_ymd(2016, 9, 8))); - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).with_year(-308), - /// Some(NaiveDate::from_ymd(-308, 9, 8))); - /// ~~~~ - /// - /// A leap day (February 29) is a good example that this method can return `None`. - /// - /// ~~~~ - /// # use chrono::{NaiveDate, Datelike}; - /// assert!(NaiveDate::from_ymd(2016, 2, 29).with_year(2015).is_none()); - /// assert!(NaiveDate::from_ymd(2016, 2, 29).with_year(2020).is_some()); - /// ~~~~ - #[inline] - fn with_year(&self, year: i32) -> Option { - // we need to operate with `mdf` since we should keep the month and day number as is - let mdf = self.mdf(); - - // adjust the flags as needed - let flags = YearFlags::from_year(year); - let mdf = mdf.with_flags(flags); - - NaiveDate::from_mdf(year, mdf) - } - - /// Makes a new `NaiveDate` with the month number (starting from 1) changed. - /// - /// Returns `None` when the resulting `NaiveDate` would be invalid. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, Datelike}; - /// - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).with_month(10), - /// Some(NaiveDate::from_ymd(2015, 10, 8))); - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).with_month(13), None); // no month 13 - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 30).with_month(2), None); // no February 30 - /// ~~~~ - #[inline] - fn with_month(&self, month: u32) -> Option { - self.with_mdf(self.mdf().with_month(month)) - } - - /// Makes a new `NaiveDate` with the month number (starting from 0) changed. - /// - /// Returns `None` when the resulting `NaiveDate` would be invalid. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, Datelike}; - /// - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).with_month0(9), - /// Some(NaiveDate::from_ymd(2015, 10, 8))); - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).with_month0(12), None); // no month 13 - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 30).with_month0(1), None); // no February 30 - /// ~~~~ - #[inline] - fn with_month0(&self, month0: u32) -> Option { - self.with_mdf(self.mdf().with_month(month0 + 1)) - } - - /// Makes a new `NaiveDate` with the day of month (starting from 1) changed. - /// - /// Returns `None` when the resulting `NaiveDate` would be invalid. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, Datelike}; - /// - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).with_day(30), - /// Some(NaiveDate::from_ymd(2015, 9, 30))); - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).with_day(31), - /// None); // no September 31 - /// ~~~~ - #[inline] - fn with_day(&self, day: u32) -> Option { - self.with_mdf(self.mdf().with_day(day)) - } - - /// Makes a new `NaiveDate` with the day of month (starting from 0) changed. - /// - /// Returns `None` when the resulting `NaiveDate` would be invalid. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, Datelike}; - /// - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).with_day0(29), - /// Some(NaiveDate::from_ymd(2015, 9, 30))); - /// assert_eq!(NaiveDate::from_ymd(2015, 9, 8).with_day0(30), - /// None); // no September 31 - /// ~~~~ - #[inline] - fn with_day0(&self, day0: u32) -> Option { - self.with_mdf(self.mdf().with_day(day0 + 1)) - } - - /// Makes a new `NaiveDate` with the day of year (starting from 1) changed. - /// - /// Returns `None` when the resulting `NaiveDate` would be invalid. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, Datelike}; - /// - /// assert_eq!(NaiveDate::from_ymd(2015, 1, 1).with_ordinal(60), - /// Some(NaiveDate::from_ymd(2015, 3, 1))); - /// assert_eq!(NaiveDate::from_ymd(2015, 1, 1).with_ordinal(366), - /// None); // 2015 had only 365 days - /// - /// assert_eq!(NaiveDate::from_ymd(2016, 1, 1).with_ordinal(60), - /// Some(NaiveDate::from_ymd(2016, 2, 29))); - /// assert_eq!(NaiveDate::from_ymd(2016, 1, 1).with_ordinal(366), - /// Some(NaiveDate::from_ymd(2016, 12, 31))); - /// ~~~~ - #[inline] - fn with_ordinal(&self, ordinal: u32) -> Option { - self.with_of(self.of().with_ordinal(ordinal)) - } - - /// Makes a new `NaiveDate` with the day of year (starting from 0) changed. - /// - /// Returns `None` when the resulting `NaiveDate` would be invalid. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, Datelike}; - /// - /// assert_eq!(NaiveDate::from_ymd(2015, 1, 1).with_ordinal0(59), - /// Some(NaiveDate::from_ymd(2015, 3, 1))); - /// assert_eq!(NaiveDate::from_ymd(2015, 1, 1).with_ordinal0(365), - /// None); // 2015 had only 365 days - /// - /// assert_eq!(NaiveDate::from_ymd(2016, 1, 1).with_ordinal0(59), - /// Some(NaiveDate::from_ymd(2016, 2, 29))); - /// assert_eq!(NaiveDate::from_ymd(2016, 1, 1).with_ordinal0(365), - /// Some(NaiveDate::from_ymd(2016, 12, 31))); - /// ~~~~ - #[inline] - fn with_ordinal0(&self, ordinal0: u32) -> Option { - self.with_of(self.of().with_ordinal(ordinal0 + 1)) - } -} - -/// An addition of `Duration` to `NaiveDate` discards the fractional days, -/// rounding to the closest integral number of days towards `Duration::zero()`. -/// -/// Panics on underflow or overflow. -/// Use [`NaiveDate::checked_add_signed`](#method.checked_add_signed) to detect that. -/// -/// # Example -/// -/// ~~~~ -/// # extern crate chrono; fn main() { -/// use chrono::{Duration, NaiveDate}; -/// -/// let from_ymd = NaiveDate::from_ymd; -/// -/// assert_eq!(from_ymd(2014, 1, 1) + Duration::zero(), from_ymd(2014, 1, 1)); -/// assert_eq!(from_ymd(2014, 1, 1) + Duration::seconds(86399), from_ymd(2014, 1, 1)); -/// assert_eq!(from_ymd(2014, 1, 1) + Duration::seconds(-86399), from_ymd(2014, 1, 1)); -/// assert_eq!(from_ymd(2014, 1, 1) + Duration::days(1), from_ymd(2014, 1, 2)); -/// assert_eq!(from_ymd(2014, 1, 1) + Duration::days(-1), from_ymd(2013, 12, 31)); -/// assert_eq!(from_ymd(2014, 1, 1) + Duration::days(364), from_ymd(2014, 12, 31)); -/// assert_eq!(from_ymd(2014, 1, 1) + Duration::days(365*4 + 1), from_ymd(2018, 1, 1)); -/// assert_eq!(from_ymd(2014, 1, 1) + Duration::days(365*400 + 97), from_ymd(2414, 1, 1)); -/// # } -/// ~~~~ -impl Add for NaiveDate { - type Output = NaiveDate; - - #[inline] - fn add(self, rhs: OldDuration) -> NaiveDate { - self.checked_add_signed(rhs).expect("`NaiveDate + Duration` overflowed") - } -} - -impl AddAssign for NaiveDate { - #[inline] - fn add_assign(&mut self, rhs: OldDuration) { - *self = self.add(rhs); - } -} - -/// A subtraction of `Duration` from `NaiveDate` discards the fractional days, -/// rounding to the closest integral number of days towards `Duration::zero()`. -/// It is the same as the addition with a negated `Duration`. -/// -/// Panics on underflow or overflow. -/// Use [`NaiveDate::checked_sub_signed`](#method.checked_sub_signed) to detect that. -/// -/// # Example -/// -/// ~~~~ -/// # extern crate chrono; fn main() { -/// use chrono::{Duration, NaiveDate}; -/// -/// let from_ymd = NaiveDate::from_ymd; -/// -/// assert_eq!(from_ymd(2014, 1, 1) - Duration::zero(), from_ymd(2014, 1, 1)); -/// assert_eq!(from_ymd(2014, 1, 1) - Duration::seconds(86399), from_ymd(2014, 1, 1)); -/// assert_eq!(from_ymd(2014, 1, 1) - Duration::seconds(-86399), from_ymd(2014, 1, 1)); -/// assert_eq!(from_ymd(2014, 1, 1) - Duration::days(1), from_ymd(2013, 12, 31)); -/// assert_eq!(from_ymd(2014, 1, 1) - Duration::days(-1), from_ymd(2014, 1, 2)); -/// assert_eq!(from_ymd(2014, 1, 1) - Duration::days(364), from_ymd(2013, 1, 2)); -/// assert_eq!(from_ymd(2014, 1, 1) - Duration::days(365*4 + 1), from_ymd(2010, 1, 1)); -/// assert_eq!(from_ymd(2014, 1, 1) - Duration::days(365*400 + 97), from_ymd(1614, 1, 1)); -/// # } -/// ~~~~ -impl Sub for NaiveDate { - type Output = NaiveDate; - - #[inline] - fn sub(self, rhs: OldDuration) -> NaiveDate { - self.checked_sub_signed(rhs).expect("`NaiveDate - Duration` overflowed") - } -} - -impl SubAssign for NaiveDate { - #[inline] - fn sub_assign(&mut self, rhs: OldDuration) { - *self = self.sub(rhs); - } -} - -/// Subtracts another `NaiveDate` from the current date. -/// Returns a `Duration` of integral numbers. -/// -/// This does not overflow or underflow at all, -/// as all possible output fits in the range of `Duration`. -/// -/// The implementation is a wrapper around -/// [`NaiveDate::signed_duration_since`](#method.signed_duration_since). -/// -/// # Example -/// -/// ~~~~ -/// # extern crate chrono; fn main() { -/// use chrono::{Duration, NaiveDate}; -/// -/// let from_ymd = NaiveDate::from_ymd; -/// -/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2014, 1, 1), Duration::zero()); -/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2013, 12, 31), Duration::days(1)); -/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2014, 1, 2), Duration::days(-1)); -/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2013, 9, 23), Duration::days(100)); -/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2013, 1, 1), Duration::days(365)); -/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2010, 1, 1), Duration::days(365*4 + 1)); -/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(1614, 1, 1), Duration::days(365*400 + 97)); -/// # } -/// ~~~~ -impl Sub for NaiveDate { - type Output = OldDuration; - - #[inline] - fn sub(self, rhs: NaiveDate) -> OldDuration { - self.signed_duration_since(rhs) - } -} - -/// Iterator over `NaiveDate` with a step size of one day. -#[derive(Debug, Copy, Clone, Hash, PartialEq, PartialOrd, Eq, Ord)] -pub struct NaiveDateDaysIterator { - value: NaiveDate, -} - -impl Iterator for NaiveDateDaysIterator { - type Item = NaiveDate; - - fn next(&mut self) -> Option { - if self.value == MAX_DATE { - return None; - } - // current < MAX_DATE from here on: - let current = self.value; - // This can't panic because current is < MAX_DATE: - self.value = current.succ(); - Some(current) - } - - fn size_hint(&self) -> (usize, Option) { - let exact_size = MAX_DATE.signed_duration_since(self.value).num_days(); - (exact_size as usize, Some(exact_size as usize)) - } -} - -impl ExactSizeIterator for NaiveDateDaysIterator {} - -#[derive(Debug, Copy, Clone, Hash, PartialEq, PartialOrd, Eq, Ord)] -pub struct NaiveDateWeeksIterator { - value: NaiveDate, -} - -impl Iterator for NaiveDateWeeksIterator { - type Item = NaiveDate; - - fn next(&mut self) -> Option { - if MAX_DATE - self.value < OldDuration::weeks(1) { - return None; - } - let current = self.value; - self.value = current + OldDuration::weeks(1); - Some(current) - } - - fn size_hint(&self) -> (usize, Option) { - let exact_size = MAX_DATE.signed_duration_since(self.value).num_weeks(); - (exact_size as usize, Some(exact_size as usize)) - } -} - -impl ExactSizeIterator for NaiveDateWeeksIterator {} - -// TODO: NaiveDateDaysIterator and NaiveDateWeeksIterator should implement FusedIterator, -// TrustedLen, and Step once they becomes stable. -// See: https://github.com/chronotope/chrono/issues/208 - -/// The `Debug` output of the naive date `d` is the same as -/// [`d.format("%Y-%m-%d")`](../format/strftime/index.html). -/// -/// The string printed can be readily parsed via the `parse` method on `str`. -/// -/// # Example -/// -/// ~~~~ -/// use chrono::NaiveDate; -/// -/// assert_eq!(format!("{:?}", NaiveDate::from_ymd(2015, 9, 5)), "2015-09-05"); -/// assert_eq!(format!("{:?}", NaiveDate::from_ymd( 0, 1, 1)), "0000-01-01"); -/// assert_eq!(format!("{:?}", NaiveDate::from_ymd(9999, 12, 31)), "9999-12-31"); -/// ~~~~ -/// -/// ISO 8601 requires an explicit sign for years before 1 BCE or after 9999 CE. -/// -/// ~~~~ -/// # use chrono::NaiveDate; -/// assert_eq!(format!("{:?}", NaiveDate::from_ymd( -1, 1, 1)), "-0001-01-01"); -/// assert_eq!(format!("{:?}", NaiveDate::from_ymd(10000, 12, 31)), "+10000-12-31"); -/// ~~~~ -impl fmt::Debug for NaiveDate { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let year = self.year(); - let mdf = self.mdf(); - if 0 <= year && year <= 9999 { - write!(f, "{:04}-{:02}-{:02}", year, mdf.month(), mdf.day()) - } else { - // ISO 8601 requires the explicit sign for out-of-range years - write!(f, "{:+05}-{:02}-{:02}", year, mdf.month(), mdf.day()) - } - } -} - -/// The `Display` output of the naive date `d` is the same as -/// [`d.format("%Y-%m-%d")`](../format/strftime/index.html). -/// -/// The string printed can be readily parsed via the `parse` method on `str`. -/// -/// # Example -/// -/// ~~~~ -/// use chrono::NaiveDate; -/// -/// assert_eq!(format!("{}", NaiveDate::from_ymd(2015, 9, 5)), "2015-09-05"); -/// assert_eq!(format!("{}", NaiveDate::from_ymd( 0, 1, 1)), "0000-01-01"); -/// assert_eq!(format!("{}", NaiveDate::from_ymd(9999, 12, 31)), "9999-12-31"); -/// ~~~~ -/// -/// ISO 8601 requires an explicit sign for years before 1 BCE or after 9999 CE. -/// -/// ~~~~ -/// # use chrono::NaiveDate; -/// assert_eq!(format!("{}", NaiveDate::from_ymd( -1, 1, 1)), "-0001-01-01"); -/// assert_eq!(format!("{}", NaiveDate::from_ymd(10000, 12, 31)), "+10000-12-31"); -/// ~~~~ -impl fmt::Display for NaiveDate { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Debug::fmt(self, f) - } -} - -/// Parsing a `str` into a `NaiveDate` uses the same format, -/// [`%Y-%m-%d`](../format/strftime/index.html), as in `Debug` and `Display`. -/// -/// # Example -/// -/// ~~~~ -/// use chrono::NaiveDate; -/// -/// let d = NaiveDate::from_ymd(2015, 9, 18); -/// assert_eq!("2015-09-18".parse::(), Ok(d)); -/// -/// let d = NaiveDate::from_ymd(12345, 6, 7); -/// assert_eq!("+12345-6-7".parse::(), Ok(d)); -/// -/// assert!("foo".parse::().is_err()); -/// ~~~~ -impl str::FromStr for NaiveDate { - type Err = ParseError; - - fn from_str(s: &str) -> ParseResult { - const ITEMS: &'static [Item<'static>] = &[ - Item::Numeric(Numeric::Year, Pad::Zero), - Item::Space(""), - Item::Literal("-"), - Item::Numeric(Numeric::Month, Pad::Zero), - Item::Space(""), - Item::Literal("-"), - Item::Numeric(Numeric::Day, Pad::Zero), - Item::Space(""), - ]; - - let mut parsed = Parsed::new(); - parse(&mut parsed, s, ITEMS.iter())?; - parsed.to_naive_date() - } -} - -#[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))] -fn test_encodable_json(to_string: F) -where - F: Fn(&NaiveDate) -> Result, - E: ::std::fmt::Debug, -{ - assert_eq!(to_string(&NaiveDate::from_ymd(2014, 7, 24)).ok(), Some(r#""2014-07-24""#.into())); - assert_eq!(to_string(&NaiveDate::from_ymd(0, 1, 1)).ok(), Some(r#""0000-01-01""#.into())); - assert_eq!(to_string(&NaiveDate::from_ymd(-1, 12, 31)).ok(), Some(r#""-0001-12-31""#.into())); - assert_eq!(to_string(&MIN_DATE).ok(), Some(r#""-262144-01-01""#.into())); - assert_eq!(to_string(&MAX_DATE).ok(), Some(r#""+262143-12-31""#.into())); -} - -#[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))] -fn test_decodable_json(from_str: F) -where - F: Fn(&str) -> Result, - E: ::std::fmt::Debug, -{ - use std::{i32, i64}; - - assert_eq!(from_str(r#""2016-07-08""#).ok(), Some(NaiveDate::from_ymd(2016, 7, 8))); - assert_eq!(from_str(r#""2016-7-8""#).ok(), Some(NaiveDate::from_ymd(2016, 7, 8))); - assert_eq!(from_str(r#""+002016-07-08""#).ok(), Some(NaiveDate::from_ymd(2016, 7, 8))); - assert_eq!(from_str(r#""0000-01-01""#).ok(), Some(NaiveDate::from_ymd(0, 1, 1))); - assert_eq!(from_str(r#""0-1-1""#).ok(), Some(NaiveDate::from_ymd(0, 1, 1))); - assert_eq!(from_str(r#""-0001-12-31""#).ok(), Some(NaiveDate::from_ymd(-1, 12, 31))); - assert_eq!(from_str(r#""-262144-01-01""#).ok(), Some(MIN_DATE)); - assert_eq!(from_str(r#""+262143-12-31""#).ok(), Some(MAX_DATE)); - - // bad formats - assert!(from_str(r#""""#).is_err()); - assert!(from_str(r#""20001231""#).is_err()); - assert!(from_str(r#""2000-00-00""#).is_err()); - assert!(from_str(r#""2000-02-30""#).is_err()); - assert!(from_str(r#""2001-02-29""#).is_err()); - assert!(from_str(r#""2002-002-28""#).is_err()); - assert!(from_str(r#""yyyy-mm-dd""#).is_err()); - assert!(from_str(r#"0"#).is_err()); - assert!(from_str(r#"20.01"#).is_err()); - assert!(from_str(&i32::MIN.to_string()).is_err()); - assert!(from_str(&i32::MAX.to_string()).is_err()); - assert!(from_str(&i64::MIN.to_string()).is_err()); - assert!(from_str(&i64::MAX.to_string()).is_err()); - assert!(from_str(r#"{}"#).is_err()); - // pre-0.3.0 rustc-serialize format is now invalid - assert!(from_str(r#"{"ymdf":20}"#).is_err()); - assert!(from_str(r#"null"#).is_err()); -} - -#[cfg(feature = "rustc-serialize")] -mod rustc_serialize { - use super::NaiveDate; - use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; - - impl Encodable for NaiveDate { - fn encode(&self, s: &mut S) -> Result<(), S::Error> { - format!("{:?}", self).encode(s) - } - } - - impl Decodable for NaiveDate { - fn decode(d: &mut D) -> Result { - d.read_str()?.parse().map_err(|_| d.error("invalid date")) - } - } - - #[cfg(test)] - use rustc_serialize::json; - - #[test] - fn test_encodable() { - super::test_encodable_json(json::encode); - } - - #[test] - fn test_decodable() { - super::test_decodable_json(json::decode); - } -} - -#[cfg(feature = "serde")] -mod serde { - use super::NaiveDate; - use core::fmt; - use serdelib::{de, ser}; - - // TODO not very optimized for space (binary formats would want something better) - - impl ser::Serialize for NaiveDate { - fn serialize(&self, serializer: S) -> Result - where - S: ser::Serializer, - { - struct FormatWrapped<'a, D: 'a> { - inner: &'a D, - } - - impl<'a, D: fmt::Debug> fmt::Display for FormatWrapped<'a, D> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.inner.fmt(f) - } - } - - serializer.collect_str(&FormatWrapped { inner: &self }) - } - } - - struct NaiveDateVisitor; - - impl<'de> de::Visitor<'de> for NaiveDateVisitor { - type Value = NaiveDate; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - write!(formatter, "a formatted date string") - } - - #[cfg(any(feature = "std", test))] - fn visit_str(self, value: &str) -> Result - where - E: de::Error, - { - value.parse().map_err(E::custom) - } - - #[cfg(not(any(feature = "std", test)))] - fn visit_str(self, value: &str) -> Result - where - E: de::Error, - { - value.parse().map_err(E::custom) - } - } - - impl<'de> de::Deserialize<'de> for NaiveDate { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - deserializer.deserialize_str(NaiveDateVisitor) - } - } - - #[cfg(test)] - extern crate bincode; - #[cfg(test)] - extern crate serde_json; - - #[test] - fn test_serde_serialize() { - super::test_encodable_json(self::serde_json::to_string); - } - - #[test] - fn test_serde_deserialize() { - super::test_decodable_json(|input| self::serde_json::from_str(&input)); - } - - #[test] - fn test_serde_bincode() { - // Bincode is relevant to test separately from JSON because - // it is not self-describing. - use self::bincode::{deserialize, serialize, Infinite}; - - let d = NaiveDate::from_ymd(2014, 7, 24); - let encoded = serialize(&d, Infinite).unwrap(); - let decoded: NaiveDate = deserialize(&encoded).unwrap(); - assert_eq!(d, decoded); - } -} - -#[cfg(test)] -mod tests { - use super::NaiveDate; - use super::{MAX_DATE, MAX_DAYS_FROM_YEAR_0, MAX_YEAR}; - use super::{MIN_DATE, MIN_DAYS_FROM_YEAR_0, MIN_YEAR}; - use oldtime::Duration; - use std::{i32, u32}; - use {Datelike, Weekday}; - - #[test] - fn test_date_from_ymd() { - let ymd_opt = |y, m, d| NaiveDate::from_ymd_opt(y, m, d); - - assert!(ymd_opt(2012, 0, 1).is_none()); - assert!(ymd_opt(2012, 1, 1).is_some()); - assert!(ymd_opt(2012, 2, 29).is_some()); - assert!(ymd_opt(2014, 2, 29).is_none()); - assert!(ymd_opt(2014, 3, 0).is_none()); - assert!(ymd_opt(2014, 3, 1).is_some()); - assert!(ymd_opt(2014, 3, 31).is_some()); - assert!(ymd_opt(2014, 3, 32).is_none()); - assert!(ymd_opt(2014, 12, 31).is_some()); - assert!(ymd_opt(2014, 13, 1).is_none()); - } - - #[test] - fn test_date_from_yo() { - let yo_opt = |y, o| NaiveDate::from_yo_opt(y, o); - let ymd = |y, m, d| NaiveDate::from_ymd(y, m, d); - - assert_eq!(yo_opt(2012, 0), None); - assert_eq!(yo_opt(2012, 1), Some(ymd(2012, 1, 1))); - assert_eq!(yo_opt(2012, 2), Some(ymd(2012, 1, 2))); - assert_eq!(yo_opt(2012, 32), Some(ymd(2012, 2, 1))); - assert_eq!(yo_opt(2012, 60), Some(ymd(2012, 2, 29))); - assert_eq!(yo_opt(2012, 61), Some(ymd(2012, 3, 1))); - assert_eq!(yo_opt(2012, 100), Some(ymd(2012, 4, 9))); - assert_eq!(yo_opt(2012, 200), Some(ymd(2012, 7, 18))); - assert_eq!(yo_opt(2012, 300), Some(ymd(2012, 10, 26))); - assert_eq!(yo_opt(2012, 366), Some(ymd(2012, 12, 31))); - assert_eq!(yo_opt(2012, 367), None); - - assert_eq!(yo_opt(2014, 0), None); - assert_eq!(yo_opt(2014, 1), Some(ymd(2014, 1, 1))); - assert_eq!(yo_opt(2014, 2), Some(ymd(2014, 1, 2))); - assert_eq!(yo_opt(2014, 32), Some(ymd(2014, 2, 1))); - assert_eq!(yo_opt(2014, 59), Some(ymd(2014, 2, 28))); - assert_eq!(yo_opt(2014, 60), Some(ymd(2014, 3, 1))); - assert_eq!(yo_opt(2014, 100), Some(ymd(2014, 4, 10))); - assert_eq!(yo_opt(2014, 200), Some(ymd(2014, 7, 19))); - assert_eq!(yo_opt(2014, 300), Some(ymd(2014, 10, 27))); - assert_eq!(yo_opt(2014, 365), Some(ymd(2014, 12, 31))); - assert_eq!(yo_opt(2014, 366), None); - } - - #[test] - fn test_date_from_isoywd() { - let isoywd_opt = |y, w, d| NaiveDate::from_isoywd_opt(y, w, d); - let ymd = |y, m, d| NaiveDate::from_ymd(y, m, d); - - assert_eq!(isoywd_opt(2004, 0, Weekday::Sun), None); - assert_eq!(isoywd_opt(2004, 1, Weekday::Mon), Some(ymd(2003, 12, 29))); - assert_eq!(isoywd_opt(2004, 1, Weekday::Sun), Some(ymd(2004, 1, 4))); - assert_eq!(isoywd_opt(2004, 2, Weekday::Mon), Some(ymd(2004, 1, 5))); - assert_eq!(isoywd_opt(2004, 2, Weekday::Sun), Some(ymd(2004, 1, 11))); - assert_eq!(isoywd_opt(2004, 52, Weekday::Mon), Some(ymd(2004, 12, 20))); - assert_eq!(isoywd_opt(2004, 52, Weekday::Sun), Some(ymd(2004, 12, 26))); - assert_eq!(isoywd_opt(2004, 53, Weekday::Mon), Some(ymd(2004, 12, 27))); - assert_eq!(isoywd_opt(2004, 53, Weekday::Sun), Some(ymd(2005, 1, 2))); - assert_eq!(isoywd_opt(2004, 54, Weekday::Mon), None); - - assert_eq!(isoywd_opt(2011, 0, Weekday::Sun), None); - assert_eq!(isoywd_opt(2011, 1, Weekday::Mon), Some(ymd(2011, 1, 3))); - assert_eq!(isoywd_opt(2011, 1, Weekday::Sun), Some(ymd(2011, 1, 9))); - assert_eq!(isoywd_opt(2011, 2, Weekday::Mon), Some(ymd(2011, 1, 10))); - assert_eq!(isoywd_opt(2011, 2, Weekday::Sun), Some(ymd(2011, 1, 16))); - - assert_eq!(isoywd_opt(2018, 51, Weekday::Mon), Some(ymd(2018, 12, 17))); - assert_eq!(isoywd_opt(2018, 51, Weekday::Sun), Some(ymd(2018, 12, 23))); - assert_eq!(isoywd_opt(2018, 52, Weekday::Mon), Some(ymd(2018, 12, 24))); - assert_eq!(isoywd_opt(2018, 52, Weekday::Sun), Some(ymd(2018, 12, 30))); - assert_eq!(isoywd_opt(2018, 53, Weekday::Mon), None); - } - - #[test] - fn test_date_from_isoywd_and_iso_week() { - for year in 2000..2401 { - for week in 1..54 { - for &weekday in [ - Weekday::Mon, - Weekday::Tue, - Weekday::Wed, - Weekday::Thu, - Weekday::Fri, - Weekday::Sat, - Weekday::Sun, - ] - .iter() - { - let d = NaiveDate::from_isoywd_opt(year, week, weekday); - if d.is_some() { - let d = d.unwrap(); - assert_eq!(d.weekday(), weekday); - let w = d.iso_week(); - assert_eq!(w.year(), year); - assert_eq!(w.week(), week); - } - } - } - } - - for year in 2000..2401 { - for month in 1..13 { - for day in 1..32 { - let d = NaiveDate::from_ymd_opt(year, month, day); - if d.is_some() { - let d = d.unwrap(); - let w = d.iso_week(); - let d_ = NaiveDate::from_isoywd(w.year(), w.week(), d.weekday()); - assert_eq!(d, d_); - } - } - } - } - } - - #[test] - fn test_date_from_num_days_from_ce() { - let from_ndays_from_ce = |days| NaiveDate::from_num_days_from_ce_opt(days); - assert_eq!(from_ndays_from_ce(1), Some(NaiveDate::from_ymd(1, 1, 1))); - assert_eq!(from_ndays_from_ce(2), Some(NaiveDate::from_ymd(1, 1, 2))); - assert_eq!(from_ndays_from_ce(31), Some(NaiveDate::from_ymd(1, 1, 31))); - assert_eq!(from_ndays_from_ce(32), Some(NaiveDate::from_ymd(1, 2, 1))); - assert_eq!(from_ndays_from_ce(59), Some(NaiveDate::from_ymd(1, 2, 28))); - assert_eq!(from_ndays_from_ce(60), Some(NaiveDate::from_ymd(1, 3, 1))); - assert_eq!(from_ndays_from_ce(365), Some(NaiveDate::from_ymd(1, 12, 31))); - assert_eq!(from_ndays_from_ce(365 * 1 + 1), Some(NaiveDate::from_ymd(2, 1, 1))); - assert_eq!(from_ndays_from_ce(365 * 2 + 1), Some(NaiveDate::from_ymd(3, 1, 1))); - assert_eq!(from_ndays_from_ce(365 * 3 + 1), Some(NaiveDate::from_ymd(4, 1, 1))); - assert_eq!(from_ndays_from_ce(365 * 4 + 2), Some(NaiveDate::from_ymd(5, 1, 1))); - assert_eq!(from_ndays_from_ce(146097 + 1), Some(NaiveDate::from_ymd(401, 1, 1))); - assert_eq!(from_ndays_from_ce(146097 * 5 + 1), Some(NaiveDate::from_ymd(2001, 1, 1))); - assert_eq!(from_ndays_from_ce(719163), Some(NaiveDate::from_ymd(1970, 1, 1))); - assert_eq!(from_ndays_from_ce(0), Some(NaiveDate::from_ymd(0, 12, 31))); // 1 BCE - assert_eq!(from_ndays_from_ce(-365), Some(NaiveDate::from_ymd(0, 1, 1))); - assert_eq!(from_ndays_from_ce(-366), Some(NaiveDate::from_ymd(-1, 12, 31))); // 2 BCE - - for days in (-9999..10001).map(|x| x * 100) { - assert_eq!(from_ndays_from_ce(days).map(|d| d.num_days_from_ce()), Some(days)); - } - - assert_eq!(from_ndays_from_ce(MIN_DATE.num_days_from_ce()), Some(MIN_DATE)); - assert_eq!(from_ndays_from_ce(MIN_DATE.num_days_from_ce() - 1), None); - assert_eq!(from_ndays_from_ce(MAX_DATE.num_days_from_ce()), Some(MAX_DATE)); - assert_eq!(from_ndays_from_ce(MAX_DATE.num_days_from_ce() + 1), None); - } - - #[test] - fn test_date_from_weekday_of_month_opt() { - let ymwd = |y, m, w, n| NaiveDate::from_weekday_of_month_opt(y, m, w, n); - assert_eq!(ymwd(2018, 8, Weekday::Tue, 0), None); - assert_eq!(ymwd(2018, 8, Weekday::Wed, 1), Some(NaiveDate::from_ymd(2018, 8, 1))); - assert_eq!(ymwd(2018, 8, Weekday::Thu, 1), Some(NaiveDate::from_ymd(2018, 8, 2))); - assert_eq!(ymwd(2018, 8, Weekday::Sun, 1), Some(NaiveDate::from_ymd(2018, 8, 5))); - assert_eq!(ymwd(2018, 8, Weekday::Mon, 1), Some(NaiveDate::from_ymd(2018, 8, 6))); - assert_eq!(ymwd(2018, 8, Weekday::Tue, 1), Some(NaiveDate::from_ymd(2018, 8, 7))); - assert_eq!(ymwd(2018, 8, Weekday::Wed, 2), Some(NaiveDate::from_ymd(2018, 8, 8))); - assert_eq!(ymwd(2018, 8, Weekday::Sun, 2), Some(NaiveDate::from_ymd(2018, 8, 12))); - assert_eq!(ymwd(2018, 8, Weekday::Thu, 3), Some(NaiveDate::from_ymd(2018, 8, 16))); - assert_eq!(ymwd(2018, 8, Weekday::Thu, 4), Some(NaiveDate::from_ymd(2018, 8, 23))); - assert_eq!(ymwd(2018, 8, Weekday::Thu, 5), Some(NaiveDate::from_ymd(2018, 8, 30))); - assert_eq!(ymwd(2018, 8, Weekday::Fri, 5), Some(NaiveDate::from_ymd(2018, 8, 31))); - assert_eq!(ymwd(2018, 8, Weekday::Sat, 5), None); - } - - #[test] - fn test_date_fields() { - fn check(year: i32, month: u32, day: u32, ordinal: u32) { - let d1 = NaiveDate::from_ymd(year, month, day); - assert_eq!(d1.year(), year); - assert_eq!(d1.month(), month); - assert_eq!(d1.day(), day); - assert_eq!(d1.ordinal(), ordinal); - - let d2 = NaiveDate::from_yo(year, ordinal); - assert_eq!(d2.year(), year); - assert_eq!(d2.month(), month); - assert_eq!(d2.day(), day); - assert_eq!(d2.ordinal(), ordinal); - - assert_eq!(d1, d2); - } - - check(2012, 1, 1, 1); - check(2012, 1, 2, 2); - check(2012, 2, 1, 32); - check(2012, 2, 29, 60); - check(2012, 3, 1, 61); - check(2012, 4, 9, 100); - check(2012, 7, 18, 200); - check(2012, 10, 26, 300); - check(2012, 12, 31, 366); - - check(2014, 1, 1, 1); - check(2014, 1, 2, 2); - check(2014, 2, 1, 32); - check(2014, 2, 28, 59); - check(2014, 3, 1, 60); - check(2014, 4, 10, 100); - check(2014, 7, 19, 200); - check(2014, 10, 27, 300); - check(2014, 12, 31, 365); - } - - #[test] - fn test_date_weekday() { - assert_eq!(NaiveDate::from_ymd(1582, 10, 15).weekday(), Weekday::Fri); - // May 20, 1875 = ISO 8601 reference date - assert_eq!(NaiveDate::from_ymd(1875, 5, 20).weekday(), Weekday::Thu); - assert_eq!(NaiveDate::from_ymd(2000, 1, 1).weekday(), Weekday::Sat); - } - - #[test] - fn test_date_with_fields() { - let d = NaiveDate::from_ymd(2000, 2, 29); - assert_eq!(d.with_year(-400), Some(NaiveDate::from_ymd(-400, 2, 29))); - assert_eq!(d.with_year(-100), None); - assert_eq!(d.with_year(1600), Some(NaiveDate::from_ymd(1600, 2, 29))); - assert_eq!(d.with_year(1900), None); - assert_eq!(d.with_year(2000), Some(NaiveDate::from_ymd(2000, 2, 29))); - assert_eq!(d.with_year(2001), None); - assert_eq!(d.with_year(2004), Some(NaiveDate::from_ymd(2004, 2, 29))); - assert_eq!(d.with_year(i32::MAX), None); - - let d = NaiveDate::from_ymd(2000, 4, 30); - assert_eq!(d.with_month(0), None); - assert_eq!(d.with_month(1), Some(NaiveDate::from_ymd(2000, 1, 30))); - assert_eq!(d.with_month(2), None); - assert_eq!(d.with_month(3), Some(NaiveDate::from_ymd(2000, 3, 30))); - assert_eq!(d.with_month(4), Some(NaiveDate::from_ymd(2000, 4, 30))); - assert_eq!(d.with_month(12), Some(NaiveDate::from_ymd(2000, 12, 30))); - assert_eq!(d.with_month(13), None); - assert_eq!(d.with_month(u32::MAX), None); - - let d = NaiveDate::from_ymd(2000, 2, 8); - assert_eq!(d.with_day(0), None); - assert_eq!(d.with_day(1), Some(NaiveDate::from_ymd(2000, 2, 1))); - assert_eq!(d.with_day(29), Some(NaiveDate::from_ymd(2000, 2, 29))); - assert_eq!(d.with_day(30), None); - assert_eq!(d.with_day(u32::MAX), None); - - let d = NaiveDate::from_ymd(2000, 5, 5); - assert_eq!(d.with_ordinal(0), None); - assert_eq!(d.with_ordinal(1), Some(NaiveDate::from_ymd(2000, 1, 1))); - assert_eq!(d.with_ordinal(60), Some(NaiveDate::from_ymd(2000, 2, 29))); - assert_eq!(d.with_ordinal(61), Some(NaiveDate::from_ymd(2000, 3, 1))); - assert_eq!(d.with_ordinal(366), Some(NaiveDate::from_ymd(2000, 12, 31))); - assert_eq!(d.with_ordinal(367), None); - assert_eq!(d.with_ordinal(u32::MAX), None); - } - - #[test] - fn test_date_num_days_from_ce() { - assert_eq!(NaiveDate::from_ymd(1, 1, 1).num_days_from_ce(), 1); - - for year in -9999..10001 { - assert_eq!( - NaiveDate::from_ymd(year, 1, 1).num_days_from_ce(), - NaiveDate::from_ymd(year - 1, 12, 31).num_days_from_ce() + 1 - ); - } - } - - #[test] - fn test_date_succ() { - let ymd = |y, m, d| NaiveDate::from_ymd(y, m, d); - assert_eq!(ymd(2014, 5, 6).succ_opt(), Some(ymd(2014, 5, 7))); - assert_eq!(ymd(2014, 5, 31).succ_opt(), Some(ymd(2014, 6, 1))); - assert_eq!(ymd(2014, 12, 31).succ_opt(), Some(ymd(2015, 1, 1))); - assert_eq!(ymd(2016, 2, 28).succ_opt(), Some(ymd(2016, 2, 29))); - assert_eq!(ymd(MAX_DATE.year(), 12, 31).succ_opt(), None); - } - - #[test] - fn test_date_pred() { - let ymd = |y, m, d| NaiveDate::from_ymd(y, m, d); - assert_eq!(ymd(2016, 3, 1).pred_opt(), Some(ymd(2016, 2, 29))); - assert_eq!(ymd(2015, 1, 1).pred_opt(), Some(ymd(2014, 12, 31))); - assert_eq!(ymd(2014, 6, 1).pred_opt(), Some(ymd(2014, 5, 31))); - assert_eq!(ymd(2014, 5, 7).pred_opt(), Some(ymd(2014, 5, 6))); - assert_eq!(ymd(MIN_DATE.year(), 1, 1).pred_opt(), None); - } - - #[test] - fn test_date_add() { - fn check((y1, m1, d1): (i32, u32, u32), rhs: Duration, ymd: Option<(i32, u32, u32)>) { - let lhs = NaiveDate::from_ymd(y1, m1, d1); - let sum = ymd.map(|(y, m, d)| NaiveDate::from_ymd(y, m, d)); - assert_eq!(lhs.checked_add_signed(rhs), sum); - assert_eq!(lhs.checked_sub_signed(-rhs), sum); - } - - check((2014, 1, 1), Duration::zero(), Some((2014, 1, 1))); - check((2014, 1, 1), Duration::seconds(86399), Some((2014, 1, 1))); - // always round towards zero - check((2014, 1, 1), Duration::seconds(-86399), Some((2014, 1, 1))); - check((2014, 1, 1), Duration::days(1), Some((2014, 1, 2))); - check((2014, 1, 1), Duration::days(-1), Some((2013, 12, 31))); - check((2014, 1, 1), Duration::days(364), Some((2014, 12, 31))); - check((2014, 1, 1), Duration::days(365 * 4 + 1), Some((2018, 1, 1))); - check((2014, 1, 1), Duration::days(365 * 400 + 97), Some((2414, 1, 1))); - - check((-7, 1, 1), Duration::days(365 * 12 + 3), Some((5, 1, 1))); - - // overflow check - check((0, 1, 1), Duration::days(MAX_DAYS_FROM_YEAR_0 as i64), Some((MAX_YEAR, 12, 31))); - check((0, 1, 1), Duration::days(MAX_DAYS_FROM_YEAR_0 as i64 + 1), None); - check((0, 1, 1), Duration::max_value(), None); - check((0, 1, 1), Duration::days(MIN_DAYS_FROM_YEAR_0 as i64), Some((MIN_YEAR, 1, 1))); - check((0, 1, 1), Duration::days(MIN_DAYS_FROM_YEAR_0 as i64 - 1), None); - check((0, 1, 1), Duration::min_value(), None); - } - - #[test] - fn test_date_sub() { - fn check((y1, m1, d1): (i32, u32, u32), (y2, m2, d2): (i32, u32, u32), diff: Duration) { - let lhs = NaiveDate::from_ymd(y1, m1, d1); - let rhs = NaiveDate::from_ymd(y2, m2, d2); - assert_eq!(lhs.signed_duration_since(rhs), diff); - assert_eq!(rhs.signed_duration_since(lhs), -diff); - } - - check((2014, 1, 1), (2014, 1, 1), Duration::zero()); - check((2014, 1, 2), (2014, 1, 1), Duration::days(1)); - check((2014, 12, 31), (2014, 1, 1), Duration::days(364)); - check((2015, 1, 3), (2014, 1, 1), Duration::days(365 + 2)); - check((2018, 1, 1), (2014, 1, 1), Duration::days(365 * 4 + 1)); - check((2414, 1, 1), (2014, 1, 1), Duration::days(365 * 400 + 97)); - - check((MAX_YEAR, 12, 31), (0, 1, 1), Duration::days(MAX_DAYS_FROM_YEAR_0 as i64)); - check((MIN_YEAR, 1, 1), (0, 1, 1), Duration::days(MIN_DAYS_FROM_YEAR_0 as i64)); - } - - #[test] - fn test_date_addassignment() { - let ymd = NaiveDate::from_ymd; - let mut date = ymd(2016, 10, 1); - date += Duration::days(10); - assert_eq!(date, ymd(2016, 10, 11)); - date += Duration::days(30); - assert_eq!(date, ymd(2016, 11, 10)); - } - - #[test] - fn test_date_subassignment() { - let ymd = NaiveDate::from_ymd; - let mut date = ymd(2016, 10, 11); - date -= Duration::days(10); - assert_eq!(date, ymd(2016, 10, 1)); - date -= Duration::days(2); - assert_eq!(date, ymd(2016, 9, 29)); - } - - #[test] - fn test_date_fmt() { - assert_eq!(format!("{:?}", NaiveDate::from_ymd(2012, 3, 4)), "2012-03-04"); - assert_eq!(format!("{:?}", NaiveDate::from_ymd(0, 3, 4)), "0000-03-04"); - assert_eq!(format!("{:?}", NaiveDate::from_ymd(-307, 3, 4)), "-0307-03-04"); - assert_eq!(format!("{:?}", NaiveDate::from_ymd(12345, 3, 4)), "+12345-03-04"); - - assert_eq!(NaiveDate::from_ymd(2012, 3, 4).to_string(), "2012-03-04"); - assert_eq!(NaiveDate::from_ymd(0, 3, 4).to_string(), "0000-03-04"); - assert_eq!(NaiveDate::from_ymd(-307, 3, 4).to_string(), "-0307-03-04"); - assert_eq!(NaiveDate::from_ymd(12345, 3, 4).to_string(), "+12345-03-04"); - - // the format specifier should have no effect on `NaiveTime` - assert_eq!(format!("{:+30?}", NaiveDate::from_ymd(1234, 5, 6)), "1234-05-06"); - assert_eq!(format!("{:30?}", NaiveDate::from_ymd(12345, 6, 7)), "+12345-06-07"); - } - - #[test] - fn test_date_from_str() { - // valid cases - let valid = [ - "-0000000123456-1-2", - " -123456 - 1 - 2 ", - "-12345-1-2", - "-1234-12-31", - "-7-6-5", - "350-2-28", - "360-02-29", - "0360-02-29", - "2015-2 -18", - "+70-2-18", - "+70000-2-18", - "+00007-2-18", - ]; - for &s in &valid { - let d = match s.parse::() { - Ok(d) => d, - Err(e) => panic!("parsing `{}` has failed: {}", s, e), - }; - let s_ = format!("{:?}", d); - // `s` and `s_` may differ, but `s.parse()` and `s_.parse()` must be same - let d_ = match s_.parse::() { - Ok(d) => d, - Err(e) => { - panic!("`{}` is parsed into `{:?}`, but reparsing that has failed: {}", s, d, e) - } - }; - assert!( - d == d_, - "`{}` is parsed into `{:?}`, but reparsed result \ - `{:?}` does not match", - s, - d, - d_ - ); - } - - // some invalid cases - // since `ParseErrorKind` is private, all we can do is to check if there was an error - assert!("".parse::().is_err()); - assert!("x".parse::().is_err()); - assert!("2014".parse::().is_err()); - assert!("2014-01".parse::().is_err()); - assert!("2014-01-00".parse::().is_err()); - assert!("2014-13-57".parse::().is_err()); - assert!("9999999-9-9".parse::().is_err()); // out-of-bounds - } - - #[test] - fn test_date_parse_from_str() { - let ymd = |y, m, d| NaiveDate::from_ymd(y, m, d); - assert_eq!( - NaiveDate::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), - Ok(ymd(2014, 5, 7)) - ); // ignore time and offset - assert_eq!( - NaiveDate::parse_from_str("2015-W06-1=2015-033", "%G-W%V-%u = %Y-%j"), - Ok(ymd(2015, 2, 2)) - ); - assert_eq!( - NaiveDate::parse_from_str("Fri, 09 Aug 13", "%a, %d %b %y"), - Ok(ymd(2013, 8, 9)) - ); - assert!(NaiveDate::parse_from_str("Sat, 09 Aug 2013", "%a, %d %b %Y").is_err()); - assert!(NaiveDate::parse_from_str("2014-57", "%Y-%m-%d").is_err()); - assert!(NaiveDate::parse_from_str("2014", "%Y").is_err()); // insufficient - } - - #[test] - fn test_date_format() { - let d = NaiveDate::from_ymd(2012, 3, 4); - assert_eq!(d.format("%Y,%C,%y,%G,%g").to_string(), "2012,20,12,2012,12"); - assert_eq!(d.format("%m,%b,%h,%B").to_string(), "03,Mar,Mar,March"); - assert_eq!(d.format("%d,%e").to_string(), "04, 4"); - assert_eq!(d.format("%U,%W,%V").to_string(), "10,09,09"); - assert_eq!(d.format("%a,%A,%w,%u").to_string(), "Sun,Sunday,0,7"); - assert_eq!(d.format("%j").to_string(), "064"); // since 2012 is a leap year - assert_eq!(d.format("%D,%x").to_string(), "03/04/12,03/04/12"); - assert_eq!(d.format("%F").to_string(), "2012-03-04"); - assert_eq!(d.format("%v").to_string(), " 4-Mar-2012"); - assert_eq!(d.format("%t%n%%%n%t").to_string(), "\t\n%\n\t"); - - // non-four-digit years - assert_eq!(NaiveDate::from_ymd(12345, 1, 1).format("%Y").to_string(), "+12345"); - assert_eq!(NaiveDate::from_ymd(1234, 1, 1).format("%Y").to_string(), "1234"); - assert_eq!(NaiveDate::from_ymd(123, 1, 1).format("%Y").to_string(), "0123"); - assert_eq!(NaiveDate::from_ymd(12, 1, 1).format("%Y").to_string(), "0012"); - assert_eq!(NaiveDate::from_ymd(1, 1, 1).format("%Y").to_string(), "0001"); - assert_eq!(NaiveDate::from_ymd(0, 1, 1).format("%Y").to_string(), "0000"); - assert_eq!(NaiveDate::from_ymd(-1, 1, 1).format("%Y").to_string(), "-0001"); - assert_eq!(NaiveDate::from_ymd(-12, 1, 1).format("%Y").to_string(), "-0012"); - assert_eq!(NaiveDate::from_ymd(-123, 1, 1).format("%Y").to_string(), "-0123"); - assert_eq!(NaiveDate::from_ymd(-1234, 1, 1).format("%Y").to_string(), "-1234"); - assert_eq!(NaiveDate::from_ymd(-12345, 1, 1).format("%Y").to_string(), "-12345"); - - // corner cases - assert_eq!( - NaiveDate::from_ymd(2007, 12, 31).format("%G,%g,%U,%W,%V").to_string(), - "2008,08,53,53,01" - ); - assert_eq!( - NaiveDate::from_ymd(2010, 1, 3).format("%G,%g,%U,%W,%V").to_string(), - "2009,09,01,00,53" - ); - } - - #[test] - fn test_day_iterator_limit() { - assert_eq!( - NaiveDate::from_ymd(262143, 12, 29).iter_days().take(4).collect::>().len(), - 2 - ); - } - - #[test] - fn test_week_iterator_limit() { - assert_eq!( - NaiveDate::from_ymd(262143, 12, 12).iter_weeks().take(4).collect::>().len(), - 2 - ); - } -} diff --git a/third_party/rust/chrono/src/naive/date/mod.rs b/third_party/rust/chrono/src/naive/date/mod.rs new file mode 100644 index 00000000000..62efd454592 --- /dev/null +++ b/third_party/rust/chrono/src/naive/date/mod.rs @@ -0,0 +1,2534 @@ +// This is a part of Chrono. +// See README.md and LICENSE.txt for details. + +//! ISO 8601 calendar date without timezone. +//! +//! The implementation is optimized for determining year, month, day and day of week. +//! +//! Format of `NaiveDate`: +//! `YYYY_YYYY_YYYY_YYYY_YYYO_OOOO_OOOO_LWWW` +//! `Y`: Year +//! `O`: Ordinal +//! `L`: leap year flag (1 = common year, 0 is leap year) +//! `W`: weekday before the first day of the year +//! `LWWW`: will also be referred to as the year flags (`F`) + +#[cfg(feature = "alloc")] +use core::borrow::Borrow; +use core::iter::FusedIterator; +use core::num::NonZeroI32; +use core::ops::{Add, AddAssign, Sub, SubAssign}; +use core::{fmt, str}; + +#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] +use rkyv::{Archive, Deserialize, Serialize}; + +/// L10n locales. +#[cfg(all(feature = "unstable-locales", feature = "alloc"))] +use pure_rust_locales::Locale; + +#[cfg(feature = "alloc")] +use crate::format::DelayedFormat; +use crate::format::{ + Item, Numeric, Pad, ParseError, ParseResult, Parsed, StrftimeItems, parse, parse_and_remainder, + write_hundreds, +}; +use crate::month::Months; +use crate::naive::{Days, IsoWeek, NaiveDateTime, NaiveTime, NaiveWeek}; +use crate::{Datelike, TimeDelta, Weekday}; +use crate::{expect, try_opt}; + +use super::internals::{Mdf, YearFlags}; + +#[cfg(test)] +mod tests; + +/// ISO 8601 calendar date without timezone. +/// Allows for every [proleptic Gregorian date] from Jan 1, 262145 BCE to Dec 31, 262143 CE. +/// Also supports the conversion from ISO 8601 ordinal and week date. +/// +/// # Calendar Date +/// +/// The ISO 8601 **calendar date** follows the proleptic Gregorian calendar. +/// It is like a normal civil calendar but note some slight differences: +/// +/// * Dates before the Gregorian calendar's inception in 1582 are defined via the extrapolation. +/// Be careful, as historical dates are often noted in the Julian calendar and others +/// and the transition to Gregorian may differ across countries (as late as early 20C). +/// +/// (Some example: Both Shakespeare from Britain and Cervantes from Spain seemingly died +/// on the same calendar date---April 23, 1616---but in the different calendar. +/// Britain used the Julian calendar at that time, so Shakespeare's death is later.) +/// +/// * ISO 8601 calendars have the year 0, which is 1 BCE (a year before 1 CE). +/// If you need a typical BCE/BC and CE/AD notation for year numbers, +/// use the [`Datelike::year_ce`] method. +/// +/// # Week Date +/// +/// The ISO 8601 **week date** is a triple of year number, week number +/// and [day of the week](Weekday) with the following rules: +/// +/// * A week consists of Monday through Sunday, and is always numbered within some year. +/// The week number ranges from 1 to 52 or 53 depending on the year. +/// +/// * The week 1 of given year is defined as the first week containing January 4 of that year, +/// or equivalently, the first week containing four or more days in that year. +/// +/// * The year number in the week date may *not* correspond to the actual Gregorian year. +/// For example, January 3, 2016 (Sunday) was on the last (53rd) week of 2015. +/// +/// Chrono's date types default to the ISO 8601 [calendar date](#calendar-date), but +/// [`Datelike::iso_week`] and [`Datelike::weekday`] methods can be used to get the corresponding +/// week date. +/// +/// # Ordinal Date +/// +/// The ISO 8601 **ordinal date** is a pair of year number and day of the year ("ordinal"). +/// The ordinal number ranges from 1 to 365 or 366 depending on the year. +/// The year number is the same as that of the [calendar date](#calendar-date). +/// +/// This is currently the internal format of Chrono's date types. +/// +/// [proleptic Gregorian date]: crate::NaiveDate#calendar-date +#[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)] +#[cfg_attr( + any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), + derive(Archive, Deserialize, Serialize), + archive(compare(PartialEq, PartialOrd)), + archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) +)] +#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] +pub struct NaiveDate { + yof: NonZeroI32, // (year << 13) | of +} + +/// The minimum possible `NaiveDate` (January 1, 262145 BCE). +#[deprecated(since = "0.4.20", note = "Use NaiveDate::MIN instead")] +pub const MIN_DATE: NaiveDate = NaiveDate::MIN; +/// The maximum possible `NaiveDate` (December 31, 262143 CE). +#[deprecated(since = "0.4.20", note = "Use NaiveDate::MAX instead")] +pub const MAX_DATE: NaiveDate = NaiveDate::MAX; + +#[cfg(all(feature = "arbitrary", feature = "std"))] +impl arbitrary::Arbitrary<'_> for NaiveDate { + fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result { + let year = u.int_in_range(MIN_YEAR..=MAX_YEAR)?; + let max_days = YearFlags::from_year(year).ndays(); + let ord = u.int_in_range(1..=max_days)?; + NaiveDate::from_yo_opt(year, ord).ok_or(arbitrary::Error::IncorrectFormat) + } +} + +impl NaiveDate { + pub(crate) fn weeks_from(&self, day: Weekday) -> i32 { + (self.ordinal() as i32 - self.weekday().days_since(day) as i32 + 6) / 7 + } + + /// Makes a new `NaiveDate` from year, ordinal and flags. + /// Does not check whether the flags are correct for the provided year. + const fn from_ordinal_and_flags( + year: i32, + ordinal: u32, + flags: YearFlags, + ) -> Option { + if year < MIN_YEAR || year > MAX_YEAR { + return None; // Out-of-range + } + if ordinal == 0 || ordinal > 366 { + return None; // Invalid + } + debug_assert!(YearFlags::from_year(year).0 == flags.0); + let yof = (year << 13) | (ordinal << 4) as i32 | flags.0 as i32; + match yof & OL_MASK <= MAX_OL { + true => Some(NaiveDate::from_yof(yof)), + false => None, // Does not exist: Ordinal 366 in a common year. + } + } + + /// Makes a new `NaiveDate` from year and packed month-day-flags. + /// Does not check whether the flags are correct for the provided year. + const fn from_mdf(year: i32, mdf: Mdf) -> Option { + if year < MIN_YEAR || year > MAX_YEAR { + return None; // Out-of-range + } + Some(NaiveDate::from_yof((year << 13) | try_opt!(mdf.ordinal_and_flags()))) + } + + /// Makes a new `NaiveDate` from the [calendar date](#calendar-date) + /// (year, month and day). + /// + /// # Panics + /// + /// Panics if the specified calendar day does not exist, on invalid values for `month` or `day`, + /// or if `year` is out of range for `NaiveDate`. + #[deprecated(since = "0.4.23", note = "use `from_ymd_opt()` instead")] + #[must_use] + pub const fn from_ymd(year: i32, month: u32, day: u32) -> NaiveDate { + expect(NaiveDate::from_ymd_opt(year, month, day), "invalid or out-of-range date") + } + + /// Makes a new `NaiveDate` from the [calendar date](#calendar-date) + /// (year, month and day). + /// + /// # Errors + /// + /// Returns `None` if: + /// - The specified calendar day does not exist (for example 2023-04-31). + /// - The value for `month` or `day` is invalid. + /// - `year` is out of range for `NaiveDate`. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveDate; + /// + /// let from_ymd_opt = NaiveDate::from_ymd_opt; + /// + /// assert!(from_ymd_opt(2015, 3, 14).is_some()); + /// assert!(from_ymd_opt(2015, 0, 14).is_none()); + /// assert!(from_ymd_opt(2015, 2, 29).is_none()); + /// assert!(from_ymd_opt(-4, 2, 29).is_some()); // 5 BCE is a leap year + /// assert!(from_ymd_opt(400000, 1, 1).is_none()); + /// assert!(from_ymd_opt(-400000, 1, 1).is_none()); + /// ``` + #[must_use] + pub const fn from_ymd_opt(year: i32, month: u32, day: u32) -> Option { + let flags = YearFlags::from_year(year); + + if let Some(mdf) = Mdf::new(month, day, flags) { + NaiveDate::from_mdf(year, mdf) + } else { + None + } + } + + /// Makes a new `NaiveDate` from the [ordinal date](#ordinal-date) + /// (year and day of the year). + /// + /// # Panics + /// + /// Panics if the specified ordinal day does not exist, on invalid values for `ordinal`, or if + /// `year` is out of range for `NaiveDate`. + #[deprecated(since = "0.4.23", note = "use `from_yo_opt()` instead")] + #[must_use] + pub const fn from_yo(year: i32, ordinal: u32) -> NaiveDate { + expect(NaiveDate::from_yo_opt(year, ordinal), "invalid or out-of-range date") + } + + /// Makes a new `NaiveDate` from the [ordinal date](#ordinal-date) + /// (year and day of the year). + /// + /// # Errors + /// + /// Returns `None` if: + /// - The specified ordinal day does not exist (for example 2023-366). + /// - The value for `ordinal` is invalid (for example: `0`, `400`). + /// - `year` is out of range for `NaiveDate`. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveDate; + /// + /// let from_yo_opt = NaiveDate::from_yo_opt; + /// + /// assert!(from_yo_opt(2015, 100).is_some()); + /// assert!(from_yo_opt(2015, 0).is_none()); + /// assert!(from_yo_opt(2015, 365).is_some()); + /// assert!(from_yo_opt(2015, 366).is_none()); + /// assert!(from_yo_opt(-4, 366).is_some()); // 5 BCE is a leap year + /// assert!(from_yo_opt(400000, 1).is_none()); + /// assert!(from_yo_opt(-400000, 1).is_none()); + /// ``` + #[must_use] + pub const fn from_yo_opt(year: i32, ordinal: u32) -> Option { + let flags = YearFlags::from_year(year); + NaiveDate::from_ordinal_and_flags(year, ordinal, flags) + } + + /// Makes a new `NaiveDate` from the [ISO week date](#week-date) + /// (year, week number and day of the week). + /// The resulting `NaiveDate` may have a different year from the input year. + /// + /// # Panics + /// + /// Panics if the specified week does not exist in that year, on invalid values for `week`, or + /// if the resulting date is out of range for `NaiveDate`. + #[deprecated(since = "0.4.23", note = "use `from_isoywd_opt()` instead")] + #[must_use] + pub const fn from_isoywd(year: i32, week: u32, weekday: Weekday) -> NaiveDate { + expect(NaiveDate::from_isoywd_opt(year, week, weekday), "invalid or out-of-range date") + } + + /// Makes a new `NaiveDate` from the [ISO week date](#week-date) + /// (year, week number and day of the week). + /// The resulting `NaiveDate` may have a different year from the input year. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The specified week does not exist in that year (for example 2023 week 53). + /// - The value for `week` is invalid (for example: `0`, `60`). + /// - If the resulting date is out of range for `NaiveDate`. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, Weekday}; + /// + /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + /// let from_isoywd_opt = NaiveDate::from_isoywd_opt; + /// + /// assert_eq!(from_isoywd_opt(2015, 0, Weekday::Sun), None); + /// assert_eq!(from_isoywd_opt(2015, 10, Weekday::Sun), Some(from_ymd(2015, 3, 8))); + /// assert_eq!(from_isoywd_opt(2015, 30, Weekday::Mon), Some(from_ymd(2015, 7, 20))); + /// assert_eq!(from_isoywd_opt(2015, 60, Weekday::Mon), None); + /// + /// assert_eq!(from_isoywd_opt(400000, 10, Weekday::Fri), None); + /// assert_eq!(from_isoywd_opt(-400000, 10, Weekday::Sat), None); + /// ``` + /// + /// The year number of ISO week date may differ from that of the calendar date. + /// + /// ``` + /// # use chrono::{NaiveDate, Weekday}; + /// # let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + /// # let from_isoywd_opt = NaiveDate::from_isoywd_opt; + /// // Mo Tu We Th Fr Sa Su + /// // 2014-W52 22 23 24 25 26 27 28 has 4+ days of new year, + /// // 2015-W01 29 30 31 1 2 3 4 <- so this is the first week + /// assert_eq!(from_isoywd_opt(2014, 52, Weekday::Sun), Some(from_ymd(2014, 12, 28))); + /// assert_eq!(from_isoywd_opt(2014, 53, Weekday::Mon), None); + /// assert_eq!(from_isoywd_opt(2015, 1, Weekday::Mon), Some(from_ymd(2014, 12, 29))); + /// + /// // 2015-W52 21 22 23 24 25 26 27 has 4+ days of old year, + /// // 2015-W53 28 29 30 31 1 2 3 <- so this is the last week + /// // 2016-W01 4 5 6 7 8 9 10 + /// assert_eq!(from_isoywd_opt(2015, 52, Weekday::Sun), Some(from_ymd(2015, 12, 27))); + /// assert_eq!(from_isoywd_opt(2015, 53, Weekday::Sun), Some(from_ymd(2016, 1, 3))); + /// assert_eq!(from_isoywd_opt(2015, 54, Weekday::Mon), None); + /// assert_eq!(from_isoywd_opt(2016, 1, Weekday::Mon), Some(from_ymd(2016, 1, 4))); + /// ``` + #[must_use] + pub const fn from_isoywd_opt(year: i32, week: u32, weekday: Weekday) -> Option { + let flags = YearFlags::from_year(year); + let nweeks = flags.nisoweeks(); + if week == 0 || week > nweeks { + return None; + } + // ordinal = week ordinal - delta + let weekord = week * 7 + weekday as u32; + let delta = flags.isoweek_delta(); + let (year, ordinal, flags) = if weekord <= delta { + // ordinal < 1, previous year + let prevflags = YearFlags::from_year(year - 1); + (year - 1, weekord + prevflags.ndays() - delta, prevflags) + } else { + let ordinal = weekord - delta; + let ndays = flags.ndays(); + if ordinal <= ndays { + // this year + (year, ordinal, flags) + } else { + // ordinal > ndays, next year + let nextflags = YearFlags::from_year(year + 1); + (year + 1, ordinal - ndays, nextflags) + } + }; + NaiveDate::from_ordinal_and_flags(year, ordinal, flags) + } + + /// Makes a new `NaiveDate` from a day's number in the proleptic Gregorian calendar, with + /// January 1, 1 being day 1. + /// + /// # Panics + /// + /// Panics if the date is out of range. + #[deprecated(since = "0.4.23", note = "use `from_num_days_from_ce_opt()` instead")] + #[inline] + #[must_use] + pub const fn from_num_days_from_ce(days: i32) -> NaiveDate { + expect(NaiveDate::from_num_days_from_ce_opt(days), "out-of-range date") + } + + /// Makes a new `NaiveDate` from a day's number in the proleptic Gregorian calendar, with + /// January 1, 1 being day 1. + /// + /// # Errors + /// + /// Returns `None` if the date is out of range. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveDate; + /// + /// let from_ndays_opt = NaiveDate::from_num_days_from_ce_opt; + /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + /// + /// assert_eq!(from_ndays_opt(730_000), Some(from_ymd(1999, 9, 3))); + /// assert_eq!(from_ndays_opt(1), Some(from_ymd(1, 1, 1))); + /// assert_eq!(from_ndays_opt(0), Some(from_ymd(0, 12, 31))); + /// assert_eq!(from_ndays_opt(-1), Some(from_ymd(0, 12, 30))); + /// assert_eq!(from_ndays_opt(100_000_000), None); + /// assert_eq!(from_ndays_opt(-100_000_000), None); + /// ``` + #[must_use] + pub const fn from_num_days_from_ce_opt(days: i32) -> Option { + let days = try_opt!(days.checked_add(365)); // make December 31, 1 BCE equal to day 0 + let year_div_400 = days.div_euclid(146_097); + let cycle = days.rem_euclid(146_097); + let (year_mod_400, ordinal) = cycle_to_yo(cycle as u32); + let flags = YearFlags::from_year_mod_400(year_mod_400 as i32); + NaiveDate::from_ordinal_and_flags(year_div_400 * 400 + year_mod_400 as i32, ordinal, flags) + } + + /// Makes a new `NaiveDate` by counting the number of occurrences of a particular day-of-week + /// since the beginning of the given month. For instance, if you want the 2nd Friday of March + /// 2017, you would use `NaiveDate::from_weekday_of_month(2017, 3, Weekday::Fri, 2)`. + /// + /// `n` is 1-indexed. + /// + /// # Panics + /// + /// Panics if the specified day does not exist in that month, on invalid values for `month` or + /// `n`, or if `year` is out of range for `NaiveDate`. + #[deprecated(since = "0.4.23", note = "use `from_weekday_of_month_opt()` instead")] + #[must_use] + pub const fn from_weekday_of_month( + year: i32, + month: u32, + weekday: Weekday, + n: u8, + ) -> NaiveDate { + expect(NaiveDate::from_weekday_of_month_opt(year, month, weekday, n), "out-of-range date") + } + + /// Makes a new `NaiveDate` by counting the number of occurrences of a particular day-of-week + /// since the beginning of the given month. For instance, if you want the 2nd Friday of March + /// 2017, you would use `NaiveDate::from_weekday_of_month(2017, 3, Weekday::Fri, 2)`. + /// + /// `n` is 1-indexed. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The specified day does not exist in that month (for example the 5th Monday of Apr. 2023). + /// - The value for `month` or `n` is invalid. + /// - `year` is out of range for `NaiveDate`. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, Weekday}; + /// assert_eq!( + /// NaiveDate::from_weekday_of_month_opt(2017, 3, Weekday::Fri, 2), + /// NaiveDate::from_ymd_opt(2017, 3, 10) + /// ) + /// ``` + #[must_use] + pub const fn from_weekday_of_month_opt( + year: i32, + month: u32, + weekday: Weekday, + n: u8, + ) -> Option { + if n == 0 { + return None; + } + let first = try_opt!(NaiveDate::from_ymd_opt(year, month, 1)).weekday(); + let first_to_dow = (7 + weekday.number_from_monday() - first.number_from_monday()) % 7; + let day = (n - 1) as u32 * 7 + first_to_dow + 1; + NaiveDate::from_ymd_opt(year, month, day) + } + + /// Parses a string with the specified format string and returns a new `NaiveDate`. + /// See the [`format::strftime` module](crate::format::strftime) + /// on the supported escape sequences. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveDate; + /// + /// let parse_from_str = NaiveDate::parse_from_str; + /// + /// assert_eq!( + /// parse_from_str("2015-09-05", "%Y-%m-%d"), + /// Ok(NaiveDate::from_ymd_opt(2015, 9, 5).unwrap()) + /// ); + /// assert_eq!( + /// parse_from_str("5sep2015", "%d%b%Y"), + /// Ok(NaiveDate::from_ymd_opt(2015, 9, 5).unwrap()) + /// ); + /// ``` + /// + /// Time and offset is ignored for the purpose of parsing. + /// + /// ``` + /// # use chrono::NaiveDate; + /// # let parse_from_str = NaiveDate::parse_from_str; + /// assert_eq!( + /// parse_from_str("2014-5-17T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), + /// Ok(NaiveDate::from_ymd_opt(2014, 5, 17).unwrap()) + /// ); + /// ``` + /// + /// Out-of-bound dates or insufficient fields are errors. + /// + /// ``` + /// # use chrono::NaiveDate; + /// # let parse_from_str = NaiveDate::parse_from_str; + /// assert!(parse_from_str("2015/9", "%Y/%m").is_err()); + /// assert!(parse_from_str("2015/9/31", "%Y/%m/%d").is_err()); + /// ``` + /// + /// All parsed fields should be consistent to each other, otherwise it's an error. + /// + /// ``` + /// # use chrono::NaiveDate; + /// # let parse_from_str = NaiveDate::parse_from_str; + /// assert!(parse_from_str("Sat, 09 Aug 2013", "%a, %d %b %Y").is_err()); + /// ``` + pub fn parse_from_str(s: &str, fmt: &str) -> ParseResult { + let mut parsed = Parsed::new(); + parse(&mut parsed, s, StrftimeItems::new(fmt))?; + parsed.to_naive_date() + } + + /// Parses a string from a user-specified format into a new `NaiveDate` value, and a slice with + /// the remaining portion of the string. + /// See the [`format::strftime` module](crate::format::strftime) + /// on the supported escape sequences. + /// + /// Similar to [`parse_from_str`](#method.parse_from_str). + /// + /// # Example + /// + /// ```rust + /// # use chrono::{NaiveDate}; + /// let (date, remainder) = + /// NaiveDate::parse_and_remainder("2015-02-18 trailing text", "%Y-%m-%d").unwrap(); + /// assert_eq!(date, NaiveDate::from_ymd_opt(2015, 2, 18).unwrap()); + /// assert_eq!(remainder, " trailing text"); + /// ``` + pub fn parse_and_remainder<'a>(s: &'a str, fmt: &str) -> ParseResult<(NaiveDate, &'a str)> { + let mut parsed = Parsed::new(); + let remainder = parse_and_remainder(&mut parsed, s, StrftimeItems::new(fmt))?; + parsed.to_naive_date().map(|d| (d, remainder)) + } + + /// Add a duration in [`Months`] to the date + /// + /// Uses the last day of the month if the day does not exist in the resulting month. + /// + /// # Errors + /// + /// Returns `None` if the resulting date would be out of range. + /// + /// # Example + /// + /// ``` + /// # use chrono::{NaiveDate, Months}; + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2022, 2, 20).unwrap().checked_add_months(Months::new(6)), + /// Some(NaiveDate::from_ymd_opt(2022, 8, 20).unwrap()) + /// ); + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2022, 7, 31).unwrap().checked_add_months(Months::new(2)), + /// Some(NaiveDate::from_ymd_opt(2022, 9, 30).unwrap()) + /// ); + /// ``` + #[must_use] + pub const fn checked_add_months(self, months: Months) -> Option { + if months.0 == 0 { + return Some(self); + } + + match months.0 <= i32::MAX as u32 { + true => self.diff_months(months.0 as i32), + false => None, + } + } + + /// Subtract a duration in [`Months`] from the date + /// + /// Uses the last day of the month if the day does not exist in the resulting month. + /// + /// # Errors + /// + /// Returns `None` if the resulting date would be out of range. + /// + /// # Example + /// + /// ``` + /// # use chrono::{NaiveDate, Months}; + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2022, 2, 20).unwrap().checked_sub_months(Months::new(6)), + /// Some(NaiveDate::from_ymd_opt(2021, 8, 20).unwrap()) + /// ); + /// + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2014, 1, 1) + /// .unwrap() + /// .checked_sub_months(Months::new(core::i32::MAX as u32 + 1)), + /// None + /// ); + /// ``` + #[must_use] + pub const fn checked_sub_months(self, months: Months) -> Option { + if months.0 == 0 { + return Some(self); + } + + match months.0 <= i32::MAX as u32 { + true => self.diff_months(-(months.0 as i32)), + false => None, + } + } + + const fn diff_months(self, months: i32) -> Option { + let months = try_opt!((self.year() * 12 + self.month() as i32 - 1).checked_add(months)); + let year = months.div_euclid(12); + let month = months.rem_euclid(12) as u32 + 1; + + // Clamp original day in case new month is shorter + let flags = YearFlags::from_year(year); + let feb_days = if flags.ndays() == 366 { 29 } else { 28 }; + let days = [31, feb_days, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + let day_max = days[(month - 1) as usize]; + let mut day = self.day(); + if day > day_max { + day = day_max; + }; + + NaiveDate::from_ymd_opt(year, month, day) + } + + /// Add a duration in [`Days`] to the date + /// + /// # Errors + /// + /// Returns `None` if the resulting date would be out of range. + /// + /// # Example + /// + /// ``` + /// # use chrono::{NaiveDate, Days}; + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2022, 2, 20).unwrap().checked_add_days(Days::new(9)), + /// Some(NaiveDate::from_ymd_opt(2022, 3, 1).unwrap()) + /// ); + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2022, 7, 31).unwrap().checked_add_days(Days::new(2)), + /// Some(NaiveDate::from_ymd_opt(2022, 8, 2).unwrap()) + /// ); + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2022, 7, 31).unwrap().checked_add_days(Days::new(1000000000000)), + /// None + /// ); + /// ``` + #[must_use] + pub const fn checked_add_days(self, days: Days) -> Option { + match days.0 <= i32::MAX as u64 { + true => self.add_days(days.0 as i32), + false => None, + } + } + + /// Subtract a duration in [`Days`] from the date + /// + /// # Errors + /// + /// Returns `None` if the resulting date would be out of range. + /// + /// # Example + /// + /// ``` + /// # use chrono::{NaiveDate, Days}; + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2022, 2, 20).unwrap().checked_sub_days(Days::new(6)), + /// Some(NaiveDate::from_ymd_opt(2022, 2, 14).unwrap()) + /// ); + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2022, 2, 20).unwrap().checked_sub_days(Days::new(1000000000000)), + /// None + /// ); + /// ``` + #[must_use] + pub const fn checked_sub_days(self, days: Days) -> Option { + match days.0 <= i32::MAX as u64 { + true => self.add_days(-(days.0 as i32)), + false => None, + } + } + + /// Add a duration of `i32` days to the date. + pub(crate) const fn add_days(self, days: i32) -> Option { + // Fast path if the result is within the same year. + // Also `DateTime::checked_(add|sub)_days` relies on this path, because if the value remains + // within the year it doesn't do a check if the year is in range. + // This way `DateTime:checked_(add|sub)_days(Days::new(0))` can be a no-op on dates were the + // local datetime is beyond `NaiveDate::{MIN, MAX}. + const ORDINAL_MASK: i32 = 0b1_1111_1111_0000; + if let Some(ordinal) = ((self.yof() & ORDINAL_MASK) >> 4).checked_add(days) { + if ordinal > 0 && ordinal <= (365 + self.leap_year() as i32) { + let year_and_flags = self.yof() & !ORDINAL_MASK; + return Some(NaiveDate::from_yof(year_and_flags | (ordinal << 4))); + } + } + // do the full check + let year = self.year(); + let (mut year_div_400, year_mod_400) = div_mod_floor(year, 400); + let cycle = yo_to_cycle(year_mod_400 as u32, self.ordinal()); + let cycle = try_opt!((cycle as i32).checked_add(days)); + let (cycle_div_400y, cycle) = div_mod_floor(cycle, 146_097); + year_div_400 += cycle_div_400y; + + let (year_mod_400, ordinal) = cycle_to_yo(cycle as u32); + let flags = YearFlags::from_year_mod_400(year_mod_400 as i32); + NaiveDate::from_ordinal_and_flags(year_div_400 * 400 + year_mod_400 as i32, ordinal, flags) + } + + /// Makes a new `NaiveDateTime` from the current date and given `NaiveTime`. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, NaiveDateTime, NaiveTime}; + /// + /// let d = NaiveDate::from_ymd_opt(2015, 6, 3).unwrap(); + /// let t = NaiveTime::from_hms_milli_opt(12, 34, 56, 789).unwrap(); + /// + /// let dt: NaiveDateTime = d.and_time(t); + /// assert_eq!(dt.date(), d); + /// assert_eq!(dt.time(), t); + /// ``` + #[inline] + #[must_use] + pub const fn and_time(&self, time: NaiveTime) -> NaiveDateTime { + NaiveDateTime::new(*self, time) + } + + /// Makes a new `NaiveDateTime` from the current date, hour, minute and second. + /// + /// No [leap second](./struct.NaiveTime.html#leap-second-handling) is allowed here; + /// use `NaiveDate::and_hms_*` methods with a subsecond parameter instead. + /// + /// # Panics + /// + /// Panics on invalid hour, minute and/or second. + #[deprecated(since = "0.4.23", note = "use `and_hms_opt()` instead")] + #[inline] + #[must_use] + pub const fn and_hms(&self, hour: u32, min: u32, sec: u32) -> NaiveDateTime { + expect(self.and_hms_opt(hour, min, sec), "invalid time") + } + + /// Makes a new `NaiveDateTime` from the current date, hour, minute and second. + /// + /// No [leap second](./struct.NaiveTime.html#leap-second-handling) is allowed here; + /// use `NaiveDate::and_hms_*_opt` methods with a subsecond parameter instead. + /// + /// # Errors + /// + /// Returns `None` on invalid hour, minute and/or second. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveDate; + /// + /// let d = NaiveDate::from_ymd_opt(2015, 6, 3).unwrap(); + /// assert!(d.and_hms_opt(12, 34, 56).is_some()); + /// assert!(d.and_hms_opt(12, 34, 60).is_none()); // use `and_hms_milli_opt` instead + /// assert!(d.and_hms_opt(12, 60, 56).is_none()); + /// assert!(d.and_hms_opt(24, 34, 56).is_none()); + /// ``` + #[inline] + #[must_use] + pub const fn and_hms_opt(&self, hour: u32, min: u32, sec: u32) -> Option { + let time = try_opt!(NaiveTime::from_hms_opt(hour, min, sec)); + Some(self.and_time(time)) + } + + /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and millisecond. + /// + /// The millisecond part is allowed to exceed 1,000,000,000 in order to represent a [leap second]( + /// ./struct.NaiveTime.html#leap-second-handling), but only when `sec == 59`. + /// + /// # Panics + /// + /// Panics on invalid hour, minute, second and/or millisecond. + #[deprecated(since = "0.4.23", note = "use `and_hms_milli_opt()` instead")] + #[inline] + #[must_use] + pub const fn and_hms_milli(&self, hour: u32, min: u32, sec: u32, milli: u32) -> NaiveDateTime { + expect(self.and_hms_milli_opt(hour, min, sec, milli), "invalid time") + } + + /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and millisecond. + /// + /// The millisecond part is allowed to exceed 1,000,000,000 in order to represent a [leap second]( + /// ./struct.NaiveTime.html#leap-second-handling), but only when `sec == 59`. + /// + /// # Errors + /// + /// Returns `None` on invalid hour, minute, second and/or millisecond. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveDate; + /// + /// let d = NaiveDate::from_ymd_opt(2015, 6, 3).unwrap(); + /// assert!(d.and_hms_milli_opt(12, 34, 56, 789).is_some()); + /// assert!(d.and_hms_milli_opt(12, 34, 59, 1_789).is_some()); // leap second + /// assert!(d.and_hms_milli_opt(12, 34, 59, 2_789).is_none()); + /// assert!(d.and_hms_milli_opt(12, 34, 60, 789).is_none()); + /// assert!(d.and_hms_milli_opt(12, 60, 56, 789).is_none()); + /// assert!(d.and_hms_milli_opt(24, 34, 56, 789).is_none()); + /// ``` + #[inline] + #[must_use] + pub const fn and_hms_milli_opt( + &self, + hour: u32, + min: u32, + sec: u32, + milli: u32, + ) -> Option { + let time = try_opt!(NaiveTime::from_hms_milli_opt(hour, min, sec, milli)); + Some(self.and_time(time)) + } + + /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and microsecond. + /// + /// The microsecond part is allowed to exceed 1,000,000,000 in order to represent a [leap second]( + /// ./struct.NaiveTime.html#leap-second-handling), but only when `sec == 59`. + /// + /// # Panics + /// + /// Panics on invalid hour, minute, second and/or microsecond. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate, NaiveDateTime, Timelike, Weekday}; + /// + /// let d = NaiveDate::from_ymd_opt(2015, 6, 3).unwrap(); + /// + /// let dt: NaiveDateTime = d.and_hms_micro_opt(12, 34, 56, 789_012).unwrap(); + /// assert_eq!(dt.year(), 2015); + /// assert_eq!(dt.weekday(), Weekday::Wed); + /// assert_eq!(dt.second(), 56); + /// assert_eq!(dt.nanosecond(), 789_012_000); + /// ``` + #[deprecated(since = "0.4.23", note = "use `and_hms_micro_opt()` instead")] + #[inline] + #[must_use] + pub const fn and_hms_micro(&self, hour: u32, min: u32, sec: u32, micro: u32) -> NaiveDateTime { + expect(self.and_hms_micro_opt(hour, min, sec, micro), "invalid time") + } + + /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and microsecond. + /// + /// The microsecond part is allowed to exceed 1,000,000 in order to represent a [leap second]( + /// ./struct.NaiveTime.html#leap-second-handling), but only when `sec == 59`. + /// + /// # Errors + /// + /// Returns `None` on invalid hour, minute, second and/or microsecond. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveDate; + /// + /// let d = NaiveDate::from_ymd_opt(2015, 6, 3).unwrap(); + /// assert!(d.and_hms_micro_opt(12, 34, 56, 789_012).is_some()); + /// assert!(d.and_hms_micro_opt(12, 34, 59, 1_789_012).is_some()); // leap second + /// assert!(d.and_hms_micro_opt(12, 34, 59, 2_789_012).is_none()); + /// assert!(d.and_hms_micro_opt(12, 34, 60, 789_012).is_none()); + /// assert!(d.and_hms_micro_opt(12, 60, 56, 789_012).is_none()); + /// assert!(d.and_hms_micro_opt(24, 34, 56, 789_012).is_none()); + /// ``` + #[inline] + #[must_use] + pub const fn and_hms_micro_opt( + &self, + hour: u32, + min: u32, + sec: u32, + micro: u32, + ) -> Option { + let time = try_opt!(NaiveTime::from_hms_micro_opt(hour, min, sec, micro)); + Some(self.and_time(time)) + } + + /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and nanosecond. + /// + /// The nanosecond part is allowed to exceed 1,000,000,000 in order to represent a [leap second]( + /// ./struct.NaiveTime.html#leap-second-handling), but only when `sec == 59`. + /// + /// # Panics + /// + /// Panics on invalid hour, minute, second and/or nanosecond. + #[deprecated(since = "0.4.23", note = "use `and_hms_nano_opt()` instead")] + #[inline] + #[must_use] + pub const fn and_hms_nano(&self, hour: u32, min: u32, sec: u32, nano: u32) -> NaiveDateTime { + expect(self.and_hms_nano_opt(hour, min, sec, nano), "invalid time") + } + + /// Makes a new `NaiveDateTime` from the current date, hour, minute, second and nanosecond. + /// + /// The nanosecond part is allowed to exceed 1,000,000,000 in order to represent a [leap second]( + /// ./struct.NaiveTime.html#leap-second-handling), but only when `sec == 59`. + /// + /// # Errors + /// + /// Returns `None` on invalid hour, minute, second and/or nanosecond. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveDate; + /// + /// let d = NaiveDate::from_ymd_opt(2015, 6, 3).unwrap(); + /// assert!(d.and_hms_nano_opt(12, 34, 56, 789_012_345).is_some()); + /// assert!(d.and_hms_nano_opt(12, 34, 59, 1_789_012_345).is_some()); // leap second + /// assert!(d.and_hms_nano_opt(12, 34, 59, 2_789_012_345).is_none()); + /// assert!(d.and_hms_nano_opt(12, 34, 60, 789_012_345).is_none()); + /// assert!(d.and_hms_nano_opt(12, 60, 56, 789_012_345).is_none()); + /// assert!(d.and_hms_nano_opt(24, 34, 56, 789_012_345).is_none()); + /// ``` + #[inline] + #[must_use] + pub const fn and_hms_nano_opt( + &self, + hour: u32, + min: u32, + sec: u32, + nano: u32, + ) -> Option { + let time = try_opt!(NaiveTime::from_hms_nano_opt(hour, min, sec, nano)); + Some(self.and_time(time)) + } + + /// Returns the packed month-day-flags. + #[inline] + const fn mdf(&self) -> Mdf { + Mdf::from_ol((self.yof() & OL_MASK) >> 3, self.year_flags()) + } + + /// Makes a new `NaiveDate` with the packed month-day-flags changed. + /// + /// Returns `None` when the resulting `NaiveDate` would be invalid. + #[inline] + const fn with_mdf(&self, mdf: Mdf) -> Option { + debug_assert!(self.year_flags().0 == mdf.year_flags().0); + match mdf.ordinal() { + Some(ordinal) => { + Some(NaiveDate::from_yof((self.yof() & !ORDINAL_MASK) | (ordinal << 4) as i32)) + } + None => None, // Non-existing date + } + } + + /// Makes a new `NaiveDate` for the next calendar date. + /// + /// # Panics + /// + /// Panics when `self` is the last representable date. + #[deprecated(since = "0.4.23", note = "use `succ_opt()` instead")] + #[inline] + #[must_use] + pub const fn succ(&self) -> NaiveDate { + expect(self.succ_opt(), "out of bound") + } + + /// Makes a new `NaiveDate` for the next calendar date. + /// + /// # Errors + /// + /// Returns `None` when `self` is the last representable date. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveDate; + /// + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2015, 6, 3).unwrap().succ_opt(), + /// Some(NaiveDate::from_ymd_opt(2015, 6, 4).unwrap()) + /// ); + /// assert_eq!(NaiveDate::MAX.succ_opt(), None); + /// ``` + #[inline] + #[must_use] + pub const fn succ_opt(&self) -> Option { + let new_ol = (self.yof() & OL_MASK) + (1 << 4); + match new_ol <= MAX_OL { + true => Some(NaiveDate::from_yof(self.yof() & !OL_MASK | new_ol)), + false => NaiveDate::from_yo_opt(self.year() + 1, 1), + } + } + + /// Makes a new `NaiveDate` for the previous calendar date. + /// + /// # Panics + /// + /// Panics when `self` is the first representable date. + #[deprecated(since = "0.4.23", note = "use `pred_opt()` instead")] + #[inline] + #[must_use] + pub const fn pred(&self) -> NaiveDate { + expect(self.pred_opt(), "out of bound") + } + + /// Makes a new `NaiveDate` for the previous calendar date. + /// + /// # Errors + /// + /// Returns `None` when `self` is the first representable date. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveDate; + /// + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2015, 6, 3).unwrap().pred_opt(), + /// Some(NaiveDate::from_ymd_opt(2015, 6, 2).unwrap()) + /// ); + /// assert_eq!(NaiveDate::MIN.pred_opt(), None); + /// ``` + #[inline] + #[must_use] + pub const fn pred_opt(&self) -> Option { + let new_shifted_ordinal = (self.yof() & ORDINAL_MASK) - (1 << 4); + match new_shifted_ordinal > 0 { + true => Some(NaiveDate::from_yof(self.yof() & !ORDINAL_MASK | new_shifted_ordinal)), + false => NaiveDate::from_ymd_opt(self.year() - 1, 12, 31), + } + } + + /// Adds the number of whole days in the given `TimeDelta` to the current date. + /// + /// # Errors + /// + /// Returns `None` if the resulting date would be out of range. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, TimeDelta}; + /// + /// let d = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap(); + /// assert_eq!( + /// d.checked_add_signed(TimeDelta::try_days(40).unwrap()), + /// Some(NaiveDate::from_ymd_opt(2015, 10, 15).unwrap()) + /// ); + /// assert_eq!( + /// d.checked_add_signed(TimeDelta::try_days(-40).unwrap()), + /// Some(NaiveDate::from_ymd_opt(2015, 7, 27).unwrap()) + /// ); + /// assert_eq!(d.checked_add_signed(TimeDelta::try_days(1_000_000_000).unwrap()), None); + /// assert_eq!(d.checked_add_signed(TimeDelta::try_days(-1_000_000_000).unwrap()), None); + /// assert_eq!(NaiveDate::MAX.checked_add_signed(TimeDelta::try_days(1).unwrap()), None); + /// ``` + #[must_use] + pub const fn checked_add_signed(self, rhs: TimeDelta) -> Option { + let days = rhs.num_days(); + if days < i32::MIN as i64 || days > i32::MAX as i64 { + return None; + } + self.add_days(days as i32) + } + + /// Subtracts the number of whole days in the given `TimeDelta` from the current date. + /// + /// # Errors + /// + /// Returns `None` if the resulting date would be out of range. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, TimeDelta}; + /// + /// let d = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap(); + /// assert_eq!( + /// d.checked_sub_signed(TimeDelta::try_days(40).unwrap()), + /// Some(NaiveDate::from_ymd_opt(2015, 7, 27).unwrap()) + /// ); + /// assert_eq!( + /// d.checked_sub_signed(TimeDelta::try_days(-40).unwrap()), + /// Some(NaiveDate::from_ymd_opt(2015, 10, 15).unwrap()) + /// ); + /// assert_eq!(d.checked_sub_signed(TimeDelta::try_days(1_000_000_000).unwrap()), None); + /// assert_eq!(d.checked_sub_signed(TimeDelta::try_days(-1_000_000_000).unwrap()), None); + /// assert_eq!(NaiveDate::MIN.checked_sub_signed(TimeDelta::try_days(1).unwrap()), None); + /// ``` + #[must_use] + pub const fn checked_sub_signed(self, rhs: TimeDelta) -> Option { + let days = -rhs.num_days(); + if days < i32::MIN as i64 || days > i32::MAX as i64 { + return None; + } + self.add_days(days as i32) + } + + /// Subtracts another `NaiveDate` from the current date. + /// Returns a `TimeDelta` of integral numbers. + /// + /// This does not overflow or underflow at all, + /// as all possible output fits in the range of `TimeDelta`. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, TimeDelta}; + /// + /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + /// let since = NaiveDate::signed_duration_since; + /// + /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2014, 1, 1)), TimeDelta::zero()); + /// assert_eq!( + /// since(from_ymd(2014, 1, 1), from_ymd(2013, 12, 31)), + /// TimeDelta::try_days(1).unwrap() + /// ); + /// assert_eq!(since(from_ymd(2014, 1, 1), from_ymd(2014, 1, 2)), TimeDelta::try_days(-1).unwrap()); + /// assert_eq!( + /// since(from_ymd(2014, 1, 1), from_ymd(2013, 9, 23)), + /// TimeDelta::try_days(100).unwrap() + /// ); + /// assert_eq!( + /// since(from_ymd(2014, 1, 1), from_ymd(2013, 1, 1)), + /// TimeDelta::try_days(365).unwrap() + /// ); + /// assert_eq!( + /// since(from_ymd(2014, 1, 1), from_ymd(2010, 1, 1)), + /// TimeDelta::try_days(365 * 4 + 1).unwrap() + /// ); + /// assert_eq!( + /// since(from_ymd(2014, 1, 1), from_ymd(1614, 1, 1)), + /// TimeDelta::try_days(365 * 400 + 97).unwrap() + /// ); + /// ``` + #[must_use] + pub const fn signed_duration_since(self, rhs: NaiveDate) -> TimeDelta { + let year1 = self.year(); + let year2 = rhs.year(); + let (year1_div_400, year1_mod_400) = div_mod_floor(year1, 400); + let (year2_div_400, year2_mod_400) = div_mod_floor(year2, 400); + let cycle1 = yo_to_cycle(year1_mod_400 as u32, self.ordinal()) as i64; + let cycle2 = yo_to_cycle(year2_mod_400 as u32, rhs.ordinal()) as i64; + let days = (year1_div_400 as i64 - year2_div_400 as i64) * 146_097 + (cycle1 - cycle2); + // The range of `TimeDelta` is ca. 585 million years, the range of `NaiveDate` ca. 525.000 + // years. + expect(TimeDelta::try_days(days), "always in range") + } + + /// Returns the number of whole years from the given `base` until `self`. + /// + /// # Errors + /// + /// Returns `None` if `base > self`. + #[must_use] + pub const fn years_since(&self, base: Self) -> Option { + let mut years = self.year() - base.year(); + // Comparing tuples is not (yet) possible in const context. Instead we combine month and + // day into one `u32` for easy comparison. + if ((self.month() << 5) | self.day()) < ((base.month() << 5) | base.day()) { + years -= 1; + } + + match years >= 0 { + true => Some(years as u32), + false => None, + } + } + + /// Formats the date with the specified formatting items. + /// Otherwise it is the same as the ordinary `format` method. + /// + /// The `Iterator` of items should be `Clone`able, + /// since the resulting `DelayedFormat` value may be formatted multiple times. + /// + /// # Example + /// + /// ``` + /// use chrono::format::strftime::StrftimeItems; + /// use chrono::NaiveDate; + /// + /// let fmt = StrftimeItems::new("%Y-%m-%d"); + /// let d = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap(); + /// assert_eq!(d.format_with_items(fmt.clone()).to_string(), "2015-09-05"); + /// assert_eq!(d.format("%Y-%m-%d").to_string(), "2015-09-05"); + /// ``` + /// + /// The resulting `DelayedFormat` can be formatted directly via the `Display` trait. + /// + /// ``` + /// # use chrono::NaiveDate; + /// # use chrono::format::strftime::StrftimeItems; + /// # let fmt = StrftimeItems::new("%Y-%m-%d").clone(); + /// # let d = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap(); + /// assert_eq!(format!("{}", d.format_with_items(fmt)), "2015-09-05"); + /// ``` + #[cfg(feature = "alloc")] + #[inline] + #[must_use] + pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat + where + I: Iterator + Clone, + B: Borrow>, + { + DelayedFormat::new(Some(*self), None, items) + } + + /// Formats the date with the specified format string. + /// See the [`format::strftime` module](crate::format::strftime) + /// on the supported escape sequences. + /// + /// This returns a `DelayedFormat`, + /// which gets converted to a string only when actual formatting happens. + /// You may use the `to_string` method to get a `String`, + /// or just feed it into `print!` and other formatting macros. + /// (In this way it avoids the redundant memory allocation.) + /// + /// # Panics + /// + /// Converting or formatting the returned `DelayedFormat` panics if the format string is wrong. + /// Because of this delayed failure, you are recommended to immediately use the `DelayedFormat` + /// value. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveDate; + /// + /// let d = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap(); + /// assert_eq!(d.format("%Y-%m-%d").to_string(), "2015-09-05"); + /// assert_eq!(d.format("%A, %-d %B, %C%y").to_string(), "Saturday, 5 September, 2015"); + /// ``` + /// + /// The resulting `DelayedFormat` can be formatted directly via the `Display` trait. + /// + /// ``` + /// # use chrono::NaiveDate; + /// # let d = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap(); + /// assert_eq!(format!("{}", d.format("%Y-%m-%d")), "2015-09-05"); + /// assert_eq!(format!("{}", d.format("%A, %-d %B, %C%y")), "Saturday, 5 September, 2015"); + /// ``` + #[cfg(feature = "alloc")] + #[inline] + #[must_use] + pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { + self.format_with_items(StrftimeItems::new(fmt)) + } + + /// Formats the date with the specified formatting items and locale. + #[cfg(all(feature = "unstable-locales", feature = "alloc"))] + #[inline] + #[must_use] + pub fn format_localized_with_items<'a, I, B>( + &self, + items: I, + locale: Locale, + ) -> DelayedFormat + where + I: Iterator + Clone, + B: Borrow>, + { + DelayedFormat::new_with_locale(Some(*self), None, items, locale) + } + + /// Formats the date with the specified format string and locale. + /// + /// See the [`crate::format::strftime`] module on the supported escape + /// sequences. + #[cfg(all(feature = "unstable-locales", feature = "alloc"))] + #[inline] + #[must_use] + pub fn format_localized<'a>( + &self, + fmt: &'a str, + locale: Locale, + ) -> DelayedFormat> { + self.format_localized_with_items(StrftimeItems::new_with_locale(fmt, locale), locale) + } + + /// Returns an iterator that steps by days across all representable dates. + /// + /// # Example + /// + /// ``` + /// # use chrono::NaiveDate; + /// + /// let expected = [ + /// NaiveDate::from_ymd_opt(2016, 2, 27).unwrap(), + /// NaiveDate::from_ymd_opt(2016, 2, 28).unwrap(), + /// NaiveDate::from_ymd_opt(2016, 2, 29).unwrap(), + /// NaiveDate::from_ymd_opt(2016, 3, 1).unwrap(), + /// ]; + /// + /// let mut count = 0; + /// for (idx, d) in NaiveDate::from_ymd_opt(2016, 2, 27).unwrap().iter_days().take(4).enumerate() { + /// assert_eq!(d, expected[idx]); + /// count += 1; + /// } + /// assert_eq!(count, 4); + /// + /// for d in NaiveDate::from_ymd_opt(2016, 3, 1).unwrap().iter_days().rev().take(4) { + /// count -= 1; + /// assert_eq!(d, expected[count]); + /// } + /// ``` + #[inline] + pub const fn iter_days(&self) -> NaiveDateDaysIterator { + NaiveDateDaysIterator { value: *self } + } + + /// Returns an iterator that steps by weeks across all representable dates. + /// + /// # Example + /// + /// ``` + /// # use chrono::NaiveDate; + /// + /// let expected = [ + /// NaiveDate::from_ymd_opt(2016, 2, 27).unwrap(), + /// NaiveDate::from_ymd_opt(2016, 3, 5).unwrap(), + /// NaiveDate::from_ymd_opt(2016, 3, 12).unwrap(), + /// NaiveDate::from_ymd_opt(2016, 3, 19).unwrap(), + /// ]; + /// + /// let mut count = 0; + /// for (idx, d) in NaiveDate::from_ymd_opt(2016, 2, 27).unwrap().iter_weeks().take(4).enumerate() { + /// assert_eq!(d, expected[idx]); + /// count += 1; + /// } + /// assert_eq!(count, 4); + /// + /// for d in NaiveDate::from_ymd_opt(2016, 3, 19).unwrap().iter_weeks().rev().take(4) { + /// count -= 1; + /// assert_eq!(d, expected[count]); + /// } + /// ``` + #[inline] + pub const fn iter_weeks(&self) -> NaiveDateWeeksIterator { + NaiveDateWeeksIterator { value: *self } + } + + /// Returns the [`NaiveWeek`] that the date belongs to, starting with the [`Weekday`] + /// specified. + #[inline] + pub const fn week(&self, start: Weekday) -> NaiveWeek { + NaiveWeek::new(*self, start) + } + + /// Returns `true` if this is a leap year. + /// + /// ``` + /// # use chrono::NaiveDate; + /// assert_eq!(NaiveDate::from_ymd_opt(2000, 1, 1).unwrap().leap_year(), true); + /// assert_eq!(NaiveDate::from_ymd_opt(2001, 1, 1).unwrap().leap_year(), false); + /// assert_eq!(NaiveDate::from_ymd_opt(2002, 1, 1).unwrap().leap_year(), false); + /// assert_eq!(NaiveDate::from_ymd_opt(2003, 1, 1).unwrap().leap_year(), false); + /// assert_eq!(NaiveDate::from_ymd_opt(2004, 1, 1).unwrap().leap_year(), true); + /// assert_eq!(NaiveDate::from_ymd_opt(2100, 1, 1).unwrap().leap_year(), false); + /// ``` + pub const fn leap_year(&self) -> bool { + self.yof() & (0b1000) == 0 + } + + // This duplicates `Datelike::year()`, because trait methods can't be const yet. + #[inline] + const fn year(&self) -> i32 { + self.yof() >> 13 + } + + /// Returns the day of year starting from 1. + // This duplicates `Datelike::ordinal()`, because trait methods can't be const yet. + #[inline] + const fn ordinal(&self) -> u32 { + ((self.yof() & ORDINAL_MASK) >> 4) as u32 + } + + // This duplicates `Datelike::month()`, because trait methods can't be const yet. + #[inline] + const fn month(&self) -> u32 { + self.mdf().month() + } + + // This duplicates `Datelike::day()`, because trait methods can't be const yet. + #[inline] + const fn day(&self) -> u32 { + self.mdf().day() + } + + /// Returns the day of week. + // This duplicates `Datelike::weekday()`, because trait methods can't be const yet. + #[inline] + pub(super) const fn weekday(&self) -> Weekday { + match (((self.yof() & ORDINAL_MASK) >> 4) + (self.yof() & WEEKDAY_FLAGS_MASK)) % 7 { + 0 => Weekday::Mon, + 1 => Weekday::Tue, + 2 => Weekday::Wed, + 3 => Weekday::Thu, + 4 => Weekday::Fri, + 5 => Weekday::Sat, + _ => Weekday::Sun, + } + } + + #[inline] + const fn year_flags(&self) -> YearFlags { + YearFlags((self.yof() & YEAR_FLAGS_MASK) as u8) + } + + /// Counts the days in the proleptic Gregorian calendar, with January 1, Year 1 (CE) as day 1. + // This duplicates `Datelike::num_days_from_ce()`, because trait methods can't be const yet. + pub(crate) const fn num_days_from_ce(&self) -> i32 { + // we know this wouldn't overflow since year is limited to 1/2^13 of i32's full range. + let mut year = self.year() - 1; + let mut ndays = 0; + if year < 0 { + let excess = 1 + (-year) / 400; + year += excess * 400; + ndays -= excess * 146_097; + } + let div_100 = year / 100; + ndays += ((year * 1461) >> 2) - div_100 + (div_100 >> 2); + ndays + self.ordinal() as i32 + } + + /// Create a new `NaiveDate` from a raw year-ordinal-flags `i32`. + /// + /// In a valid value an ordinal is never `0`, and neither are the year flags. This method + /// doesn't do any validation in release builds. + #[inline] + const fn from_yof(yof: i32) -> NaiveDate { + // The following are the invariants our ordinal and flags should uphold for a valid + // `NaiveDate`. + debug_assert!(((yof & OL_MASK) >> 3) > 1); + debug_assert!(((yof & OL_MASK) >> 3) <= MAX_OL); + debug_assert!((yof & 0b111) != 000); + NaiveDate { yof: unsafe { NonZeroI32::new_unchecked(yof) } } + } + + /// Get the raw year-ordinal-flags `i32`. + #[inline] + const fn yof(&self) -> i32 { + self.yof.get() + } + + /// The minimum possible `NaiveDate` (January 1, 262144 BCE). + pub const MIN: NaiveDate = NaiveDate::from_yof((MIN_YEAR << 13) | (1 << 4) | 0o12 /* D */); + /// The maximum possible `NaiveDate` (December 31, 262142 CE). + pub const MAX: NaiveDate = + NaiveDate::from_yof((MAX_YEAR << 13) | (365 << 4) | 0o16 /* G */); + + /// One day before the minimum possible `NaiveDate` (December 31, 262145 BCE). + pub(crate) const BEFORE_MIN: NaiveDate = + NaiveDate::from_yof(((MIN_YEAR - 1) << 13) | (366 << 4) | 0o07 /* FE */); + /// One day after the maximum possible `NaiveDate` (January 1, 262143 CE). + pub(crate) const AFTER_MAX: NaiveDate = + NaiveDate::from_yof(((MAX_YEAR + 1) << 13) | (1 << 4) | 0o17 /* F */); +} + +impl Datelike for NaiveDate { + /// Returns the year number in the [calendar date](#calendar-date). + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().year(), 2015); + /// assert_eq!(NaiveDate::from_ymd_opt(-308, 3, 14).unwrap().year(), -308); // 309 BCE + /// ``` + #[inline] + fn year(&self) -> i32 { + self.year() + } + + /// Returns the month number starting from 1. + /// + /// The return value ranges from 1 to 12. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().month(), 9); + /// assert_eq!(NaiveDate::from_ymd_opt(-308, 3, 14).unwrap().month(), 3); + /// ``` + #[inline] + fn month(&self) -> u32 { + self.month() + } + + /// Returns the month number starting from 0. + /// + /// The return value ranges from 0 to 11. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().month0(), 8); + /// assert_eq!(NaiveDate::from_ymd_opt(-308, 3, 14).unwrap().month0(), 2); + /// ``` + #[inline] + fn month0(&self) -> u32 { + self.month() - 1 + } + + /// Returns the day of month starting from 1. + /// + /// The return value ranges from 1 to 31. (The last day of month differs by months.) + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().day(), 8); + /// assert_eq!(NaiveDate::from_ymd_opt(-308, 3, 14).unwrap().day(), 14); + /// ``` + /// + /// Combined with [`NaiveDate::pred_opt`](#method.pred_opt), + /// one can determine the number of days in a particular month. + /// (Note that this panics when `year` is out of range.) + /// + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// fn ndays_in_month(year: i32, month: u32) -> u32 { + /// // the first day of the next month... + /// let (y, m) = if month == 12 { (year + 1, 1) } else { (year, month + 1) }; + /// let d = NaiveDate::from_ymd_opt(y, m, 1).unwrap(); + /// + /// // ...is preceded by the last day of the original month + /// d.pred_opt().unwrap().day() + /// } + /// + /// assert_eq!(ndays_in_month(2015, 8), 31); + /// assert_eq!(ndays_in_month(2015, 9), 30); + /// assert_eq!(ndays_in_month(2015, 12), 31); + /// assert_eq!(ndays_in_month(2016, 2), 29); + /// assert_eq!(ndays_in_month(2017, 2), 28); + /// ``` + #[inline] + fn day(&self) -> u32 { + self.day() + } + + /// Returns the day of month starting from 0. + /// + /// The return value ranges from 0 to 30. (The last day of month differs by months.) + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().day0(), 7); + /// assert_eq!(NaiveDate::from_ymd_opt(-308, 3, 14).unwrap().day0(), 13); + /// ``` + #[inline] + fn day0(&self) -> u32 { + self.mdf().day() - 1 + } + + /// Returns the day of year starting from 1. + /// + /// The return value ranges from 1 to 366. (The last day of year differs by years.) + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().ordinal(), 251); + /// assert_eq!(NaiveDate::from_ymd_opt(-308, 3, 14).unwrap().ordinal(), 74); + /// ``` + /// + /// Combined with [`NaiveDate::pred_opt`](#method.pred_opt), + /// one can determine the number of days in a particular year. + /// (Note that this panics when `year` is out of range.) + /// + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// fn ndays_in_year(year: i32) -> u32 { + /// // the first day of the next year... + /// let d = NaiveDate::from_ymd_opt(year + 1, 1, 1).unwrap(); + /// + /// // ...is preceded by the last day of the original year + /// d.pred_opt().unwrap().ordinal() + /// } + /// + /// assert_eq!(ndays_in_year(2015), 365); + /// assert_eq!(ndays_in_year(2016), 366); + /// assert_eq!(ndays_in_year(2017), 365); + /// assert_eq!(ndays_in_year(2000), 366); + /// assert_eq!(ndays_in_year(2100), 365); + /// ``` + #[inline] + fn ordinal(&self) -> u32 { + ((self.yof() & ORDINAL_MASK) >> 4) as u32 + } + + /// Returns the day of year starting from 0. + /// + /// The return value ranges from 0 to 365. (The last day of year differs by years.) + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().ordinal0(), 250); + /// assert_eq!(NaiveDate::from_ymd_opt(-308, 3, 14).unwrap().ordinal0(), 73); + /// ``` + #[inline] + fn ordinal0(&self) -> u32 { + self.ordinal() - 1 + } + + /// Returns the day of week. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate, Weekday}; + /// + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().weekday(), Weekday::Tue); + /// assert_eq!(NaiveDate::from_ymd_opt(-308, 3, 14).unwrap().weekday(), Weekday::Fri); + /// ``` + #[inline] + fn weekday(&self) -> Weekday { + self.weekday() + } + + #[inline] + fn iso_week(&self) -> IsoWeek { + IsoWeek::from_yof(self.year(), self.ordinal(), self.year_flags()) + } + + /// Makes a new `NaiveDate` with the year number changed, while keeping the same month and day. + /// + /// This method assumes you want to work on the date as a year-month-day value. Don't use it if + /// you want the ordinal to stay the same after changing the year, of if you want the week and + /// weekday values to stay the same. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist (February 29 in a non-leap year). + /// - The year is out of range for a `NaiveDate`. + /// + /// # Examples + /// + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_year(2016), + /// Some(NaiveDate::from_ymd_opt(2016, 9, 8).unwrap()) + /// ); + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_year(-308), + /// Some(NaiveDate::from_ymd_opt(-308, 9, 8).unwrap()) + /// ); + /// ``` + /// + /// A leap day (February 29) is a case where this method can return `None`. + /// + /// ``` + /// # use chrono::{NaiveDate, Datelike}; + /// assert!(NaiveDate::from_ymd_opt(2016, 2, 29).unwrap().with_year(2015).is_none()); + /// assert!(NaiveDate::from_ymd_opt(2016, 2, 29).unwrap().with_year(2020).is_some()); + /// ``` + /// + /// Don't use `with_year` if you want the ordinal date to stay the same: + /// + /// ``` + /// # use chrono::{Datelike, NaiveDate}; + /// assert_ne!( + /// NaiveDate::from_yo_opt(2020, 100).unwrap().with_year(2023).unwrap(), + /// NaiveDate::from_yo_opt(2023, 100).unwrap() // result is 2023-101 + /// ); + /// ``` + #[inline] + fn with_year(&self, year: i32) -> Option { + // we need to operate with `mdf` since we should keep the month and day number as is + let mdf = self.mdf(); + + // adjust the flags as needed + let flags = YearFlags::from_year(year); + let mdf = mdf.with_flags(flags); + + NaiveDate::from_mdf(year, mdf) + } + + /// Makes a new `NaiveDate` with the month number (starting from 1) changed. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist (for example `month(4)` when day of the month is 31). + /// - The value for `month` is invalid. + /// + /// # Examples + /// + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_month(10), + /// Some(NaiveDate::from_ymd_opt(2015, 10, 8).unwrap()) + /// ); + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_month(13), None); // No month 13 + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 30).unwrap().with_month(2), None); // No Feb 30 + /// ``` + /// + /// Don't combine multiple `Datelike::with_*` methods. The intermediate value may not exist. + /// + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// fn with_year_month(date: NaiveDate, year: i32, month: u32) -> Option { + /// date.with_year(year)?.with_month(month) + /// } + /// let d = NaiveDate::from_ymd_opt(2020, 2, 29).unwrap(); + /// assert!(with_year_month(d, 2019, 1).is_none()); // fails because of invalid intermediate value + /// + /// // Correct version: + /// fn with_year_month_fixed(date: NaiveDate, year: i32, month: u32) -> Option { + /// NaiveDate::from_ymd_opt(year, month, date.day()) + /// } + /// let d = NaiveDate::from_ymd_opt(2020, 2, 29).unwrap(); + /// assert_eq!(with_year_month_fixed(d, 2019, 1), NaiveDate::from_ymd_opt(2019, 1, 29)); + /// ``` + #[inline] + fn with_month(&self, month: u32) -> Option { + self.with_mdf(self.mdf().with_month(month)?) + } + + /// Makes a new `NaiveDate` with the month number (starting from 0) changed. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist (for example `month0(3)` when day of the month is 31). + /// - The value for `month0` is invalid. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_month0(9), + /// Some(NaiveDate::from_ymd_opt(2015, 10, 8).unwrap()) + /// ); + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_month0(12), None); // No month 12 + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 30).unwrap().with_month0(1), None); // No Feb 30 + /// ``` + #[inline] + fn with_month0(&self, month0: u32) -> Option { + let month = month0.checked_add(1)?; + self.with_mdf(self.mdf().with_month(month)?) + } + + /// Makes a new `NaiveDate` with the day of month (starting from 1) changed. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist (for example `day(31)` in April). + /// - The value for `day` is invalid. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_day(30), + /// Some(NaiveDate::from_ymd_opt(2015, 9, 30).unwrap()) + /// ); + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_day(31), None); + /// // no September 31 + /// ``` + #[inline] + fn with_day(&self, day: u32) -> Option { + self.with_mdf(self.mdf().with_day(day)?) + } + + /// Makes a new `NaiveDate` with the day of month (starting from 0) changed. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist (for example `day(30)` in April). + /// - The value for `day0` is invalid. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_day0(29), + /// Some(NaiveDate::from_ymd_opt(2015, 9, 30).unwrap()) + /// ); + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().with_day0(30), None); + /// // no September 31 + /// ``` + #[inline] + fn with_day0(&self, day0: u32) -> Option { + let day = day0.checked_add(1)?; + self.with_mdf(self.mdf().with_day(day)?) + } + + /// Makes a new `NaiveDate` with the day of year (starting from 1) changed. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist (`with_ordinal(366)` in a non-leap year). + /// - The value for `ordinal` is invalid. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, Datelike}; + /// + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 1, 1).unwrap().with_ordinal(60), + /// Some(NaiveDate::from_ymd_opt(2015, 3, 1).unwrap())); + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 1, 1).unwrap().with_ordinal(366), + /// None); // 2015 had only 365 days + /// + /// assert_eq!(NaiveDate::from_ymd_opt(2016, 1, 1).unwrap().with_ordinal(60), + /// Some(NaiveDate::from_ymd_opt(2016, 2, 29).unwrap())); + /// assert_eq!(NaiveDate::from_ymd_opt(2016, 1, 1).unwrap().with_ordinal(366), + /// Some(NaiveDate::from_ymd_opt(2016, 12, 31).unwrap())); + /// ``` + #[inline] + fn with_ordinal(&self, ordinal: u32) -> Option { + if ordinal == 0 || ordinal > 366 { + return None; + } + let yof = (self.yof() & !ORDINAL_MASK) | (ordinal << 4) as i32; + match yof & OL_MASK <= MAX_OL { + true => Some(NaiveDate::from_yof(yof)), + false => None, // Does not exist: Ordinal 366 in a common year. + } + } + + /// Makes a new `NaiveDate` with the day of year (starting from 0) changed. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist (`with_ordinal0(365)` in a non-leap year). + /// - The value for `ordinal0` is invalid. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, Datelike}; + /// + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 1, 1).unwrap().with_ordinal0(59), + /// Some(NaiveDate::from_ymd_opt(2015, 3, 1).unwrap())); + /// assert_eq!(NaiveDate::from_ymd_opt(2015, 1, 1).unwrap().with_ordinal0(365), + /// None); // 2015 had only 365 days + /// + /// assert_eq!(NaiveDate::from_ymd_opt(2016, 1, 1).unwrap().with_ordinal0(59), + /// Some(NaiveDate::from_ymd_opt(2016, 2, 29).unwrap())); + /// assert_eq!(NaiveDate::from_ymd_opt(2016, 1, 1).unwrap().with_ordinal0(365), + /// Some(NaiveDate::from_ymd_opt(2016, 12, 31).unwrap())); + /// ``` + #[inline] + fn with_ordinal0(&self, ordinal0: u32) -> Option { + let ordinal = ordinal0.checked_add(1)?; + self.with_ordinal(ordinal) + } +} + +/// Add `TimeDelta` to `NaiveDate`. +/// +/// This discards the fractional days in `TimeDelta`, rounding to the closest integral number of +/// days towards `TimeDelta::zero()`. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDate::checked_add_signed`] to get an `Option` instead. +/// +/// # Example +/// +/// ``` +/// use chrono::{NaiveDate, TimeDelta}; +/// +/// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); +/// +/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::zero(), from_ymd(2014, 1, 1)); +/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::try_seconds(86399).unwrap(), from_ymd(2014, 1, 1)); +/// assert_eq!( +/// from_ymd(2014, 1, 1) + TimeDelta::try_seconds(-86399).unwrap(), +/// from_ymd(2014, 1, 1) +/// ); +/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::try_days(1).unwrap(), from_ymd(2014, 1, 2)); +/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::try_days(-1).unwrap(), from_ymd(2013, 12, 31)); +/// assert_eq!(from_ymd(2014, 1, 1) + TimeDelta::try_days(364).unwrap(), from_ymd(2014, 12, 31)); +/// assert_eq!( +/// from_ymd(2014, 1, 1) + TimeDelta::try_days(365 * 4 + 1).unwrap(), +/// from_ymd(2018, 1, 1) +/// ); +/// assert_eq!( +/// from_ymd(2014, 1, 1) + TimeDelta::try_days(365 * 400 + 97).unwrap(), +/// from_ymd(2414, 1, 1) +/// ); +/// ``` +/// +/// [`NaiveDate::checked_add_signed`]: crate::NaiveDate::checked_add_signed +impl Add for NaiveDate { + type Output = NaiveDate; + + #[inline] + fn add(self, rhs: TimeDelta) -> NaiveDate { + self.checked_add_signed(rhs).expect("`NaiveDate + TimeDelta` overflowed") + } +} + +/// Add-assign of `TimeDelta` to `NaiveDate`. +/// +/// This discards the fractional days in `TimeDelta`, rounding to the closest integral number of days +/// towards `TimeDelta::zero()`. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDate::checked_add_signed`] to get an `Option` instead. +impl AddAssign for NaiveDate { + #[inline] + fn add_assign(&mut self, rhs: TimeDelta) { + *self = self.add(rhs); + } +} + +/// Add `Months` to `NaiveDate`. +/// +/// The result will be clamped to valid days in the resulting month, see `checked_add_months` for +/// details. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using `NaiveDate::checked_add_months` to get an `Option` instead. +/// +/// # Example +/// +/// ``` +/// use chrono::{Months, NaiveDate}; +/// +/// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); +/// +/// assert_eq!(from_ymd(2014, 1, 1) + Months::new(1), from_ymd(2014, 2, 1)); +/// assert_eq!(from_ymd(2014, 1, 1) + Months::new(11), from_ymd(2014, 12, 1)); +/// assert_eq!(from_ymd(2014, 1, 1) + Months::new(12), from_ymd(2015, 1, 1)); +/// assert_eq!(from_ymd(2014, 1, 1) + Months::new(13), from_ymd(2015, 2, 1)); +/// assert_eq!(from_ymd(2014, 1, 31) + Months::new(1), from_ymd(2014, 2, 28)); +/// assert_eq!(from_ymd(2020, 1, 31) + Months::new(1), from_ymd(2020, 2, 29)); +/// ``` +impl Add for NaiveDate { + type Output = NaiveDate; + + fn add(self, months: Months) -> Self::Output { + self.checked_add_months(months).expect("`NaiveDate + Months` out of range") + } +} + +/// Subtract `Months` from `NaiveDate`. +/// +/// The result will be clamped to valid days in the resulting month, see `checked_sub_months` for +/// details. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using `NaiveDate::checked_sub_months` to get an `Option` instead. +/// +/// # Example +/// +/// ``` +/// use chrono::{Months, NaiveDate}; +/// +/// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); +/// +/// assert_eq!(from_ymd(2014, 1, 1) - Months::new(11), from_ymd(2013, 2, 1)); +/// assert_eq!(from_ymd(2014, 1, 1) - Months::new(12), from_ymd(2013, 1, 1)); +/// assert_eq!(from_ymd(2014, 1, 1) - Months::new(13), from_ymd(2012, 12, 1)); +/// ``` +impl Sub for NaiveDate { + type Output = NaiveDate; + + fn sub(self, months: Months) -> Self::Output { + self.checked_sub_months(months).expect("`NaiveDate - Months` out of range") + } +} + +/// Add `Days` to `NaiveDate`. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using `NaiveDate::checked_add_days` to get an `Option` instead. +impl Add for NaiveDate { + type Output = NaiveDate; + + fn add(self, days: Days) -> Self::Output { + self.checked_add_days(days).expect("`NaiveDate + Days` out of range") + } +} + +/// Subtract `Days` from `NaiveDate`. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using `NaiveDate::checked_sub_days` to get an `Option` instead. +impl Sub for NaiveDate { + type Output = NaiveDate; + + fn sub(self, days: Days) -> Self::Output { + self.checked_sub_days(days).expect("`NaiveDate - Days` out of range") + } +} + +/// Subtract `TimeDelta` from `NaiveDate`. +/// +/// This discards the fractional days in `TimeDelta`, rounding to the closest integral number of +/// days towards `TimeDelta::zero()`. +/// It is the same as the addition with a negated `TimeDelta`. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDate::checked_sub_signed`] to get an `Option` instead. +/// +/// # Example +/// +/// ``` +/// use chrono::{NaiveDate, TimeDelta}; +/// +/// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); +/// +/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::zero(), from_ymd(2014, 1, 1)); +/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::try_seconds(86399).unwrap(), from_ymd(2014, 1, 1)); +/// assert_eq!( +/// from_ymd(2014, 1, 1) - TimeDelta::try_seconds(-86399).unwrap(), +/// from_ymd(2014, 1, 1) +/// ); +/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::try_days(1).unwrap(), from_ymd(2013, 12, 31)); +/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::try_days(-1).unwrap(), from_ymd(2014, 1, 2)); +/// assert_eq!(from_ymd(2014, 1, 1) - TimeDelta::try_days(364).unwrap(), from_ymd(2013, 1, 2)); +/// assert_eq!( +/// from_ymd(2014, 1, 1) - TimeDelta::try_days(365 * 4 + 1).unwrap(), +/// from_ymd(2010, 1, 1) +/// ); +/// assert_eq!( +/// from_ymd(2014, 1, 1) - TimeDelta::try_days(365 * 400 + 97).unwrap(), +/// from_ymd(1614, 1, 1) +/// ); +/// ``` +/// +/// [`NaiveDate::checked_sub_signed`]: crate::NaiveDate::checked_sub_signed +impl Sub for NaiveDate { + type Output = NaiveDate; + + #[inline] + fn sub(self, rhs: TimeDelta) -> NaiveDate { + self.checked_sub_signed(rhs).expect("`NaiveDate - TimeDelta` overflowed") + } +} + +/// Subtract-assign `TimeDelta` from `NaiveDate`. +/// +/// This discards the fractional days in `TimeDelta`, rounding to the closest integral number of +/// days towards `TimeDelta::zero()`. +/// It is the same as the addition with a negated `TimeDelta`. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDate::checked_sub_signed`] to get an `Option` instead. +impl SubAssign for NaiveDate { + #[inline] + fn sub_assign(&mut self, rhs: TimeDelta) { + *self = self.sub(rhs); + } +} + +/// Subtracts another `NaiveDate` from the current date. +/// Returns a `TimeDelta` of integral numbers. +/// +/// This does not overflow or underflow at all, +/// as all possible output fits in the range of `TimeDelta`. +/// +/// The implementation is a wrapper around +/// [`NaiveDate::signed_duration_since`](#method.signed_duration_since). +/// +/// # Example +/// +/// ``` +/// use chrono::{NaiveDate, TimeDelta}; +/// +/// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); +/// +/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2014, 1, 1), TimeDelta::zero()); +/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2013, 12, 31), TimeDelta::try_days(1).unwrap()); +/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2014, 1, 2), TimeDelta::try_days(-1).unwrap()); +/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2013, 9, 23), TimeDelta::try_days(100).unwrap()); +/// assert_eq!(from_ymd(2014, 1, 1) - from_ymd(2013, 1, 1), TimeDelta::try_days(365).unwrap()); +/// assert_eq!( +/// from_ymd(2014, 1, 1) - from_ymd(2010, 1, 1), +/// TimeDelta::try_days(365 * 4 + 1).unwrap() +/// ); +/// assert_eq!( +/// from_ymd(2014, 1, 1) - from_ymd(1614, 1, 1), +/// TimeDelta::try_days(365 * 400 + 97).unwrap() +/// ); +/// ``` +impl Sub for NaiveDate { + type Output = TimeDelta; + + #[inline] + fn sub(self, rhs: NaiveDate) -> TimeDelta { + self.signed_duration_since(rhs) + } +} + +impl From for NaiveDate { + fn from(naive_datetime: NaiveDateTime) -> Self { + naive_datetime.date() + } +} + +/// Iterator over `NaiveDate` with a step size of one day. +#[derive(Debug, Copy, Clone, Hash, PartialEq, PartialOrd, Eq, Ord)] +pub struct NaiveDateDaysIterator { + value: NaiveDate, +} + +impl Iterator for NaiveDateDaysIterator { + type Item = NaiveDate; + + fn next(&mut self) -> Option { + // We return the current value, and have no way to return `NaiveDate::MAX`. + let current = self.value; + // This can't panic because current is < NaiveDate::MAX: + self.value = current.succ_opt()?; + Some(current) + } + + fn size_hint(&self) -> (usize, Option) { + let exact_size = NaiveDate::MAX.signed_duration_since(self.value).num_days(); + (exact_size as usize, Some(exact_size as usize)) + } +} + +impl ExactSizeIterator for NaiveDateDaysIterator {} + +impl DoubleEndedIterator for NaiveDateDaysIterator { + fn next_back(&mut self) -> Option { + // We return the current value, and have no way to return `NaiveDate::MIN`. + let current = self.value; + self.value = current.pred_opt()?; + Some(current) + } +} + +impl FusedIterator for NaiveDateDaysIterator {} + +/// Iterator over `NaiveDate` with a step size of one week. +#[derive(Debug, Copy, Clone, Hash, PartialEq, PartialOrd, Eq, Ord)] +pub struct NaiveDateWeeksIterator { + value: NaiveDate, +} + +impl Iterator for NaiveDateWeeksIterator { + type Item = NaiveDate; + + fn next(&mut self) -> Option { + let current = self.value; + self.value = current.checked_add_days(Days::new(7))?; + Some(current) + } + + fn size_hint(&self) -> (usize, Option) { + let exact_size = NaiveDate::MAX.signed_duration_since(self.value).num_weeks(); + (exact_size as usize, Some(exact_size as usize)) + } +} + +impl ExactSizeIterator for NaiveDateWeeksIterator {} + +impl DoubleEndedIterator for NaiveDateWeeksIterator { + fn next_back(&mut self) -> Option { + let current = self.value; + self.value = current.checked_sub_days(Days::new(7))?; + Some(current) + } +} + +impl FusedIterator for NaiveDateWeeksIterator {} + +/// The `Debug` output of the naive date `d` is the same as +/// [`d.format("%Y-%m-%d")`](crate::format::strftime). +/// +/// The string printed can be readily parsed via the `parse` method on `str`. +/// +/// # Example +/// +/// ``` +/// use chrono::NaiveDate; +/// +/// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(2015, 9, 5).unwrap()), "2015-09-05"); +/// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(0, 1, 1).unwrap()), "0000-01-01"); +/// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(9999, 12, 31).unwrap()), "9999-12-31"); +/// ``` +/// +/// ISO 8601 requires an explicit sign for years before 1 BCE or after 9999 CE. +/// +/// ``` +/// # use chrono::NaiveDate; +/// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(-1, 1, 1).unwrap()), "-0001-01-01"); +/// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(10000, 12, 31).unwrap()), "+10000-12-31"); +/// ``` +impl fmt::Debug for NaiveDate { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use core::fmt::Write; + + let year = self.year(); + let mdf = self.mdf(); + if (0..=9999).contains(&year) { + write_hundreds(f, (year / 100) as u8)?; + write_hundreds(f, (year % 100) as u8)?; + } else { + // ISO 8601 requires the explicit sign for out-of-range years + write!(f, "{:+05}", year)?; + } + + f.write_char('-')?; + write_hundreds(f, mdf.month() as u8)?; + f.write_char('-')?; + write_hundreds(f, mdf.day() as u8) + } +} + +/// The `Display` output of the naive date `d` is the same as +/// [`d.format("%Y-%m-%d")`](crate::format::strftime). +/// +/// The string printed can be readily parsed via the `parse` method on `str`. +/// +/// # Example +/// +/// ``` +/// use chrono::NaiveDate; +/// +/// assert_eq!(format!("{}", NaiveDate::from_ymd_opt(2015, 9, 5).unwrap()), "2015-09-05"); +/// assert_eq!(format!("{}", NaiveDate::from_ymd_opt(0, 1, 1).unwrap()), "0000-01-01"); +/// assert_eq!(format!("{}", NaiveDate::from_ymd_opt(9999, 12, 31).unwrap()), "9999-12-31"); +/// ``` +/// +/// ISO 8601 requires an explicit sign for years before 1 BCE or after 9999 CE. +/// +/// ``` +/// # use chrono::NaiveDate; +/// assert_eq!(format!("{}", NaiveDate::from_ymd_opt(-1, 1, 1).unwrap()), "-0001-01-01"); +/// assert_eq!(format!("{}", NaiveDate::from_ymd_opt(10000, 12, 31).unwrap()), "+10000-12-31"); +/// ``` +impl fmt::Display for NaiveDate { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(self, f) + } +} + +/// Parsing a `str` into a `NaiveDate` uses the same format, +/// [`%Y-%m-%d`](crate::format::strftime), as in `Debug` and `Display`. +/// +/// # Example +/// +/// ``` +/// use chrono::NaiveDate; +/// +/// let d = NaiveDate::from_ymd_opt(2015, 9, 18).unwrap(); +/// assert_eq!("2015-09-18".parse::(), Ok(d)); +/// +/// let d = NaiveDate::from_ymd_opt(12345, 6, 7).unwrap(); +/// assert_eq!("+12345-6-7".parse::(), Ok(d)); +/// +/// assert!("foo".parse::().is_err()); +/// ``` +impl str::FromStr for NaiveDate { + type Err = ParseError; + + fn from_str(s: &str) -> ParseResult { + const ITEMS: &[Item<'static>] = &[ + Item::Numeric(Numeric::Year, Pad::Zero), + Item::Space(""), + Item::Literal("-"), + Item::Numeric(Numeric::Month, Pad::Zero), + Item::Space(""), + Item::Literal("-"), + Item::Numeric(Numeric::Day, Pad::Zero), + Item::Space(""), + ]; + + let mut parsed = Parsed::new(); + parse(&mut parsed, s, ITEMS.iter())?; + parsed.to_naive_date() + } +} + +/// The default value for a NaiveDate is 1st of January 1970. +/// +/// # Example +/// +/// ```rust +/// use chrono::NaiveDate; +/// +/// let default_date = NaiveDate::default(); +/// assert_eq!(default_date, NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()); +/// ``` +impl Default for NaiveDate { + fn default() -> Self { + NaiveDate::from_ymd_opt(1970, 1, 1).unwrap() + } +} + +const fn cycle_to_yo(cycle: u32) -> (u32, u32) { + let mut year_mod_400 = cycle / 365; + let mut ordinal0 = cycle % 365; + let delta = YEAR_DELTAS[year_mod_400 as usize] as u32; + if ordinal0 < delta { + year_mod_400 -= 1; + ordinal0 += 365 - YEAR_DELTAS[year_mod_400 as usize] as u32; + } else { + ordinal0 -= delta; + } + (year_mod_400, ordinal0 + 1) +} + +const fn yo_to_cycle(year_mod_400: u32, ordinal: u32) -> u32 { + year_mod_400 * 365 + YEAR_DELTAS[year_mod_400 as usize] as u32 + ordinal - 1 +} + +const fn div_mod_floor(val: i32, div: i32) -> (i32, i32) { + (val.div_euclid(div), val.rem_euclid(div)) +} + +/// MAX_YEAR is one year less than the type is capable of representing. Internally we may sometimes +/// use the headroom, notably to handle cases where the offset of a `DateTime` constructed with +/// `NaiveDate::MAX` pushes it beyond the valid, representable range. +pub(super) const MAX_YEAR: i32 = (i32::MAX >> 13) - 1; + +/// MIN_YEAR is one year more than the type is capable of representing. Internally we may sometimes +/// use the headroom, notably to handle cases where the offset of a `DateTime` constructed with +/// `NaiveDate::MIN` pushes it beyond the valid, representable range. +pub(super) const MIN_YEAR: i32 = (i32::MIN >> 13) + 1; + +const ORDINAL_MASK: i32 = 0b1_1111_1111_0000; + +const LEAP_YEAR_MASK: i32 = 0b1000; + +// OL: ordinal and leap year flag. +// With only these parts of the date an ordinal 366 in a common year would be encoded as +// `((366 << 1) | 1) << 3`, and in a leap year as `((366 << 1) | 0) << 3`, which is less. +// This allows for efficiently checking the ordinal exists depending on whether this is a leap year. +const OL_MASK: i32 = ORDINAL_MASK | LEAP_YEAR_MASK; +const MAX_OL: i32 = 366 << 4; + +// Weekday of the last day in the preceding year. +// Allows for quick day of week calculation from the 1-based ordinal. +const WEEKDAY_FLAGS_MASK: i32 = 0b111; + +const YEAR_FLAGS_MASK: i32 = LEAP_YEAR_MASK | WEEKDAY_FLAGS_MASK; + +const YEAR_DELTAS: &[u8; 401] = &[ + 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, + 8, 9, 9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 11, 12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, + 15, 15, 15, 15, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19, 20, 20, 20, 20, + 21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 24, 24, 24, 24, 25, 25, 25, // 100 + 25, 25, 25, 25, 25, 26, 26, 26, 26, 27, 27, 27, 27, 28, 28, 28, 28, 29, 29, 29, 29, 30, 30, 30, + 30, 31, 31, 31, 31, 32, 32, 32, 32, 33, 33, 33, 33, 34, 34, 34, 34, 35, 35, 35, 35, 36, 36, 36, + 36, 37, 37, 37, 37, 38, 38, 38, 38, 39, 39, 39, 39, 40, 40, 40, 40, 41, 41, 41, 41, 42, 42, 42, + 42, 43, 43, 43, 43, 44, 44, 44, 44, 45, 45, 45, 45, 46, 46, 46, 46, 47, 47, 47, 47, 48, 48, 48, + 48, 49, 49, 49, // 200 + 49, 49, 49, 49, 49, 50, 50, 50, 50, 51, 51, 51, 51, 52, 52, 52, 52, 53, 53, 53, 53, 54, 54, 54, + 54, 55, 55, 55, 55, 56, 56, 56, 56, 57, 57, 57, 57, 58, 58, 58, 58, 59, 59, 59, 59, 60, 60, 60, + 60, 61, 61, 61, 61, 62, 62, 62, 62, 63, 63, 63, 63, 64, 64, 64, 64, 65, 65, 65, 65, 66, 66, 66, + 66, 67, 67, 67, 67, 68, 68, 68, 68, 69, 69, 69, 69, 70, 70, 70, 70, 71, 71, 71, 71, 72, 72, 72, + 72, 73, 73, 73, // 300 + 73, 73, 73, 73, 73, 74, 74, 74, 74, 75, 75, 75, 75, 76, 76, 76, 76, 77, 77, 77, 77, 78, 78, 78, + 78, 79, 79, 79, 79, 80, 80, 80, 80, 81, 81, 81, 81, 82, 82, 82, 82, 83, 83, 83, 83, 84, 84, 84, + 84, 85, 85, 85, 85, 86, 86, 86, 86, 87, 87, 87, 87, 88, 88, 88, 88, 89, 89, 89, 89, 90, 90, 90, + 90, 91, 91, 91, 91, 92, 92, 92, 92, 93, 93, 93, 93, 94, 94, 94, 94, 95, 95, 95, 95, 96, 96, 96, + 96, 97, 97, 97, 97, // 400+1 +]; + +#[cfg(feature = "serde")] +mod serde { + use super::NaiveDate; + use core::fmt; + use serde::{de, ser}; + + // TODO not very optimized for space (binary formats would want something better) + + impl ser::Serialize for NaiveDate { + fn serialize(&self, serializer: S) -> Result + where + S: ser::Serializer, + { + struct FormatWrapped<'a, D: 'a> { + inner: &'a D, + } + + impl fmt::Display for FormatWrapped<'_, D> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.inner.fmt(f) + } + } + + serializer.collect_str(&FormatWrapped { inner: &self }) + } + } + + struct NaiveDateVisitor; + + impl de::Visitor<'_> for NaiveDateVisitor { + type Value = NaiveDate; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a formatted date string") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + value.parse().map_err(E::custom) + } + } + + impl<'de> de::Deserialize<'de> for NaiveDate { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + deserializer.deserialize_str(NaiveDateVisitor) + } + } + + #[cfg(test)] + mod tests { + use crate::NaiveDate; + + #[test] + fn test_serde_serialize() { + assert_eq!( + serde_json::to_string(&NaiveDate::from_ymd_opt(2014, 7, 24).unwrap()).ok(), + Some(r#""2014-07-24""#.into()) + ); + assert_eq!( + serde_json::to_string(&NaiveDate::from_ymd_opt(0, 1, 1).unwrap()).ok(), + Some(r#""0000-01-01""#.into()) + ); + assert_eq!( + serde_json::to_string(&NaiveDate::from_ymd_opt(-1, 12, 31).unwrap()).ok(), + Some(r#""-0001-12-31""#.into()) + ); + assert_eq!( + serde_json::to_string(&NaiveDate::MIN).ok(), + Some(r#""-262143-01-01""#.into()) + ); + assert_eq!( + serde_json::to_string(&NaiveDate::MAX).ok(), + Some(r#""+262142-12-31""#.into()) + ); + } + + #[test] + fn test_serde_deserialize() { + let from_str = serde_json::from_str::; + + assert_eq!( + from_str(r#""2016-07-08""#).ok(), + Some(NaiveDate::from_ymd_opt(2016, 7, 8).unwrap()) + ); + assert_eq!( + from_str(r#""2016-7-8""#).ok(), + Some(NaiveDate::from_ymd_opt(2016, 7, 8).unwrap()) + ); + assert_eq!(from_str(r#""+002016-07-08""#).ok(), NaiveDate::from_ymd_opt(2016, 7, 8)); + assert_eq!( + from_str(r#""0000-01-01""#).ok(), + Some(NaiveDate::from_ymd_opt(0, 1, 1).unwrap()) + ); + assert_eq!( + from_str(r#""0-1-1""#).ok(), + Some(NaiveDate::from_ymd_opt(0, 1, 1).unwrap()) + ); + assert_eq!( + from_str(r#""-0001-12-31""#).ok(), + Some(NaiveDate::from_ymd_opt(-1, 12, 31).unwrap()) + ); + assert_eq!(from_str(r#""-262143-01-01""#).ok(), Some(NaiveDate::MIN)); + assert_eq!(from_str(r#""+262142-12-31""#).ok(), Some(NaiveDate::MAX)); + + // bad formats + assert!(from_str(r#""""#).is_err()); + assert!(from_str(r#""20001231""#).is_err()); + assert!(from_str(r#""2000-00-00""#).is_err()); + assert!(from_str(r#""2000-02-30""#).is_err()); + assert!(from_str(r#""2001-02-29""#).is_err()); + assert!(from_str(r#""2002-002-28""#).is_err()); + assert!(from_str(r#""yyyy-mm-dd""#).is_err()); + assert!(from_str(r#"0"#).is_err()); + assert!(from_str(r#"20.01"#).is_err()); + let min = i32::MIN.to_string(); + assert!(from_str(&min).is_err()); + let max = i32::MAX.to_string(); + assert!(from_str(&max).is_err()); + let min = i64::MIN.to_string(); + assert!(from_str(&min).is_err()); + let max = i64::MAX.to_string(); + assert!(from_str(&max).is_err()); + assert!(from_str(r#"{}"#).is_err()); + } + + #[test] + fn test_serde_bincode() { + // Bincode is relevant to test separately from JSON because + // it is not self-describing. + use bincode::{deserialize, serialize}; + + let d = NaiveDate::from_ymd_opt(2014, 7, 24).unwrap(); + let encoded = serialize(&d).unwrap(); + let decoded: NaiveDate = deserialize(&encoded).unwrap(); + assert_eq!(d, decoded); + } + } +} diff --git a/third_party/rust/chrono/src/naive/date/tests.rs b/third_party/rust/chrono/src/naive/date/tests.rs new file mode 100644 index 00000000000..35c9da24e7e --- /dev/null +++ b/third_party/rust/chrono/src/naive/date/tests.rs @@ -0,0 +1,879 @@ +use super::{Days, MAX_YEAR, MIN_YEAR, Months, NaiveDate}; +use crate::naive::internals::{A, AG, B, BA, C, CB, D, DC, E, ED, F, FE, G, GF, YearFlags}; +use crate::{Datelike, TimeDelta, Weekday}; + +// as it is hard to verify year flags in `NaiveDate::MIN` and `NaiveDate::MAX`, +// we use a separate run-time test. +#[test] +fn test_date_bounds() { + let calculated_min = NaiveDate::from_ymd_opt(MIN_YEAR, 1, 1).unwrap(); + let calculated_max = NaiveDate::from_ymd_opt(MAX_YEAR, 12, 31).unwrap(); + assert!( + NaiveDate::MIN == calculated_min, + "`NaiveDate::MIN` should have year flag {:?}", + calculated_min.year_flags() + ); + assert!( + NaiveDate::MAX == calculated_max, + "`NaiveDate::MAX` should have year flag {:?} and ordinal {}", + calculated_max.year_flags(), + calculated_max.ordinal() + ); + + // let's also check that the entire range do not exceed 2^44 seconds + // (sometimes used for bounding `TimeDelta` against overflow) + let maxsecs = NaiveDate::MAX.signed_duration_since(NaiveDate::MIN).num_seconds(); + let maxsecs = maxsecs + 86401; // also take care of DateTime + assert!( + maxsecs < (1 << MAX_BITS), + "The entire `NaiveDate` range somehow exceeds 2^{} seconds", + MAX_BITS + ); + + const BEFORE_MIN: NaiveDate = NaiveDate::BEFORE_MIN; + assert_eq!(BEFORE_MIN.year_flags(), YearFlags::from_year(BEFORE_MIN.year())); + assert_eq!((BEFORE_MIN.month(), BEFORE_MIN.day()), (12, 31)); + + const AFTER_MAX: NaiveDate = NaiveDate::AFTER_MAX; + assert_eq!(AFTER_MAX.year_flags(), YearFlags::from_year(AFTER_MAX.year())); + assert_eq!((AFTER_MAX.month(), AFTER_MAX.day()), (1, 1)); +} + +#[test] +fn diff_months() { + // identity + assert_eq!( + NaiveDate::from_ymd_opt(2022, 8, 3).unwrap().checked_add_months(Months::new(0)), + Some(NaiveDate::from_ymd_opt(2022, 8, 3).unwrap()) + ); + + // add with months exceeding `i32::MAX` + assert_eq!( + NaiveDate::from_ymd_opt(2022, 8, 3) + .unwrap() + .checked_add_months(Months::new(i32::MAX as u32 + 1)), + None + ); + + // sub with months exceeding `i32::MIN` + assert_eq!( + NaiveDate::from_ymd_opt(2022, 8, 3) + .unwrap() + .checked_sub_months(Months::new(i32::MIN.unsigned_abs() + 1)), + None + ); + + // add overflowing year + assert_eq!(NaiveDate::MAX.checked_add_months(Months::new(1)), None); + + // add underflowing year + assert_eq!(NaiveDate::MIN.checked_sub_months(Months::new(1)), None); + + // sub crossing year 0 boundary + assert_eq!( + NaiveDate::from_ymd_opt(2022, 8, 3).unwrap().checked_sub_months(Months::new(2050 * 12)), + Some(NaiveDate::from_ymd_opt(-28, 8, 3).unwrap()) + ); + + // add crossing year boundary + assert_eq!( + NaiveDate::from_ymd_opt(2022, 8, 3).unwrap().checked_add_months(Months::new(6)), + Some(NaiveDate::from_ymd_opt(2023, 2, 3).unwrap()) + ); + + // sub crossing year boundary + assert_eq!( + NaiveDate::from_ymd_opt(2022, 8, 3).unwrap().checked_sub_months(Months::new(10)), + Some(NaiveDate::from_ymd_opt(2021, 10, 3).unwrap()) + ); + + // add clamping day, non-leap year + assert_eq!( + NaiveDate::from_ymd_opt(2022, 1, 29).unwrap().checked_add_months(Months::new(1)), + Some(NaiveDate::from_ymd_opt(2022, 2, 28).unwrap()) + ); + + // add to leap day + assert_eq!( + NaiveDate::from_ymd_opt(2022, 10, 29).unwrap().checked_add_months(Months::new(16)), + Some(NaiveDate::from_ymd_opt(2024, 2, 29).unwrap()) + ); + + // add into december + assert_eq!( + NaiveDate::from_ymd_opt(2022, 10, 31).unwrap().checked_add_months(Months::new(2)), + Some(NaiveDate::from_ymd_opt(2022, 12, 31).unwrap()) + ); + + // sub into december + assert_eq!( + NaiveDate::from_ymd_opt(2022, 10, 31).unwrap().checked_sub_months(Months::new(10)), + Some(NaiveDate::from_ymd_opt(2021, 12, 31).unwrap()) + ); + + // add into january + assert_eq!( + NaiveDate::from_ymd_opt(2022, 8, 3).unwrap().checked_add_months(Months::new(5)), + Some(NaiveDate::from_ymd_opt(2023, 1, 3).unwrap()) + ); + + // sub into january + assert_eq!( + NaiveDate::from_ymd_opt(2022, 8, 3).unwrap().checked_sub_months(Months::new(7)), + Some(NaiveDate::from_ymd_opt(2022, 1, 3).unwrap()) + ); +} + +#[test] +fn test_readme_doomsday() { + for y in NaiveDate::MIN.year()..=NaiveDate::MAX.year() { + // even months + let d4 = NaiveDate::from_ymd_opt(y, 4, 4).unwrap(); + let d6 = NaiveDate::from_ymd_opt(y, 6, 6).unwrap(); + let d8 = NaiveDate::from_ymd_opt(y, 8, 8).unwrap(); + let d10 = NaiveDate::from_ymd_opt(y, 10, 10).unwrap(); + let d12 = NaiveDate::from_ymd_opt(y, 12, 12).unwrap(); + + // nine to five, seven-eleven + let d59 = NaiveDate::from_ymd_opt(y, 5, 9).unwrap(); + let d95 = NaiveDate::from_ymd_opt(y, 9, 5).unwrap(); + let d711 = NaiveDate::from_ymd_opt(y, 7, 11).unwrap(); + let d117 = NaiveDate::from_ymd_opt(y, 11, 7).unwrap(); + + // "March 0" + let d30 = NaiveDate::from_ymd_opt(y, 3, 1).unwrap().pred_opt().unwrap(); + + let weekday = d30.weekday(); + let other_dates = [d4, d6, d8, d10, d12, d59, d95, d711, d117]; + assert!(other_dates.iter().all(|d| d.weekday() == weekday)); + } +} + +#[test] +fn test_date_from_ymd() { + let from_ymd = NaiveDate::from_ymd_opt; + + assert!(from_ymd(2012, 0, 1).is_none()); + assert!(from_ymd(2012, 1, 1).is_some()); + assert!(from_ymd(2012, 2, 29).is_some()); + assert!(from_ymd(2014, 2, 29).is_none()); + assert!(from_ymd(2014, 3, 0).is_none()); + assert!(from_ymd(2014, 3, 1).is_some()); + assert!(from_ymd(2014, 3, 31).is_some()); + assert!(from_ymd(2014, 3, 32).is_none()); + assert!(from_ymd(2014, 12, 31).is_some()); + assert!(from_ymd(2014, 13, 1).is_none()); +} + +#[test] +fn test_date_from_yo() { + let from_yo = NaiveDate::from_yo_opt; + let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + + assert_eq!(from_yo(2012, 0), None); + assert_eq!(from_yo(2012, 1), Some(ymd(2012, 1, 1))); + assert_eq!(from_yo(2012, 2), Some(ymd(2012, 1, 2))); + assert_eq!(from_yo(2012, 32), Some(ymd(2012, 2, 1))); + assert_eq!(from_yo(2012, 60), Some(ymd(2012, 2, 29))); + assert_eq!(from_yo(2012, 61), Some(ymd(2012, 3, 1))); + assert_eq!(from_yo(2012, 100), Some(ymd(2012, 4, 9))); + assert_eq!(from_yo(2012, 200), Some(ymd(2012, 7, 18))); + assert_eq!(from_yo(2012, 300), Some(ymd(2012, 10, 26))); + assert_eq!(from_yo(2012, 366), Some(ymd(2012, 12, 31))); + assert_eq!(from_yo(2012, 367), None); + assert_eq!(from_yo(2012, (1 << 28) | 60), None); + + assert_eq!(from_yo(2014, 0), None); + assert_eq!(from_yo(2014, 1), Some(ymd(2014, 1, 1))); + assert_eq!(from_yo(2014, 2), Some(ymd(2014, 1, 2))); + assert_eq!(from_yo(2014, 32), Some(ymd(2014, 2, 1))); + assert_eq!(from_yo(2014, 59), Some(ymd(2014, 2, 28))); + assert_eq!(from_yo(2014, 60), Some(ymd(2014, 3, 1))); + assert_eq!(from_yo(2014, 100), Some(ymd(2014, 4, 10))); + assert_eq!(from_yo(2014, 200), Some(ymd(2014, 7, 19))); + assert_eq!(from_yo(2014, 300), Some(ymd(2014, 10, 27))); + assert_eq!(from_yo(2014, 365), Some(ymd(2014, 12, 31))); + assert_eq!(from_yo(2014, 366), None); +} + +#[test] +fn test_date_from_isoywd() { + let from_isoywd = NaiveDate::from_isoywd_opt; + let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + + assert_eq!(from_isoywd(2004, 0, Weekday::Sun), None); + assert_eq!(from_isoywd(2004, 1, Weekday::Mon), Some(ymd(2003, 12, 29))); + assert_eq!(from_isoywd(2004, 1, Weekday::Sun), Some(ymd(2004, 1, 4))); + assert_eq!(from_isoywd(2004, 2, Weekday::Mon), Some(ymd(2004, 1, 5))); + assert_eq!(from_isoywd(2004, 2, Weekday::Sun), Some(ymd(2004, 1, 11))); + assert_eq!(from_isoywd(2004, 52, Weekday::Mon), Some(ymd(2004, 12, 20))); + assert_eq!(from_isoywd(2004, 52, Weekday::Sun), Some(ymd(2004, 12, 26))); + assert_eq!(from_isoywd(2004, 53, Weekday::Mon), Some(ymd(2004, 12, 27))); + assert_eq!(from_isoywd(2004, 53, Weekday::Sun), Some(ymd(2005, 1, 2))); + assert_eq!(from_isoywd(2004, 54, Weekday::Mon), None); + + assert_eq!(from_isoywd(2011, 0, Weekday::Sun), None); + assert_eq!(from_isoywd(2011, 1, Weekday::Mon), Some(ymd(2011, 1, 3))); + assert_eq!(from_isoywd(2011, 1, Weekday::Sun), Some(ymd(2011, 1, 9))); + assert_eq!(from_isoywd(2011, 2, Weekday::Mon), Some(ymd(2011, 1, 10))); + assert_eq!(from_isoywd(2011, 2, Weekday::Sun), Some(ymd(2011, 1, 16))); + + assert_eq!(from_isoywd(2018, 51, Weekday::Mon), Some(ymd(2018, 12, 17))); + assert_eq!(from_isoywd(2018, 51, Weekday::Sun), Some(ymd(2018, 12, 23))); + assert_eq!(from_isoywd(2018, 52, Weekday::Mon), Some(ymd(2018, 12, 24))); + assert_eq!(from_isoywd(2018, 52, Weekday::Sun), Some(ymd(2018, 12, 30))); + assert_eq!(from_isoywd(2018, 53, Weekday::Mon), None); +} + +#[test] +fn test_date_from_isoywd_and_iso_week() { + for year in 2000..2401 { + for week in 1..54 { + for &weekday in [ + Weekday::Mon, + Weekday::Tue, + Weekday::Wed, + Weekday::Thu, + Weekday::Fri, + Weekday::Sat, + Weekday::Sun, + ] + .iter() + { + let d = NaiveDate::from_isoywd_opt(year, week, weekday); + if let Some(d) = d { + assert_eq!(d.weekday(), weekday); + let w = d.iso_week(); + assert_eq!(w.year(), year); + assert_eq!(w.week(), week); + } + } + } + } + + for year in 2000..2401 { + for month in 1..13 { + for day in 1..32 { + let d = NaiveDate::from_ymd_opt(year, month, day); + if let Some(d) = d { + let w = d.iso_week(); + let d_ = NaiveDate::from_isoywd_opt(w.year(), w.week(), d.weekday()); + assert_eq!(d, d_.unwrap()); + } + } + } + } +} + +#[test] +fn test_date_from_num_days_from_ce() { + let from_ndays_from_ce = NaiveDate::from_num_days_from_ce_opt; + assert_eq!(from_ndays_from_ce(1), Some(NaiveDate::from_ymd_opt(1, 1, 1).unwrap())); + assert_eq!(from_ndays_from_ce(2), Some(NaiveDate::from_ymd_opt(1, 1, 2).unwrap())); + assert_eq!(from_ndays_from_ce(31), Some(NaiveDate::from_ymd_opt(1, 1, 31).unwrap())); + assert_eq!(from_ndays_from_ce(32), Some(NaiveDate::from_ymd_opt(1, 2, 1).unwrap())); + assert_eq!(from_ndays_from_ce(59), Some(NaiveDate::from_ymd_opt(1, 2, 28).unwrap())); + assert_eq!(from_ndays_from_ce(60), Some(NaiveDate::from_ymd_opt(1, 3, 1).unwrap())); + assert_eq!(from_ndays_from_ce(365), Some(NaiveDate::from_ymd_opt(1, 12, 31).unwrap())); + assert_eq!(from_ndays_from_ce(365 + 1), Some(NaiveDate::from_ymd_opt(2, 1, 1).unwrap())); + assert_eq!(from_ndays_from_ce(365 * 2 + 1), Some(NaiveDate::from_ymd_opt(3, 1, 1).unwrap())); + assert_eq!(from_ndays_from_ce(365 * 3 + 1), Some(NaiveDate::from_ymd_opt(4, 1, 1).unwrap())); + assert_eq!(from_ndays_from_ce(365 * 4 + 2), Some(NaiveDate::from_ymd_opt(5, 1, 1).unwrap())); + assert_eq!(from_ndays_from_ce(146097 + 1), Some(NaiveDate::from_ymd_opt(401, 1, 1).unwrap())); + assert_eq!( + from_ndays_from_ce(146097 * 5 + 1), + Some(NaiveDate::from_ymd_opt(2001, 1, 1).unwrap()) + ); + assert_eq!(from_ndays_from_ce(719163), Some(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap())); + assert_eq!(from_ndays_from_ce(0), Some(NaiveDate::from_ymd_opt(0, 12, 31).unwrap())); // 1 BCE + assert_eq!(from_ndays_from_ce(-365), Some(NaiveDate::from_ymd_opt(0, 1, 1).unwrap())); + assert_eq!(from_ndays_from_ce(-366), Some(NaiveDate::from_ymd_opt(-1, 12, 31).unwrap())); // 2 BCE + + for days in (-9999..10001).map(|x| x * 100) { + assert_eq!(from_ndays_from_ce(days).map(|d| d.num_days_from_ce()), Some(days)); + } + + assert_eq!(from_ndays_from_ce(NaiveDate::MIN.num_days_from_ce()), Some(NaiveDate::MIN)); + assert_eq!(from_ndays_from_ce(NaiveDate::MIN.num_days_from_ce() - 1), None); + assert_eq!(from_ndays_from_ce(NaiveDate::MAX.num_days_from_ce()), Some(NaiveDate::MAX)); + assert_eq!(from_ndays_from_ce(NaiveDate::MAX.num_days_from_ce() + 1), None); + + assert_eq!(from_ndays_from_ce(i32::MIN), None); + assert_eq!(from_ndays_from_ce(i32::MAX), None); +} + +#[test] +fn test_date_from_weekday_of_month_opt() { + let ymwd = NaiveDate::from_weekday_of_month_opt; + assert_eq!(ymwd(2018, 8, Weekday::Tue, 0), None); + assert_eq!(ymwd(2018, 8, Weekday::Wed, 1), Some(NaiveDate::from_ymd_opt(2018, 8, 1).unwrap())); + assert_eq!(ymwd(2018, 8, Weekday::Thu, 1), Some(NaiveDate::from_ymd_opt(2018, 8, 2).unwrap())); + assert_eq!(ymwd(2018, 8, Weekday::Sun, 1), Some(NaiveDate::from_ymd_opt(2018, 8, 5).unwrap())); + assert_eq!(ymwd(2018, 8, Weekday::Mon, 1), Some(NaiveDate::from_ymd_opt(2018, 8, 6).unwrap())); + assert_eq!(ymwd(2018, 8, Weekday::Tue, 1), Some(NaiveDate::from_ymd_opt(2018, 8, 7).unwrap())); + assert_eq!(ymwd(2018, 8, Weekday::Wed, 2), Some(NaiveDate::from_ymd_opt(2018, 8, 8).unwrap())); + assert_eq!(ymwd(2018, 8, Weekday::Sun, 2), Some(NaiveDate::from_ymd_opt(2018, 8, 12).unwrap())); + assert_eq!(ymwd(2018, 8, Weekday::Thu, 3), Some(NaiveDate::from_ymd_opt(2018, 8, 16).unwrap())); + assert_eq!(ymwd(2018, 8, Weekday::Thu, 4), Some(NaiveDate::from_ymd_opt(2018, 8, 23).unwrap())); + assert_eq!(ymwd(2018, 8, Weekday::Thu, 5), Some(NaiveDate::from_ymd_opt(2018, 8, 30).unwrap())); + assert_eq!(ymwd(2018, 8, Weekday::Fri, 5), Some(NaiveDate::from_ymd_opt(2018, 8, 31).unwrap())); + assert_eq!(ymwd(2018, 8, Weekday::Sat, 5), None); +} + +#[test] +fn test_date_fields() { + fn check(year: i32, month: u32, day: u32, ordinal: u32) { + let d1 = NaiveDate::from_ymd_opt(year, month, day).unwrap(); + assert_eq!(d1.year(), year); + assert_eq!(d1.month(), month); + assert_eq!(d1.day(), day); + assert_eq!(d1.ordinal(), ordinal); + + let d2 = NaiveDate::from_yo_opt(year, ordinal).unwrap(); + assert_eq!(d2.year(), year); + assert_eq!(d2.month(), month); + assert_eq!(d2.day(), day); + assert_eq!(d2.ordinal(), ordinal); + + assert_eq!(d1, d2); + } + + check(2012, 1, 1, 1); + check(2012, 1, 2, 2); + check(2012, 2, 1, 32); + check(2012, 2, 29, 60); + check(2012, 3, 1, 61); + check(2012, 4, 9, 100); + check(2012, 7, 18, 200); + check(2012, 10, 26, 300); + check(2012, 12, 31, 366); + + check(2014, 1, 1, 1); + check(2014, 1, 2, 2); + check(2014, 2, 1, 32); + check(2014, 2, 28, 59); + check(2014, 3, 1, 60); + check(2014, 4, 10, 100); + check(2014, 7, 19, 200); + check(2014, 10, 27, 300); + check(2014, 12, 31, 365); +} + +#[test] +fn test_date_weekday() { + assert_eq!(NaiveDate::from_ymd_opt(1582, 10, 15).unwrap().weekday(), Weekday::Fri); + // May 20, 1875 = ISO 8601 reference date + assert_eq!(NaiveDate::from_ymd_opt(1875, 5, 20).unwrap().weekday(), Weekday::Thu); + assert_eq!(NaiveDate::from_ymd_opt(2000, 1, 1).unwrap().weekday(), Weekday::Sat); +} + +#[test] +fn test_date_with_fields() { + let d = NaiveDate::from_ymd_opt(2000, 2, 29).unwrap(); + assert_eq!(d.with_year(-400), Some(NaiveDate::from_ymd_opt(-400, 2, 29).unwrap())); + assert_eq!(d.with_year(-100), None); + assert_eq!(d.with_year(1600), Some(NaiveDate::from_ymd_opt(1600, 2, 29).unwrap())); + assert_eq!(d.with_year(1900), None); + assert_eq!(d.with_year(2000), Some(NaiveDate::from_ymd_opt(2000, 2, 29).unwrap())); + assert_eq!(d.with_year(2001), None); + assert_eq!(d.with_year(2004), Some(NaiveDate::from_ymd_opt(2004, 2, 29).unwrap())); + assert_eq!(d.with_year(i32::MAX), None); + + let d = NaiveDate::from_ymd_opt(2000, 4, 30).unwrap(); + assert_eq!(d.with_month(0), None); + assert_eq!(d.with_month(1), Some(NaiveDate::from_ymd_opt(2000, 1, 30).unwrap())); + assert_eq!(d.with_month(2), None); + assert_eq!(d.with_month(3), Some(NaiveDate::from_ymd_opt(2000, 3, 30).unwrap())); + assert_eq!(d.with_month(4), Some(NaiveDate::from_ymd_opt(2000, 4, 30).unwrap())); + assert_eq!(d.with_month(12), Some(NaiveDate::from_ymd_opt(2000, 12, 30).unwrap())); + assert_eq!(d.with_month(13), None); + assert_eq!(d.with_month(u32::MAX), None); + + let d = NaiveDate::from_ymd_opt(2000, 2, 8).unwrap(); + assert_eq!(d.with_day(0), None); + assert_eq!(d.with_day(1), Some(NaiveDate::from_ymd_opt(2000, 2, 1).unwrap())); + assert_eq!(d.with_day(29), Some(NaiveDate::from_ymd_opt(2000, 2, 29).unwrap())); + assert_eq!(d.with_day(30), None); + assert_eq!(d.with_day(u32::MAX), None); +} + +#[test] +fn test_date_with_ordinal() { + let d = NaiveDate::from_ymd_opt(2000, 5, 5).unwrap(); + assert_eq!(d.with_ordinal(0), None); + assert_eq!(d.with_ordinal(1), Some(NaiveDate::from_ymd_opt(2000, 1, 1).unwrap())); + assert_eq!(d.with_ordinal(60), Some(NaiveDate::from_ymd_opt(2000, 2, 29).unwrap())); + assert_eq!(d.with_ordinal(61), Some(NaiveDate::from_ymd_opt(2000, 3, 1).unwrap())); + assert_eq!(d.with_ordinal(366), Some(NaiveDate::from_ymd_opt(2000, 12, 31).unwrap())); + assert_eq!(d.with_ordinal(367), None); + assert_eq!(d.with_ordinal((1 << 28) | 60), None); + let d = NaiveDate::from_ymd_opt(1999, 5, 5).unwrap(); + assert_eq!(d.with_ordinal(366), None); + assert_eq!(d.with_ordinal(u32::MAX), None); +} + +#[test] +fn test_date_num_days_from_ce() { + assert_eq!(NaiveDate::from_ymd_opt(1, 1, 1).unwrap().num_days_from_ce(), 1); + + for year in -9999..10001 { + assert_eq!( + NaiveDate::from_ymd_opt(year, 1, 1).unwrap().num_days_from_ce(), + NaiveDate::from_ymd_opt(year - 1, 12, 31).unwrap().num_days_from_ce() + 1 + ); + } +} + +#[test] +fn test_date_succ() { + let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + assert_eq!(ymd(2014, 5, 6).succ_opt(), Some(ymd(2014, 5, 7))); + assert_eq!(ymd(2014, 5, 31).succ_opt(), Some(ymd(2014, 6, 1))); + assert_eq!(ymd(2014, 12, 31).succ_opt(), Some(ymd(2015, 1, 1))); + assert_eq!(ymd(2016, 2, 28).succ_opt(), Some(ymd(2016, 2, 29))); + assert_eq!(ymd(NaiveDate::MAX.year(), 12, 31).succ_opt(), None); +} + +#[test] +fn test_date_pred() { + let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + assert_eq!(ymd(2016, 3, 1).pred_opt(), Some(ymd(2016, 2, 29))); + assert_eq!(ymd(2015, 1, 1).pred_opt(), Some(ymd(2014, 12, 31))); + assert_eq!(ymd(2014, 6, 1).pred_opt(), Some(ymd(2014, 5, 31))); + assert_eq!(ymd(2014, 5, 7).pred_opt(), Some(ymd(2014, 5, 6))); + assert_eq!(ymd(NaiveDate::MIN.year(), 1, 1).pred_opt(), None); +} + +#[test] +fn test_date_checked_add_signed() { + fn check(lhs: Option, delta: TimeDelta, rhs: Option) { + assert_eq!(lhs.unwrap().checked_add_signed(delta), rhs); + assert_eq!(lhs.unwrap().checked_sub_signed(-delta), rhs); + } + let ymd = NaiveDate::from_ymd_opt; + + check(ymd(2014, 1, 1), TimeDelta::zero(), ymd(2014, 1, 1)); + check(ymd(2014, 1, 1), TimeDelta::try_seconds(86399).unwrap(), ymd(2014, 1, 1)); + // always round towards zero + check(ymd(2014, 1, 1), TimeDelta::try_seconds(-86399).unwrap(), ymd(2014, 1, 1)); + check(ymd(2014, 1, 1), TimeDelta::try_days(1).unwrap(), ymd(2014, 1, 2)); + check(ymd(2014, 1, 1), TimeDelta::try_days(-1).unwrap(), ymd(2013, 12, 31)); + check(ymd(2014, 1, 1), TimeDelta::try_days(364).unwrap(), ymd(2014, 12, 31)); + check(ymd(2014, 1, 1), TimeDelta::try_days(365 * 4 + 1).unwrap(), ymd(2018, 1, 1)); + check(ymd(2014, 1, 1), TimeDelta::try_days(365 * 400 + 97).unwrap(), ymd(2414, 1, 1)); + + check(ymd(-7, 1, 1), TimeDelta::try_days(365 * 12 + 3).unwrap(), ymd(5, 1, 1)); + + // overflow check + check( + ymd(0, 1, 1), + TimeDelta::try_days(MAX_DAYS_FROM_YEAR_0 as i64).unwrap(), + ymd(MAX_YEAR, 12, 31), + ); + check(ymd(0, 1, 1), TimeDelta::try_days(MAX_DAYS_FROM_YEAR_0 as i64 + 1).unwrap(), None); + check(ymd(0, 1, 1), TimeDelta::MAX, None); + check( + ymd(0, 1, 1), + TimeDelta::try_days(MIN_DAYS_FROM_YEAR_0 as i64).unwrap(), + ymd(MIN_YEAR, 1, 1), + ); + check(ymd(0, 1, 1), TimeDelta::try_days(MIN_DAYS_FROM_YEAR_0 as i64 - 1).unwrap(), None); + check(ymd(0, 1, 1), TimeDelta::MIN, None); +} + +#[test] +fn test_date_signed_duration_since() { + fn check(lhs: Option, rhs: Option, delta: TimeDelta) { + assert_eq!(lhs.unwrap().signed_duration_since(rhs.unwrap()), delta); + assert_eq!(rhs.unwrap().signed_duration_since(lhs.unwrap()), -delta); + } + let ymd = NaiveDate::from_ymd_opt; + + check(ymd(2014, 1, 1), ymd(2014, 1, 1), TimeDelta::zero()); + check(ymd(2014, 1, 2), ymd(2014, 1, 1), TimeDelta::try_days(1).unwrap()); + check(ymd(2014, 12, 31), ymd(2014, 1, 1), TimeDelta::try_days(364).unwrap()); + check(ymd(2015, 1, 3), ymd(2014, 1, 1), TimeDelta::try_days(365 + 2).unwrap()); + check(ymd(2018, 1, 1), ymd(2014, 1, 1), TimeDelta::try_days(365 * 4 + 1).unwrap()); + check(ymd(2414, 1, 1), ymd(2014, 1, 1), TimeDelta::try_days(365 * 400 + 97).unwrap()); + + check( + ymd(MAX_YEAR, 12, 31), + ymd(0, 1, 1), + TimeDelta::try_days(MAX_DAYS_FROM_YEAR_0 as i64).unwrap(), + ); + check( + ymd(MIN_YEAR, 1, 1), + ymd(0, 1, 1), + TimeDelta::try_days(MIN_DAYS_FROM_YEAR_0 as i64).unwrap(), + ); +} + +#[test] +fn test_date_add_days() { + fn check(lhs: Option, days: Days, rhs: Option) { + assert_eq!(lhs.unwrap().checked_add_days(days), rhs); + } + let ymd = NaiveDate::from_ymd_opt; + + check(ymd(2014, 1, 1), Days::new(0), ymd(2014, 1, 1)); + // always round towards zero + check(ymd(2014, 1, 1), Days::new(1), ymd(2014, 1, 2)); + check(ymd(2014, 1, 1), Days::new(364), ymd(2014, 12, 31)); + check(ymd(2014, 1, 1), Days::new(365 * 4 + 1), ymd(2018, 1, 1)); + check(ymd(2014, 1, 1), Days::new(365 * 400 + 97), ymd(2414, 1, 1)); + + check(ymd(-7, 1, 1), Days::new(365 * 12 + 3), ymd(5, 1, 1)); + + // overflow check + check(ymd(0, 1, 1), Days::new(MAX_DAYS_FROM_YEAR_0.try_into().unwrap()), ymd(MAX_YEAR, 12, 31)); + check(ymd(0, 1, 1), Days::new(u64::try_from(MAX_DAYS_FROM_YEAR_0).unwrap() + 1), None); +} + +#[test] +fn test_date_sub_days() { + fn check(lhs: Option, days: Days, rhs: Option) { + assert_eq!(lhs.unwrap().checked_sub_days(days), rhs); + } + let ymd = NaiveDate::from_ymd_opt; + + check(ymd(2014, 1, 1), Days::new(0), ymd(2014, 1, 1)); + check(ymd(2014, 1, 2), Days::new(1), ymd(2014, 1, 1)); + check(ymd(2014, 12, 31), Days::new(364), ymd(2014, 1, 1)); + check(ymd(2015, 1, 3), Days::new(365 + 2), ymd(2014, 1, 1)); + check(ymd(2018, 1, 1), Days::new(365 * 4 + 1), ymd(2014, 1, 1)); + check(ymd(2414, 1, 1), Days::new(365 * 400 + 97), ymd(2014, 1, 1)); + + check(ymd(MAX_YEAR, 12, 31), Days::new(MAX_DAYS_FROM_YEAR_0.try_into().unwrap()), ymd(0, 1, 1)); + check( + ymd(0, 1, 1), + Days::new((-MIN_DAYS_FROM_YEAR_0).try_into().unwrap()), + ymd(MIN_YEAR, 1, 1), + ); +} + +#[test] +fn test_date_addassignment() { + let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + let mut date = ymd(2016, 10, 1); + date += TimeDelta::try_days(10).unwrap(); + assert_eq!(date, ymd(2016, 10, 11)); + date += TimeDelta::try_days(30).unwrap(); + assert_eq!(date, ymd(2016, 11, 10)); +} + +#[test] +fn test_date_subassignment() { + let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + let mut date = ymd(2016, 10, 11); + date -= TimeDelta::try_days(10).unwrap(); + assert_eq!(date, ymd(2016, 10, 1)); + date -= TimeDelta::try_days(2).unwrap(); + assert_eq!(date, ymd(2016, 9, 29)); +} + +#[test] +fn test_date_fmt() { + assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(2012, 3, 4).unwrap()), "2012-03-04"); + assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(0, 3, 4).unwrap()), "0000-03-04"); + assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(-307, 3, 4).unwrap()), "-0307-03-04"); + assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(12345, 3, 4).unwrap()), "+12345-03-04"); + + assert_eq!(NaiveDate::from_ymd_opt(2012, 3, 4).unwrap().to_string(), "2012-03-04"); + assert_eq!(NaiveDate::from_ymd_opt(0, 3, 4).unwrap().to_string(), "0000-03-04"); + assert_eq!(NaiveDate::from_ymd_opt(-307, 3, 4).unwrap().to_string(), "-0307-03-04"); + assert_eq!(NaiveDate::from_ymd_opt(12345, 3, 4).unwrap().to_string(), "+12345-03-04"); + + // the format specifier should have no effect on `NaiveTime` + assert_eq!(format!("{:+30?}", NaiveDate::from_ymd_opt(1234, 5, 6).unwrap()), "1234-05-06"); + assert_eq!(format!("{:30?}", NaiveDate::from_ymd_opt(12345, 6, 7).unwrap()), "+12345-06-07"); +} + +#[test] +fn test_date_from_str() { + // valid cases + let valid = [ + "-0000000123456-1-2", + " -123456 - 1 - 2 ", + "-12345-1-2", + "-1234-12-31", + "-7-6-5", + "350-2-28", + "360-02-29", + "0360-02-29", + "2015-2 -18", + "2015-02-18", + "+70-2-18", + "+70000-2-18", + "+00007-2-18", + ]; + for &s in &valid { + eprintln!("test_date_from_str valid {:?}", s); + let d = match s.parse::() { + Ok(d) => d, + Err(e) => panic!("parsing `{}` has failed: {}", s, e), + }; + eprintln!("d {:?} (NaiveDate)", d); + let s_ = format!("{:?}", d); + eprintln!("s_ {:?}", s_); + // `s` and `s_` may differ, but `s.parse()` and `s_.parse()` must be same + let d_ = match s_.parse::() { + Ok(d) => d, + Err(e) => { + panic!("`{}` is parsed into `{:?}`, but reparsing that has failed: {}", s, d, e) + } + }; + eprintln!("d_ {:?} (NaiveDate)", d_); + assert!( + d == d_, + "`{}` is parsed into `{:?}`, but reparsed result \ + `{:?}` does not match", + s, + d, + d_ + ); + } + + // some invalid cases + // since `ParseErrorKind` is private, all we can do is to check if there was an error + let invalid = [ + "", // empty + "x", // invalid + "Fri, 09 Aug 2013 GMT", // valid date, wrong format + "Sat Jun 30 2012", // valid date, wrong format + "1441497364.649", // valid datetime, wrong format + "+1441497364.649", // valid datetime, wrong format + "+1441497364", // valid datetime, wrong format + "2014/02/03", // valid date, wrong format + "2014", // datetime missing data + "2014-01", // datetime missing data + "2014-01-00", // invalid day + "2014-11-32", // invalid day + "2014-13-01", // invalid month + "2014-13-57", // invalid month, day + "9999999-9-9", // invalid year (out of bounds) + ]; + for &s in &invalid { + eprintln!("test_date_from_str invalid {:?}", s); + assert!(s.parse::().is_err()); + } +} + +#[test] +fn test_date_parse_from_str() { + let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + assert_eq!( + NaiveDate::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), + Ok(ymd(2014, 5, 7)) + ); // ignore time and offset + assert_eq!( + NaiveDate::parse_from_str("2015-W06-1=2015-033 Q1", "%G-W%V-%u = %Y-%j Q%q"), + Ok(ymd(2015, 2, 2)) + ); + assert_eq!(NaiveDate::parse_from_str("Fri, 09 Aug 13", "%a, %d %b %y"), Ok(ymd(2013, 8, 9))); + assert!(NaiveDate::parse_from_str("Sat, 09 Aug 2013", "%a, %d %b %Y").is_err()); + assert!(NaiveDate::parse_from_str("2014-57", "%Y-%m-%d").is_err()); + assert!(NaiveDate::parse_from_str("2014", "%Y").is_err()); // insufficient + + assert!(NaiveDate::parse_from_str("2014-5-7 Q3", "%Y-%m-%d Q%q").is_err()); // mismatched quarter + + assert_eq!( + NaiveDate::parse_from_str("2020-01-0", "%Y-%W-%w").ok(), + NaiveDate::from_ymd_opt(2020, 1, 12), + ); + + assert_eq!( + NaiveDate::parse_from_str("2019-01-0", "%Y-%W-%w").ok(), + NaiveDate::from_ymd_opt(2019, 1, 13), + ); +} + +#[test] +fn test_day_iterator_limit() { + assert_eq!(NaiveDate::from_ymd_opt(MAX_YEAR, 12, 29).unwrap().iter_days().take(4).count(), 2); + assert_eq!( + NaiveDate::from_ymd_opt(MIN_YEAR, 1, 3).unwrap().iter_days().rev().take(4).count(), + 2 + ); +} + +#[test] +fn test_week_iterator_limit() { + assert_eq!(NaiveDate::from_ymd_opt(MAX_YEAR, 12, 12).unwrap().iter_weeks().take(4).count(), 2); + assert_eq!( + NaiveDate::from_ymd_opt(MIN_YEAR, 1, 15).unwrap().iter_weeks().rev().take(4).count(), + 2 + ); +} + +#[test] +fn test_weeks_from() { + // tests per: https://github.com/chronotope/chrono/issues/961 + // these internally use `weeks_from` via the parsing infrastructure + assert_eq!( + NaiveDate::parse_from_str("2020-01-0", "%Y-%W-%w").ok(), + NaiveDate::from_ymd_opt(2020, 1, 12), + ); + assert_eq!( + NaiveDate::parse_from_str("2019-01-0", "%Y-%W-%w").ok(), + NaiveDate::from_ymd_opt(2019, 1, 13), + ); + + // direct tests + for (y, starts_on) in &[ + (2019, Weekday::Tue), + (2020, Weekday::Wed), + (2021, Weekday::Fri), + (2022, Weekday::Sat), + (2023, Weekday::Sun), + (2024, Weekday::Mon), + (2025, Weekday::Wed), + (2026, Weekday::Thu), + ] { + for day in &[ + Weekday::Mon, + Weekday::Tue, + Weekday::Wed, + Weekday::Thu, + Weekday::Fri, + Weekday::Sat, + Weekday::Sun, + ] { + assert_eq!( + NaiveDate::from_ymd_opt(*y, 1, 1).map(|d| d.weeks_from(*day)), + Some(if day == starts_on { 1 } else { 0 }) + ); + + // last day must always be in week 52 or 53 + assert!( + [52, 53].contains(&NaiveDate::from_ymd_opt(*y, 12, 31).unwrap().weeks_from(*day)), + ); + } + } + + let base = NaiveDate::from_ymd_opt(2019, 1, 1).unwrap(); + + // 400 years covers all year types + for day in &[ + Weekday::Mon, + Weekday::Tue, + Weekday::Wed, + Weekday::Thu, + Weekday::Fri, + Weekday::Sat, + Weekday::Sun, + ] { + // must always be below 54 + for dplus in 1..(400 * 366) { + assert!((base + Days::new(dplus)).weeks_from(*day) < 54) + } + } +} + +#[test] +fn test_with_0_overflow() { + let dt = NaiveDate::from_ymd_opt(2023, 4, 18).unwrap(); + assert!(dt.with_month0(4294967295).is_none()); + assert!(dt.with_day0(4294967295).is_none()); + assert!(dt.with_ordinal0(4294967295).is_none()); +} + +#[test] +fn test_leap_year() { + for year in 0..=MAX_YEAR { + let date = NaiveDate::from_ymd_opt(year, 1, 1).unwrap(); + let is_leap = year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); + assert_eq!(date.leap_year(), is_leap); + assert_eq!(date.leap_year(), date.with_ordinal(366).is_some()); + } +} + +#[test] +fn test_date_yearflags() { + for (year, year_flags, _) in YEAR_FLAGS { + assert_eq!(NaiveDate::from_yo_opt(year, 1).unwrap().year_flags(), year_flags); + } +} + +#[test] +fn test_weekday_with_yearflags() { + for (year, year_flags, first_weekday) in YEAR_FLAGS { + let first_day_of_year = NaiveDate::from_yo_opt(year, 1).unwrap(); + dbg!(year); + assert_eq!(first_day_of_year.year_flags(), year_flags); + assert_eq!(first_day_of_year.weekday(), first_weekday); + + let mut prev = first_day_of_year.weekday(); + for ordinal in 2u32..=year_flags.ndays() { + let date = NaiveDate::from_yo_opt(year, ordinal).unwrap(); + let expected = prev.succ(); + assert_eq!(date.weekday(), expected); + prev = expected; + } + } +} + +#[test] +fn test_isoweekdate_with_yearflags() { + for (year, year_flags, _) in YEAR_FLAGS { + // January 4 should be in the first week + let jan4 = NaiveDate::from_ymd_opt(year, 1, 4).unwrap(); + let iso_week = jan4.iso_week(); + assert_eq!(jan4.year_flags(), year_flags); + assert_eq!(iso_week.week(), 1); + } +} + +#[test] +fn test_date_to_mdf_to_date() { + for (year, year_flags, _) in YEAR_FLAGS { + for ordinal in 1..=year_flags.ndays() { + let date = NaiveDate::from_yo_opt(year, ordinal).unwrap(); + assert_eq!(date, NaiveDate::from_mdf(date.year(), date.mdf()).unwrap()); + } + } +} + +// Used for testing some methods with all combinations of `YearFlags`. +// (year, flags, first weekday of year) +const YEAR_FLAGS: [(i32, YearFlags, Weekday); 14] = [ + (2006, A, Weekday::Sun), + (2005, B, Weekday::Sat), + (2010, C, Weekday::Fri), + (2009, D, Weekday::Thu), + (2003, E, Weekday::Wed), + (2002, F, Weekday::Tue), + (2001, G, Weekday::Mon), + (2012, AG, Weekday::Sun), + (2000, BA, Weekday::Sat), + (2016, CB, Weekday::Fri), + (2004, DC, Weekday::Thu), + (2020, ED, Weekday::Wed), + (2008, FE, Weekday::Tue), + (2024, GF, Weekday::Mon), +]; + +#[test] +#[cfg(feature = "rkyv-validation")] +fn test_rkyv_validation() { + let date_min = NaiveDate::MIN; + let bytes = rkyv::to_bytes::<_, 4>(&date_min).unwrap(); + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), date_min); + + let date_max = NaiveDate::MAX; + let bytes = rkyv::to_bytes::<_, 4>(&date_max).unwrap(); + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), date_max); +} + +// MAX_YEAR-12-31 minus 0000-01-01 +// = (MAX_YEAR-12-31 minus 0000-12-31) + (0000-12-31 - 0000-01-01) +// = MAX_YEAR * 365 + (# of leap years from 0001 to MAX_YEAR) + 365 +// = (MAX_YEAR + 1) * 365 + (# of leap years from 0001 to MAX_YEAR) +const MAX_DAYS_FROM_YEAR_0: i32 = + (MAX_YEAR + 1) * 365 + MAX_YEAR / 4 - MAX_YEAR / 100 + MAX_YEAR / 400; + +// MIN_YEAR-01-01 minus 0000-01-01 +// = MIN_YEAR * 365 + (# of leap years from MIN_YEAR to 0000) +const MIN_DAYS_FROM_YEAR_0: i32 = MIN_YEAR * 365 + MIN_YEAR / 4 - MIN_YEAR / 100 + MIN_YEAR / 400; + +// only used for testing, but duplicated in naive::datetime +const MAX_BITS: usize = 44; diff --git a/third_party/rust/chrono/src/naive/datetime.rs b/third_party/rust/chrono/src/naive/datetime.rs deleted file mode 100644 index 92d6c28554f..00000000000 --- a/third_party/rust/chrono/src/naive/datetime.rs +++ /dev/null @@ -1,2507 +0,0 @@ -// This is a part of Chrono. -// See README.md and LICENSE.txt for details. - -//! ISO 8601 date and time without timezone. - -#[cfg(any(feature = "alloc", feature = "std", test))] -use core::borrow::Borrow; -use core::ops::{Add, AddAssign, Sub, SubAssign}; -use core::{fmt, hash, str}; -use num_traits::ToPrimitive; -use oldtime::Duration as OldDuration; - -use div::div_mod_floor; -#[cfg(any(feature = "alloc", feature = "std", test))] -use format::DelayedFormat; -use format::{parse, ParseError, ParseResult, Parsed, StrftimeItems}; -use format::{Fixed, Item, Numeric, Pad}; -use naive::date::{MAX_DATE, MIN_DATE}; -use naive::time::{MAX_TIME, MIN_TIME}; -use naive::{IsoWeek, NaiveDate, NaiveTime}; -use {Datelike, Timelike, Weekday}; - -/// The tight upper bound guarantees that a duration with `|Duration| >= 2^MAX_SECS_BITS` -/// will always overflow the addition with any date and time type. -/// -/// So why is this needed? `Duration::seconds(rhs)` may overflow, and we don't have -/// an alternative returning `Option` or `Result`. Thus we need some early bound to avoid -/// touching that call when we are already sure that it WILL overflow... -const MAX_SECS_BITS: usize = 44; - -/// The minimum possible `NaiveDateTime`. -pub const MIN_DATETIME: NaiveDateTime = NaiveDateTime { date: MIN_DATE, time: MIN_TIME }; -/// The maximum possible `NaiveDateTime`. -pub const MAX_DATETIME: NaiveDateTime = NaiveDateTime { date: MAX_DATE, time: MAX_TIME }; - -/// ISO 8601 combined date and time without timezone. -/// -/// # Example -/// -/// `NaiveDateTime` is commonly created from [`NaiveDate`](./struct.NaiveDate.html). -/// -/// ~~~~ -/// use chrono::{NaiveDate, NaiveDateTime}; -/// -/// let dt: NaiveDateTime = NaiveDate::from_ymd(2016, 7, 8).and_hms(9, 10, 11); -/// # let _ = dt; -/// ~~~~ -/// -/// You can use typical [date-like](../trait.Datelike.html) and -/// [time-like](../trait.Timelike.html) methods, -/// provided that relevant traits are in the scope. -/// -/// ~~~~ -/// # use chrono::{NaiveDate, NaiveDateTime}; -/// # let dt: NaiveDateTime = NaiveDate::from_ymd(2016, 7, 8).and_hms(9, 10, 11); -/// use chrono::{Datelike, Timelike, Weekday}; -/// -/// assert_eq!(dt.weekday(), Weekday::Fri); -/// assert_eq!(dt.num_seconds_from_midnight(), 33011); -/// ~~~~ -#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone)] -pub struct NaiveDateTime { - date: NaiveDate, - time: NaiveTime, -} - -impl NaiveDateTime { - /// Makes a new `NaiveDateTime` from date and time components. - /// Equivalent to [`date.and_time(time)`](./struct.NaiveDate.html#method.and_time) - /// and many other helper constructors on `NaiveDate`. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, NaiveTime, NaiveDateTime}; - /// - /// let d = NaiveDate::from_ymd(2015, 6, 3); - /// let t = NaiveTime::from_hms_milli(12, 34, 56, 789); - /// - /// let dt = NaiveDateTime::new(d, t); - /// assert_eq!(dt.date(), d); - /// assert_eq!(dt.time(), t); - /// ~~~~ - #[inline] - pub fn new(date: NaiveDate, time: NaiveTime) -> NaiveDateTime { - NaiveDateTime { date: date, time: time } - } - - /// Makes a new `NaiveDateTime` corresponding to a UTC date and time, - /// from the number of non-leap seconds - /// since the midnight UTC on January 1, 1970 (aka "UNIX timestamp") - /// and the number of nanoseconds since the last whole non-leap second. - /// - /// For a non-naive version of this function see - /// [`TimeZone::timestamp`](../offset/trait.TimeZone.html#method.timestamp). - /// - /// The nanosecond part can exceed 1,000,000,000 in order to represent the - /// [leap second](./struct.NaiveTime.html#leap-second-handling). (The true "UNIX - /// timestamp" cannot represent a leap second unambiguously.) - /// - /// Panics on the out-of-range number of seconds and/or invalid nanosecond. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDateTime, NaiveDate}; - /// - /// let dt = NaiveDateTime::from_timestamp(0, 42_000_000); - /// assert_eq!(dt, NaiveDate::from_ymd(1970, 1, 1).and_hms_milli(0, 0, 0, 42)); - /// - /// let dt = NaiveDateTime::from_timestamp(1_000_000_000, 0); - /// assert_eq!(dt, NaiveDate::from_ymd(2001, 9, 9).and_hms(1, 46, 40)); - /// ~~~~ - #[inline] - pub fn from_timestamp(secs: i64, nsecs: u32) -> NaiveDateTime { - let datetime = NaiveDateTime::from_timestamp_opt(secs, nsecs); - datetime.expect("invalid or out-of-range datetime") - } - - /// Makes a new `NaiveDateTime` corresponding to a UTC date and time, - /// from the number of non-leap seconds - /// since the midnight UTC on January 1, 1970 (aka "UNIX timestamp") - /// and the number of nanoseconds since the last whole non-leap second. - /// - /// The nanosecond part can exceed 1,000,000,000 - /// in order to represent the [leap second](./struct.NaiveTime.html#leap-second-handling). - /// (The true "UNIX timestamp" cannot represent a leap second unambiguously.) - /// - /// Returns `None` on the out-of-range number of seconds and/or invalid nanosecond. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDateTime, NaiveDate}; - /// use std::i64; - /// - /// let from_timestamp_opt = NaiveDateTime::from_timestamp_opt; - /// - /// assert!(from_timestamp_opt(0, 0).is_some()); - /// assert!(from_timestamp_opt(0, 999_999_999).is_some()); - /// assert!(from_timestamp_opt(0, 1_500_000_000).is_some()); // leap second - /// assert!(from_timestamp_opt(0, 2_000_000_000).is_none()); - /// assert!(from_timestamp_opt(i64::MAX, 0).is_none()); - /// ~~~~ - #[inline] - pub fn from_timestamp_opt(secs: i64, nsecs: u32) -> Option { - let (days, secs) = div_mod_floor(secs, 86_400); - let date = days - .to_i32() - .and_then(|days| days.checked_add(719_163)) - .and_then(NaiveDate::from_num_days_from_ce_opt); - let time = NaiveTime::from_num_seconds_from_midnight_opt(secs as u32, nsecs); - match (date, time) { - (Some(date), Some(time)) => Some(NaiveDateTime { date: date, time: time }), - (_, _) => None, - } - } - - /// Parses a string with the specified format string and returns a new `NaiveDateTime`. - /// See the [`format::strftime` module](../format/strftime/index.html) - /// on the supported escape sequences. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDateTime, NaiveDate}; - /// - /// let parse_from_str = NaiveDateTime::parse_from_str; - /// - /// assert_eq!(parse_from_str("2015-09-05 23:56:04", "%Y-%m-%d %H:%M:%S"), - /// Ok(NaiveDate::from_ymd(2015, 9, 5).and_hms(23, 56, 4))); - /// assert_eq!(parse_from_str("5sep2015pm012345.6789", "%d%b%Y%p%I%M%S%.f"), - /// Ok(NaiveDate::from_ymd(2015, 9, 5).and_hms_micro(13, 23, 45, 678_900))); - /// ~~~~ - /// - /// Offset is ignored for the purpose of parsing. - /// - /// ~~~~ - /// # use chrono::{NaiveDateTime, NaiveDate}; - /// # let parse_from_str = NaiveDateTime::parse_from_str; - /// assert_eq!(parse_from_str("2014-5-17T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), - /// Ok(NaiveDate::from_ymd(2014, 5, 17).and_hms(12, 34, 56))); - /// ~~~~ - /// - /// [Leap seconds](./struct.NaiveTime.html#leap-second-handling) are correctly handled by - /// treating any time of the form `hh:mm:60` as a leap second. - /// (This equally applies to the formatting, so the round trip is possible.) - /// - /// ~~~~ - /// # use chrono::{NaiveDateTime, NaiveDate}; - /// # let parse_from_str = NaiveDateTime::parse_from_str; - /// assert_eq!(parse_from_str("2015-07-01 08:59:60.123", "%Y-%m-%d %H:%M:%S%.f"), - /// Ok(NaiveDate::from_ymd(2015, 7, 1).and_hms_milli(8, 59, 59, 1_123))); - /// ~~~~ - /// - /// Missing seconds are assumed to be zero, - /// but out-of-bound times or insufficient fields are errors otherwise. - /// - /// ~~~~ - /// # use chrono::{NaiveDateTime, NaiveDate}; - /// # let parse_from_str = NaiveDateTime::parse_from_str; - /// assert_eq!(parse_from_str("94/9/4 7:15", "%y/%m/%d %H:%M"), - /// Ok(NaiveDate::from_ymd(1994, 9, 4).and_hms(7, 15, 0))); - /// - /// assert!(parse_from_str("04m33s", "%Mm%Ss").is_err()); - /// assert!(parse_from_str("94/9/4 12", "%y/%m/%d %H").is_err()); - /// assert!(parse_from_str("94/9/4 17:60", "%y/%m/%d %H:%M").is_err()); - /// assert!(parse_from_str("94/9/4 24:00:00", "%y/%m/%d %H:%M:%S").is_err()); - /// ~~~~ - /// - /// All parsed fields should be consistent to each other, otherwise it's an error. - /// - /// ~~~~ - /// # use chrono::NaiveDateTime; - /// # let parse_from_str = NaiveDateTime::parse_from_str; - /// let fmt = "%Y-%m-%d %H:%M:%S = UNIX timestamp %s"; - /// assert!(parse_from_str("2001-09-09 01:46:39 = UNIX timestamp 999999999", fmt).is_ok()); - /// assert!(parse_from_str("1970-01-01 00:00:00 = UNIX timestamp 1", fmt).is_err()); - /// ~~~~ - pub fn parse_from_str(s: &str, fmt: &str) -> ParseResult { - let mut parsed = Parsed::new(); - parse(&mut parsed, s, StrftimeItems::new(fmt))?; - parsed.to_naive_datetime_with_offset(0) // no offset adjustment - } - - /// Retrieves a date component. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::NaiveDate; - /// - /// let dt = NaiveDate::from_ymd(2016, 7, 8).and_hms(9, 10, 11); - /// assert_eq!(dt.date(), NaiveDate::from_ymd(2016, 7, 8)); - /// ~~~~ - #[inline] - pub fn date(&self) -> NaiveDate { - self.date - } - - /// Retrieves a time component. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, NaiveTime}; - /// - /// let dt = NaiveDate::from_ymd(2016, 7, 8).and_hms(9, 10, 11); - /// assert_eq!(dt.time(), NaiveTime::from_hms(9, 10, 11)); - /// ~~~~ - #[inline] - pub fn time(&self) -> NaiveTime { - self.time - } - - /// Returns the number of non-leap seconds since the midnight on January 1, 1970. - /// - /// Note that this does *not* account for the timezone! - /// The true "UNIX timestamp" would count seconds since the midnight *UTC* on the epoch. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::NaiveDate; - /// - /// let dt = NaiveDate::from_ymd(1970, 1, 1).and_hms_milli(0, 0, 1, 980); - /// assert_eq!(dt.timestamp(), 1); - /// - /// let dt = NaiveDate::from_ymd(2001, 9, 9).and_hms(1, 46, 40); - /// assert_eq!(dt.timestamp(), 1_000_000_000); - /// - /// let dt = NaiveDate::from_ymd(1969, 12, 31).and_hms(23, 59, 59); - /// assert_eq!(dt.timestamp(), -1); - /// - /// let dt = NaiveDate::from_ymd(-1, 1, 1).and_hms(0, 0, 0); - /// assert_eq!(dt.timestamp(), -62198755200); - /// ~~~~ - #[inline] - pub fn timestamp(&self) -> i64 { - const UNIX_EPOCH_DAY: i64 = 719_163; - let gregorian_day = i64::from(self.date.num_days_from_ce()); - let seconds_from_midnight = i64::from(self.time.num_seconds_from_midnight()); - (gregorian_day - UNIX_EPOCH_DAY) * 86_400 + seconds_from_midnight - } - - /// Returns the number of non-leap *milliseconds* since midnight on January 1, 1970. - /// - /// Note that this does *not* account for the timezone! - /// The true "UNIX timestamp" would count seconds since the midnight *UTC* on the epoch. - /// - /// Note also that this does reduce the number of years that can be - /// represented from ~584 Billion to ~584 Million. (If this is a problem, - /// please file an issue to let me know what domain needs millisecond - /// precision over billions of years, I'm curious.) - /// - /// # Example - /// - /// ~~~~ - /// use chrono::NaiveDate; - /// - /// let dt = NaiveDate::from_ymd(1970, 1, 1).and_hms_milli(0, 0, 1, 444); - /// assert_eq!(dt.timestamp_millis(), 1_444); - /// - /// let dt = NaiveDate::from_ymd(2001, 9, 9).and_hms_milli(1, 46, 40, 555); - /// assert_eq!(dt.timestamp_millis(), 1_000_000_000_555); - /// - /// let dt = NaiveDate::from_ymd(1969, 12, 31).and_hms_milli(23, 59, 59, 100); - /// assert_eq!(dt.timestamp_millis(), -900); - /// ~~~~ - #[inline] - pub fn timestamp_millis(&self) -> i64 { - let as_ms = self.timestamp() * 1000; - as_ms + i64::from(self.timestamp_subsec_millis()) - } - - /// Returns the number of non-leap *nanoseconds* since midnight on January 1, 1970. - /// - /// Note that this does *not* account for the timezone! - /// The true "UNIX timestamp" would count seconds since the midnight *UTC* on the epoch. - /// - /// # Panics - /// - /// Note also that this does reduce the number of years that can be - /// represented from ~584 Billion to ~584 years. The dates that can be - /// represented as nanoseconds are between 1677-09-21T00:12:44.0 and - /// 2262-04-11T23:47:16.854775804. - /// - /// (If this is a problem, please file an issue to let me know what domain - /// needs nanosecond precision over millennia, I'm curious.) - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, NaiveDateTime}; - /// - /// let dt = NaiveDate::from_ymd(1970, 1, 1).and_hms_nano(0, 0, 1, 444); - /// assert_eq!(dt.timestamp_nanos(), 1_000_000_444); - /// - /// let dt = NaiveDate::from_ymd(2001, 9, 9).and_hms_nano(1, 46, 40, 555); - /// - /// const A_BILLION: i64 = 1_000_000_000; - /// let nanos = dt.timestamp_nanos(); - /// assert_eq!(nanos, 1_000_000_000_000_000_555); - /// assert_eq!( - /// dt, - /// NaiveDateTime::from_timestamp(nanos / A_BILLION, (nanos % A_BILLION) as u32) - /// ); - /// ~~~~ - #[inline] - pub fn timestamp_nanos(&self) -> i64 { - let as_ns = self.timestamp() * 1_000_000_000; - as_ns + i64::from(self.timestamp_subsec_nanos()) - } - - /// Returns the number of milliseconds since the last whole non-leap second. - /// - /// The return value ranges from 0 to 999, - /// or for [leap seconds](./struct.NaiveTime.html#leap-second-handling), to 1,999. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::NaiveDate; - /// - /// let dt = NaiveDate::from_ymd(2016, 7, 8).and_hms_nano(9, 10, 11, 123_456_789); - /// assert_eq!(dt.timestamp_subsec_millis(), 123); - /// - /// let dt = NaiveDate::from_ymd(2015, 7, 1).and_hms_nano(8, 59, 59, 1_234_567_890); - /// assert_eq!(dt.timestamp_subsec_millis(), 1_234); - /// ~~~~ - #[inline] - pub fn timestamp_subsec_millis(&self) -> u32 { - self.timestamp_subsec_nanos() / 1_000_000 - } - - /// Returns the number of microseconds since the last whole non-leap second. - /// - /// The return value ranges from 0 to 999,999, - /// or for [leap seconds](./struct.NaiveTime.html#leap-second-handling), to 1,999,999. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::NaiveDate; - /// - /// let dt = NaiveDate::from_ymd(2016, 7, 8).and_hms_nano(9, 10, 11, 123_456_789); - /// assert_eq!(dt.timestamp_subsec_micros(), 123_456); - /// - /// let dt = NaiveDate::from_ymd(2015, 7, 1).and_hms_nano(8, 59, 59, 1_234_567_890); - /// assert_eq!(dt.timestamp_subsec_micros(), 1_234_567); - /// ~~~~ - #[inline] - pub fn timestamp_subsec_micros(&self) -> u32 { - self.timestamp_subsec_nanos() / 1_000 - } - - /// Returns the number of nanoseconds since the last whole non-leap second. - /// - /// The return value ranges from 0 to 999,999,999, - /// or for [leap seconds](./struct.NaiveTime.html#leap-second-handling), to 1,999,999,999. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::NaiveDate; - /// - /// let dt = NaiveDate::from_ymd(2016, 7, 8).and_hms_nano(9, 10, 11, 123_456_789); - /// assert_eq!(dt.timestamp_subsec_nanos(), 123_456_789); - /// - /// let dt = NaiveDate::from_ymd(2015, 7, 1).and_hms_nano(8, 59, 59, 1_234_567_890); - /// assert_eq!(dt.timestamp_subsec_nanos(), 1_234_567_890); - /// ~~~~ - #[inline] - pub fn timestamp_subsec_nanos(&self) -> u32 { - self.time.nanosecond() - } - - /// Adds given `Duration` to the current date and time. - /// - /// As a part of Chrono's [leap second handling](./struct.NaiveTime.html#leap-second-handling), - /// the addition assumes that **there is no leap second ever**, - /// except when the `NaiveDateTime` itself represents a leap second - /// in which case the assumption becomes that **there is exactly a single leap second ever**. - /// - /// Returns `None` when it will result in overflow. - /// - /// # Example - /// - /// ~~~~ - /// # extern crate chrono; fn main() { - /// use chrono::{Duration, NaiveDate}; - /// - /// let from_ymd = NaiveDate::from_ymd; - /// - /// let d = from_ymd(2016, 7, 8); - /// let hms = |h, m, s| d.and_hms(h, m, s); - /// assert_eq!(hms(3, 5, 7).checked_add_signed(Duration::zero()), - /// Some(hms(3, 5, 7))); - /// assert_eq!(hms(3, 5, 7).checked_add_signed(Duration::seconds(1)), - /// Some(hms(3, 5, 8))); - /// assert_eq!(hms(3, 5, 7).checked_add_signed(Duration::seconds(-1)), - /// Some(hms(3, 5, 6))); - /// assert_eq!(hms(3, 5, 7).checked_add_signed(Duration::seconds(3600 + 60)), - /// Some(hms(4, 6, 7))); - /// assert_eq!(hms(3, 5, 7).checked_add_signed(Duration::seconds(86_400)), - /// Some(from_ymd(2016, 7, 9).and_hms(3, 5, 7))); - /// - /// let hmsm = |h, m, s, milli| d.and_hms_milli(h, m, s, milli); - /// assert_eq!(hmsm(3, 5, 7, 980).checked_add_signed(Duration::milliseconds(450)), - /// Some(hmsm(3, 5, 8, 430))); - /// # } - /// ~~~~ - /// - /// Overflow returns `None`. - /// - /// ~~~~ - /// # extern crate chrono; fn main() { - /// # use chrono::{Duration, NaiveDate}; - /// # let hms = |h, m, s| NaiveDate::from_ymd(2016, 7, 8).and_hms(h, m, s); - /// assert_eq!(hms(3, 5, 7).checked_add_signed(Duration::days(1_000_000_000)), None); - /// # } - /// ~~~~ - /// - /// Leap seconds are handled, - /// but the addition assumes that it is the only leap second happened. - /// - /// ~~~~ - /// # extern crate chrono; fn main() { - /// # use chrono::{Duration, NaiveDate}; - /// # let from_ymd = NaiveDate::from_ymd; - /// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli(h, m, s, milli); - /// let leap = hmsm(3, 5, 59, 1_300); - /// assert_eq!(leap.checked_add_signed(Duration::zero()), - /// Some(hmsm(3, 5, 59, 1_300))); - /// assert_eq!(leap.checked_add_signed(Duration::milliseconds(-500)), - /// Some(hmsm(3, 5, 59, 800))); - /// assert_eq!(leap.checked_add_signed(Duration::milliseconds(500)), - /// Some(hmsm(3, 5, 59, 1_800))); - /// assert_eq!(leap.checked_add_signed(Duration::milliseconds(800)), - /// Some(hmsm(3, 6, 0, 100))); - /// assert_eq!(leap.checked_add_signed(Duration::seconds(10)), - /// Some(hmsm(3, 6, 9, 300))); - /// assert_eq!(leap.checked_add_signed(Duration::seconds(-10)), - /// Some(hmsm(3, 5, 50, 300))); - /// assert_eq!(leap.checked_add_signed(Duration::days(1)), - /// Some(from_ymd(2016, 7, 9).and_hms_milli(3, 5, 59, 300))); - /// # } - /// ~~~~ - pub fn checked_add_signed(self, rhs: OldDuration) -> Option { - let (time, rhs) = self.time.overflowing_add_signed(rhs); - - // early checking to avoid overflow in OldDuration::seconds - if rhs <= (-1 << MAX_SECS_BITS) || rhs >= (1 << MAX_SECS_BITS) { - return None; - } - - let date = try_opt!(self.date.checked_add_signed(OldDuration::seconds(rhs))); - Some(NaiveDateTime { date: date, time: time }) - } - - /// Subtracts given `Duration` from the current date and time. - /// - /// As a part of Chrono's [leap second handling](./struct.NaiveTime.html#leap-second-handling), - /// the subtraction assumes that **there is no leap second ever**, - /// except when the `NaiveDateTime` itself represents a leap second - /// in which case the assumption becomes that **there is exactly a single leap second ever**. - /// - /// Returns `None` when it will result in overflow. - /// - /// # Example - /// - /// ~~~~ - /// # extern crate chrono; fn main() { - /// use chrono::{Duration, NaiveDate}; - /// - /// let from_ymd = NaiveDate::from_ymd; - /// - /// let d = from_ymd(2016, 7, 8); - /// let hms = |h, m, s| d.and_hms(h, m, s); - /// assert_eq!(hms(3, 5, 7).checked_sub_signed(Duration::zero()), - /// Some(hms(3, 5, 7))); - /// assert_eq!(hms(3, 5, 7).checked_sub_signed(Duration::seconds(1)), - /// Some(hms(3, 5, 6))); - /// assert_eq!(hms(3, 5, 7).checked_sub_signed(Duration::seconds(-1)), - /// Some(hms(3, 5, 8))); - /// assert_eq!(hms(3, 5, 7).checked_sub_signed(Duration::seconds(3600 + 60)), - /// Some(hms(2, 4, 7))); - /// assert_eq!(hms(3, 5, 7).checked_sub_signed(Duration::seconds(86_400)), - /// Some(from_ymd(2016, 7, 7).and_hms(3, 5, 7))); - /// - /// let hmsm = |h, m, s, milli| d.and_hms_milli(h, m, s, milli); - /// assert_eq!(hmsm(3, 5, 7, 450).checked_sub_signed(Duration::milliseconds(670)), - /// Some(hmsm(3, 5, 6, 780))); - /// # } - /// ~~~~ - /// - /// Overflow returns `None`. - /// - /// ~~~~ - /// # extern crate chrono; fn main() { - /// # use chrono::{Duration, NaiveDate}; - /// # let hms = |h, m, s| NaiveDate::from_ymd(2016, 7, 8).and_hms(h, m, s); - /// assert_eq!(hms(3, 5, 7).checked_sub_signed(Duration::days(1_000_000_000)), None); - /// # } - /// ~~~~ - /// - /// Leap seconds are handled, - /// but the subtraction assumes that it is the only leap second happened. - /// - /// ~~~~ - /// # extern crate chrono; fn main() { - /// # use chrono::{Duration, NaiveDate}; - /// # let from_ymd = NaiveDate::from_ymd; - /// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli(h, m, s, milli); - /// let leap = hmsm(3, 5, 59, 1_300); - /// assert_eq!(leap.checked_sub_signed(Duration::zero()), - /// Some(hmsm(3, 5, 59, 1_300))); - /// assert_eq!(leap.checked_sub_signed(Duration::milliseconds(200)), - /// Some(hmsm(3, 5, 59, 1_100))); - /// assert_eq!(leap.checked_sub_signed(Duration::milliseconds(500)), - /// Some(hmsm(3, 5, 59, 800))); - /// assert_eq!(leap.checked_sub_signed(Duration::seconds(60)), - /// Some(hmsm(3, 5, 0, 300))); - /// assert_eq!(leap.checked_sub_signed(Duration::days(1)), - /// Some(from_ymd(2016, 7, 7).and_hms_milli(3, 6, 0, 300))); - /// # } - /// ~~~~ - pub fn checked_sub_signed(self, rhs: OldDuration) -> Option { - let (time, rhs) = self.time.overflowing_sub_signed(rhs); - - // early checking to avoid overflow in OldDuration::seconds - if rhs <= (-1 << MAX_SECS_BITS) || rhs >= (1 << MAX_SECS_BITS) { - return None; - } - - let date = try_opt!(self.date.checked_sub_signed(OldDuration::seconds(rhs))); - Some(NaiveDateTime { date: date, time: time }) - } - - /// Subtracts another `NaiveDateTime` from the current date and time. - /// This does not overflow or underflow at all. - /// - /// As a part of Chrono's [leap second handling](./struct.NaiveTime.html#leap-second-handling), - /// the subtraction assumes that **there is no leap second ever**, - /// except when any of the `NaiveDateTime`s themselves represents a leap second - /// in which case the assumption becomes that - /// **there are exactly one (or two) leap second(s) ever**. - /// - /// # Example - /// - /// ~~~~ - /// # extern crate chrono; fn main() { - /// use chrono::{Duration, NaiveDate}; - /// - /// let from_ymd = NaiveDate::from_ymd; - /// - /// let d = from_ymd(2016, 7, 8); - /// assert_eq!(d.and_hms(3, 5, 7).signed_duration_since(d.and_hms(2, 4, 6)), - /// Duration::seconds(3600 + 60 + 1)); - /// - /// // July 8 is 190th day in the year 2016 - /// let d0 = from_ymd(2016, 1, 1); - /// assert_eq!(d.and_hms_milli(0, 7, 6, 500).signed_duration_since(d0.and_hms(0, 0, 0)), - /// Duration::seconds(189 * 86_400 + 7 * 60 + 6) + Duration::milliseconds(500)); - /// # } - /// ~~~~ - /// - /// Leap seconds are handled, but the subtraction assumes that - /// there were no other leap seconds happened. - /// - /// ~~~~ - /// # extern crate chrono; fn main() { - /// # use chrono::{Duration, NaiveDate}; - /// # let from_ymd = NaiveDate::from_ymd; - /// let leap = from_ymd(2015, 6, 30).and_hms_milli(23, 59, 59, 1_500); - /// assert_eq!(leap.signed_duration_since(from_ymd(2015, 6, 30).and_hms(23, 0, 0)), - /// Duration::seconds(3600) + Duration::milliseconds(500)); - /// assert_eq!(from_ymd(2015, 7, 1).and_hms(1, 0, 0).signed_duration_since(leap), - /// Duration::seconds(3600) - Duration::milliseconds(500)); - /// # } - /// ~~~~ - pub fn signed_duration_since(self, rhs: NaiveDateTime) -> OldDuration { - self.date.signed_duration_since(rhs.date) + self.time.signed_duration_since(rhs.time) - } - - /// Formats the combined date and time with the specified formatting items. - /// Otherwise it is the same as the ordinary [`format`](#method.format) method. - /// - /// The `Iterator` of items should be `Clone`able, - /// since the resulting `DelayedFormat` value may be formatted multiple times. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::NaiveDate; - /// use chrono::format::strftime::StrftimeItems; - /// - /// let fmt = StrftimeItems::new("%Y-%m-%d %H:%M:%S"); - /// let dt = NaiveDate::from_ymd(2015, 9, 5).and_hms(23, 56, 4); - /// assert_eq!(dt.format_with_items(fmt.clone()).to_string(), "2015-09-05 23:56:04"); - /// assert_eq!(dt.format("%Y-%m-%d %H:%M:%S").to_string(), "2015-09-05 23:56:04"); - /// ~~~~ - /// - /// The resulting `DelayedFormat` can be formatted directly via the `Display` trait. - /// - /// ~~~~ - /// # use chrono::NaiveDate; - /// # use chrono::format::strftime::StrftimeItems; - /// # let fmt = StrftimeItems::new("%Y-%m-%d %H:%M:%S").clone(); - /// # let dt = NaiveDate::from_ymd(2015, 9, 5).and_hms(23, 56, 4); - /// assert_eq!(format!("{}", dt.format_with_items(fmt)), "2015-09-05 23:56:04"); - /// ~~~~ - #[cfg(any(feature = "alloc", feature = "std", test))] - #[inline] - pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat - where - I: Iterator + Clone, - B: Borrow>, - { - DelayedFormat::new(Some(self.date), Some(self.time), items) - } - - /// Formats the combined date and time with the specified format string. - /// See the [`format::strftime` module](../format/strftime/index.html) - /// on the supported escape sequences. - /// - /// This returns a `DelayedFormat`, - /// which gets converted to a string only when actual formatting happens. - /// You may use the `to_string` method to get a `String`, - /// or just feed it into `print!` and other formatting macros. - /// (In this way it avoids the redundant memory allocation.) - /// - /// A wrong format string does *not* issue an error immediately. - /// Rather, converting or formatting the `DelayedFormat` fails. - /// You are recommended to immediately use `DelayedFormat` for this reason. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::NaiveDate; - /// - /// let dt = NaiveDate::from_ymd(2015, 9, 5).and_hms(23, 56, 4); - /// assert_eq!(dt.format("%Y-%m-%d %H:%M:%S").to_string(), "2015-09-05 23:56:04"); - /// assert_eq!(dt.format("around %l %p on %b %-d").to_string(), "around 11 PM on Sep 5"); - /// ~~~~ - /// - /// The resulting `DelayedFormat` can be formatted directly via the `Display` trait. - /// - /// ~~~~ - /// # use chrono::NaiveDate; - /// # let dt = NaiveDate::from_ymd(2015, 9, 5).and_hms(23, 56, 4); - /// assert_eq!(format!("{}", dt.format("%Y-%m-%d %H:%M:%S")), "2015-09-05 23:56:04"); - /// assert_eq!(format!("{}", dt.format("around %l %p on %b %-d")), "around 11 PM on Sep 5"); - /// ~~~~ - #[cfg(any(feature = "alloc", feature = "std", test))] - #[inline] - pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { - self.format_with_items(StrftimeItems::new(fmt)) - } -} - -impl Datelike for NaiveDateTime { - /// Returns the year number in the [calendar date](./index.html#calendar-date). - /// - /// See also the [`NaiveDate::year`](./struct.NaiveDate.html#method.year) method. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 25).and_hms(12, 34, 56); - /// assert_eq!(dt.year(), 2015); - /// ~~~~ - #[inline] - fn year(&self) -> i32 { - self.date.year() - } - - /// Returns the month number starting from 1. - /// - /// The return value ranges from 1 to 12. - /// - /// See also the [`NaiveDate::month`](./struct.NaiveDate.html#method.month) method. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 25).and_hms(12, 34, 56); - /// assert_eq!(dt.month(), 9); - /// ~~~~ - #[inline] - fn month(&self) -> u32 { - self.date.month() - } - - /// Returns the month number starting from 0. - /// - /// The return value ranges from 0 to 11. - /// - /// See also the [`NaiveDate::month0`](./struct.NaiveDate.html#method.month0) method. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 25).and_hms(12, 34, 56); - /// assert_eq!(dt.month0(), 8); - /// ~~~~ - #[inline] - fn month0(&self) -> u32 { - self.date.month0() - } - - /// Returns the day of month starting from 1. - /// - /// The return value ranges from 1 to 31. (The last day of month differs by months.) - /// - /// See also the [`NaiveDate::day`](./struct.NaiveDate.html#method.day) method. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 25).and_hms(12, 34, 56); - /// assert_eq!(dt.day(), 25); - /// ~~~~ - #[inline] - fn day(&self) -> u32 { - self.date.day() - } - - /// Returns the day of month starting from 0. - /// - /// The return value ranges from 0 to 30. (The last day of month differs by months.) - /// - /// See also the [`NaiveDate::day0`](./struct.NaiveDate.html#method.day0) method. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 25).and_hms(12, 34, 56); - /// assert_eq!(dt.day0(), 24); - /// ~~~~ - #[inline] - fn day0(&self) -> u32 { - self.date.day0() - } - - /// Returns the day of year starting from 1. - /// - /// The return value ranges from 1 to 366. (The last day of year differs by years.) - /// - /// See also the [`NaiveDate::ordinal`](./struct.NaiveDate.html#method.ordinal) method. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 25).and_hms(12, 34, 56); - /// assert_eq!(dt.ordinal(), 268); - /// ~~~~ - #[inline] - fn ordinal(&self) -> u32 { - self.date.ordinal() - } - - /// Returns the day of year starting from 0. - /// - /// The return value ranges from 0 to 365. (The last day of year differs by years.) - /// - /// See also the [`NaiveDate::ordinal0`](./struct.NaiveDate.html#method.ordinal0) method. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 25).and_hms(12, 34, 56); - /// assert_eq!(dt.ordinal0(), 267); - /// ~~~~ - #[inline] - fn ordinal0(&self) -> u32 { - self.date.ordinal0() - } - - /// Returns the day of week. - /// - /// See also the [`NaiveDate::weekday`](./struct.NaiveDate.html#method.weekday) method. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, NaiveDateTime, Datelike, Weekday}; - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 25).and_hms(12, 34, 56); - /// assert_eq!(dt.weekday(), Weekday::Fri); - /// ~~~~ - #[inline] - fn weekday(&self) -> Weekday { - self.date.weekday() - } - - #[inline] - fn iso_week(&self) -> IsoWeek { - self.date.iso_week() - } - - /// Makes a new `NaiveDateTime` with the year number changed. - /// - /// Returns `None` when the resulting `NaiveDateTime` would be invalid. - /// - /// See also the - /// [`NaiveDate::with_year`](./struct.NaiveDate.html#method.with_year) method. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 25).and_hms(12, 34, 56); - /// assert_eq!(dt.with_year(2016), Some(NaiveDate::from_ymd(2016, 9, 25).and_hms(12, 34, 56))); - /// assert_eq!(dt.with_year(-308), Some(NaiveDate::from_ymd(-308, 9, 25).and_hms(12, 34, 56))); - /// ~~~~ - #[inline] - fn with_year(&self, year: i32) -> Option { - self.date.with_year(year).map(|d| NaiveDateTime { date: d, ..*self }) - } - - /// Makes a new `NaiveDateTime` with the month number (starting from 1) changed. - /// - /// Returns `None` when the resulting `NaiveDateTime` would be invalid. - /// - /// See also the - /// [`NaiveDate::with_month`](./struct.NaiveDate.html#method.with_month) method. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 30).and_hms(12, 34, 56); - /// assert_eq!(dt.with_month(10), Some(NaiveDate::from_ymd(2015, 10, 30).and_hms(12, 34, 56))); - /// assert_eq!(dt.with_month(13), None); // no month 13 - /// assert_eq!(dt.with_month(2), None); // no February 30 - /// ~~~~ - #[inline] - fn with_month(&self, month: u32) -> Option { - self.date.with_month(month).map(|d| NaiveDateTime { date: d, ..*self }) - } - - /// Makes a new `NaiveDateTime` with the month number (starting from 0) changed. - /// - /// Returns `None` when the resulting `NaiveDateTime` would be invalid. - /// - /// See also the - /// [`NaiveDate::with_month0`](./struct.NaiveDate.html#method.with_month0) method. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 30).and_hms(12, 34, 56); - /// assert_eq!(dt.with_month0(9), Some(NaiveDate::from_ymd(2015, 10, 30).and_hms(12, 34, 56))); - /// assert_eq!(dt.with_month0(12), None); // no month 13 - /// assert_eq!(dt.with_month0(1), None); // no February 30 - /// ~~~~ - #[inline] - fn with_month0(&self, month0: u32) -> Option { - self.date.with_month0(month0).map(|d| NaiveDateTime { date: d, ..*self }) - } - - /// Makes a new `NaiveDateTime` with the day of month (starting from 1) changed. - /// - /// Returns `None` when the resulting `NaiveDateTime` would be invalid. - /// - /// See also the - /// [`NaiveDate::with_day`](./struct.NaiveDate.html#method.with_day) method. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 8).and_hms(12, 34, 56); - /// assert_eq!(dt.with_day(30), Some(NaiveDate::from_ymd(2015, 9, 30).and_hms(12, 34, 56))); - /// assert_eq!(dt.with_day(31), None); // no September 31 - /// ~~~~ - #[inline] - fn with_day(&self, day: u32) -> Option { - self.date.with_day(day).map(|d| NaiveDateTime { date: d, ..*self }) - } - - /// Makes a new `NaiveDateTime` with the day of month (starting from 0) changed. - /// - /// Returns `None` when the resulting `NaiveDateTime` would be invalid. - /// - /// See also the - /// [`NaiveDate::with_day0`](./struct.NaiveDate.html#method.with_day0) method. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 8).and_hms(12, 34, 56); - /// assert_eq!(dt.with_day0(29), Some(NaiveDate::from_ymd(2015, 9, 30).and_hms(12, 34, 56))); - /// assert_eq!(dt.with_day0(30), None); // no September 31 - /// ~~~~ - #[inline] - fn with_day0(&self, day0: u32) -> Option { - self.date.with_day0(day0).map(|d| NaiveDateTime { date: d, ..*self }) - } - - /// Makes a new `NaiveDateTime` with the day of year (starting from 1) changed. - /// - /// Returns `None` when the resulting `NaiveDateTime` would be invalid. - /// - /// See also the - /// [`NaiveDate::with_ordinal`](./struct.NaiveDate.html#method.with_ordinal) method. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 8).and_hms(12, 34, 56); - /// assert_eq!(dt.with_ordinal(60), - /// Some(NaiveDate::from_ymd(2015, 3, 1).and_hms(12, 34, 56))); - /// assert_eq!(dt.with_ordinal(366), None); // 2015 had only 365 days - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2016, 9, 8).and_hms(12, 34, 56); - /// assert_eq!(dt.with_ordinal(60), - /// Some(NaiveDate::from_ymd(2016, 2, 29).and_hms(12, 34, 56))); - /// assert_eq!(dt.with_ordinal(366), - /// Some(NaiveDate::from_ymd(2016, 12, 31).and_hms(12, 34, 56))); - /// ~~~~ - #[inline] - fn with_ordinal(&self, ordinal: u32) -> Option { - self.date.with_ordinal(ordinal).map(|d| NaiveDateTime { date: d, ..*self }) - } - - /// Makes a new `NaiveDateTime` with the day of year (starting from 0) changed. - /// - /// Returns `None` when the resulting `NaiveDateTime` would be invalid. - /// - /// See also the - /// [`NaiveDate::with_ordinal0`](./struct.NaiveDate.html#method.with_ordinal0) method. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, NaiveDateTime, Datelike}; - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 8).and_hms(12, 34, 56); - /// assert_eq!(dt.with_ordinal0(59), - /// Some(NaiveDate::from_ymd(2015, 3, 1).and_hms(12, 34, 56))); - /// assert_eq!(dt.with_ordinal0(365), None); // 2015 had only 365 days - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2016, 9, 8).and_hms(12, 34, 56); - /// assert_eq!(dt.with_ordinal0(59), - /// Some(NaiveDate::from_ymd(2016, 2, 29).and_hms(12, 34, 56))); - /// assert_eq!(dt.with_ordinal0(365), - /// Some(NaiveDate::from_ymd(2016, 12, 31).and_hms(12, 34, 56))); - /// ~~~~ - #[inline] - fn with_ordinal0(&self, ordinal0: u32) -> Option { - self.date.with_ordinal0(ordinal0).map(|d| NaiveDateTime { date: d, ..*self }) - } -} - -impl Timelike for NaiveDateTime { - /// Returns the hour number from 0 to 23. - /// - /// See also the [`NaiveTime::hour`](./struct.NaiveTime.html#method.hour) method. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 8).and_hms_milli(12, 34, 56, 789); - /// assert_eq!(dt.hour(), 12); - /// ~~~~ - #[inline] - fn hour(&self) -> u32 { - self.time.hour() - } - - /// Returns the minute number from 0 to 59. - /// - /// See also the [`NaiveTime::minute`](./struct.NaiveTime.html#method.minute) method. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 8).and_hms_milli(12, 34, 56, 789); - /// assert_eq!(dt.minute(), 34); - /// ~~~~ - #[inline] - fn minute(&self) -> u32 { - self.time.minute() - } - - /// Returns the second number from 0 to 59. - /// - /// See also the [`NaiveTime::second`](./struct.NaiveTime.html#method.second) method. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 8).and_hms_milli(12, 34, 56, 789); - /// assert_eq!(dt.second(), 56); - /// ~~~~ - #[inline] - fn second(&self) -> u32 { - self.time.second() - } - - /// Returns the number of nanoseconds since the whole non-leap second. - /// The range from 1,000,000,000 to 1,999,999,999 represents - /// the [leap second](./struct.NaiveTime.html#leap-second-handling). - /// - /// See also the - /// [`NaiveTime::nanosecond`](./struct.NaiveTime.html#method.nanosecond) method. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 8).and_hms_milli(12, 34, 56, 789); - /// assert_eq!(dt.nanosecond(), 789_000_000); - /// ~~~~ - #[inline] - fn nanosecond(&self) -> u32 { - self.time.nanosecond() - } - - /// Makes a new `NaiveDateTime` with the hour number changed. - /// - /// Returns `None` when the resulting `NaiveDateTime` would be invalid. - /// - /// See also the - /// [`NaiveTime::with_hour`](./struct.NaiveTime.html#method.with_hour) method. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 8).and_hms_milli(12, 34, 56, 789); - /// assert_eq!(dt.with_hour(7), - /// Some(NaiveDate::from_ymd(2015, 9, 8).and_hms_milli(7, 34, 56, 789))); - /// assert_eq!(dt.with_hour(24), None); - /// ~~~~ - #[inline] - fn with_hour(&self, hour: u32) -> Option { - self.time.with_hour(hour).map(|t| NaiveDateTime { time: t, ..*self }) - } - - /// Makes a new `NaiveDateTime` with the minute number changed. - /// - /// Returns `None` when the resulting `NaiveDateTime` would be invalid. - /// - /// See also the - /// [`NaiveTime::with_minute`](./struct.NaiveTime.html#method.with_minute) method. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 8).and_hms_milli(12, 34, 56, 789); - /// assert_eq!(dt.with_minute(45), - /// Some(NaiveDate::from_ymd(2015, 9, 8).and_hms_milli(12, 45, 56, 789))); - /// assert_eq!(dt.with_minute(60), None); - /// ~~~~ - #[inline] - fn with_minute(&self, min: u32) -> Option { - self.time.with_minute(min).map(|t| NaiveDateTime { time: t, ..*self }) - } - - /// Makes a new `NaiveDateTime` with the second number changed. - /// - /// Returns `None` when the resulting `NaiveDateTime` would be invalid. - /// As with the [`second`](#method.second) method, - /// the input range is restricted to 0 through 59. - /// - /// See also the - /// [`NaiveTime::with_second`](./struct.NaiveTime.html#method.with_second) method. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 8).and_hms_milli(12, 34, 56, 789); - /// assert_eq!(dt.with_second(17), - /// Some(NaiveDate::from_ymd(2015, 9, 8).and_hms_milli(12, 34, 17, 789))); - /// assert_eq!(dt.with_second(60), None); - /// ~~~~ - #[inline] - fn with_second(&self, sec: u32) -> Option { - self.time.with_second(sec).map(|t| NaiveDateTime { time: t, ..*self }) - } - - /// Makes a new `NaiveDateTime` with nanoseconds since the whole non-leap second changed. - /// - /// Returns `None` when the resulting `NaiveDateTime` would be invalid. - /// As with the [`nanosecond`](#method.nanosecond) method, - /// the input range can exceed 1,000,000,000 for leap seconds. - /// - /// See also the - /// [`NaiveTime::with_nanosecond`](./struct.NaiveTime.html#method.with_nanosecond) - /// method. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; - /// - /// let dt: NaiveDateTime = NaiveDate::from_ymd(2015, 9, 8).and_hms_milli(12, 34, 56, 789); - /// assert_eq!(dt.with_nanosecond(333_333_333), - /// Some(NaiveDate::from_ymd(2015, 9, 8).and_hms_nano(12, 34, 56, 333_333_333))); - /// assert_eq!(dt.with_nanosecond(1_333_333_333), // leap second - /// Some(NaiveDate::from_ymd(2015, 9, 8).and_hms_nano(12, 34, 56, 1_333_333_333))); - /// assert_eq!(dt.with_nanosecond(2_000_000_000), None); - /// ~~~~ - #[inline] - fn with_nanosecond(&self, nano: u32) -> Option { - self.time.with_nanosecond(nano).map(|t| NaiveDateTime { time: t, ..*self }) - } -} - -/// `NaiveDateTime` can be used as a key to the hash maps (in principle). -/// -/// Practically this also takes account of fractional seconds, so it is not recommended. -/// (For the obvious reason this also distinguishes leap seconds from non-leap seconds.) -impl hash::Hash for NaiveDateTime { - fn hash(&self, state: &mut H) { - self.date.hash(state); - self.time.hash(state); - } -} - -/// An addition of `Duration` to `NaiveDateTime` yields another `NaiveDateTime`. -/// -/// As a part of Chrono's [leap second handling](./struct.NaiveTime.html#leap-second-handling), -/// the addition assumes that **there is no leap second ever**, -/// except when the `NaiveDateTime` itself represents a leap second -/// in which case the assumption becomes that **there is exactly a single leap second ever**. -/// -/// Panics on underflow or overflow. -/// Use [`NaiveDateTime::checked_add_signed`](#method.checked_add_signed) to detect that. -/// -/// # Example -/// -/// ~~~~ -/// # extern crate chrono; fn main() { -/// use chrono::{Duration, NaiveDate}; -/// -/// let from_ymd = NaiveDate::from_ymd; -/// -/// let d = from_ymd(2016, 7, 8); -/// let hms = |h, m, s| d.and_hms(h, m, s); -/// assert_eq!(hms(3, 5, 7) + Duration::zero(), hms(3, 5, 7)); -/// assert_eq!(hms(3, 5, 7) + Duration::seconds(1), hms(3, 5, 8)); -/// assert_eq!(hms(3, 5, 7) + Duration::seconds(-1), hms(3, 5, 6)); -/// assert_eq!(hms(3, 5, 7) + Duration::seconds(3600 + 60), hms(4, 6, 7)); -/// assert_eq!(hms(3, 5, 7) + Duration::seconds(86_400), -/// from_ymd(2016, 7, 9).and_hms(3, 5, 7)); -/// assert_eq!(hms(3, 5, 7) + Duration::days(365), -/// from_ymd(2017, 7, 8).and_hms(3, 5, 7)); -/// -/// let hmsm = |h, m, s, milli| d.and_hms_milli(h, m, s, milli); -/// assert_eq!(hmsm(3, 5, 7, 980) + Duration::milliseconds(450), hmsm(3, 5, 8, 430)); -/// # } -/// ~~~~ -/// -/// Leap seconds are handled, -/// but the addition assumes that it is the only leap second happened. -/// -/// ~~~~ -/// # extern crate chrono; fn main() { -/// # use chrono::{Duration, NaiveDate}; -/// # let from_ymd = NaiveDate::from_ymd; -/// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli(h, m, s, milli); -/// let leap = hmsm(3, 5, 59, 1_300); -/// assert_eq!(leap + Duration::zero(), hmsm(3, 5, 59, 1_300)); -/// assert_eq!(leap + Duration::milliseconds(-500), hmsm(3, 5, 59, 800)); -/// assert_eq!(leap + Duration::milliseconds(500), hmsm(3, 5, 59, 1_800)); -/// assert_eq!(leap + Duration::milliseconds(800), hmsm(3, 6, 0, 100)); -/// assert_eq!(leap + Duration::seconds(10), hmsm(3, 6, 9, 300)); -/// assert_eq!(leap + Duration::seconds(-10), hmsm(3, 5, 50, 300)); -/// assert_eq!(leap + Duration::days(1), -/// from_ymd(2016, 7, 9).and_hms_milli(3, 5, 59, 300)); -/// # } -/// ~~~~ -impl Add for NaiveDateTime { - type Output = NaiveDateTime; - - #[inline] - fn add(self, rhs: OldDuration) -> NaiveDateTime { - self.checked_add_signed(rhs).expect("`NaiveDateTime + Duration` overflowed") - } -} - -impl AddAssign for NaiveDateTime { - #[inline] - fn add_assign(&mut self, rhs: OldDuration) { - *self = self.add(rhs); - } -} - -/// A subtraction of `Duration` from `NaiveDateTime` yields another `NaiveDateTime`. -/// It is the same as the addition with a negated `Duration`. -/// -/// As a part of Chrono's [leap second handling](./struct.NaiveTime.html#leap-second-handling), -/// the addition assumes that **there is no leap second ever**, -/// except when the `NaiveDateTime` itself represents a leap second -/// in which case the assumption becomes that **there is exactly a single leap second ever**. -/// -/// Panics on underflow or overflow. -/// Use [`NaiveDateTime::checked_sub_signed`](#method.checked_sub_signed) to detect that. -/// -/// # Example -/// -/// ~~~~ -/// # extern crate chrono; fn main() { -/// use chrono::{Duration, NaiveDate}; -/// -/// let from_ymd = NaiveDate::from_ymd; -/// -/// let d = from_ymd(2016, 7, 8); -/// let hms = |h, m, s| d.and_hms(h, m, s); -/// assert_eq!(hms(3, 5, 7) - Duration::zero(), hms(3, 5, 7)); -/// assert_eq!(hms(3, 5, 7) - Duration::seconds(1), hms(3, 5, 6)); -/// assert_eq!(hms(3, 5, 7) - Duration::seconds(-1), hms(3, 5, 8)); -/// assert_eq!(hms(3, 5, 7) - Duration::seconds(3600 + 60), hms(2, 4, 7)); -/// assert_eq!(hms(3, 5, 7) - Duration::seconds(86_400), -/// from_ymd(2016, 7, 7).and_hms(3, 5, 7)); -/// assert_eq!(hms(3, 5, 7) - Duration::days(365), -/// from_ymd(2015, 7, 9).and_hms(3, 5, 7)); -/// -/// let hmsm = |h, m, s, milli| d.and_hms_milli(h, m, s, milli); -/// assert_eq!(hmsm(3, 5, 7, 450) - Duration::milliseconds(670), hmsm(3, 5, 6, 780)); -/// # } -/// ~~~~ -/// -/// Leap seconds are handled, -/// but the subtraction assumes that it is the only leap second happened. -/// -/// ~~~~ -/// # extern crate chrono; fn main() { -/// # use chrono::{Duration, NaiveDate}; -/// # let from_ymd = NaiveDate::from_ymd; -/// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli(h, m, s, milli); -/// let leap = hmsm(3, 5, 59, 1_300); -/// assert_eq!(leap - Duration::zero(), hmsm(3, 5, 59, 1_300)); -/// assert_eq!(leap - Duration::milliseconds(200), hmsm(3, 5, 59, 1_100)); -/// assert_eq!(leap - Duration::milliseconds(500), hmsm(3, 5, 59, 800)); -/// assert_eq!(leap - Duration::seconds(60), hmsm(3, 5, 0, 300)); -/// assert_eq!(leap - Duration::days(1), -/// from_ymd(2016, 7, 7).and_hms_milli(3, 6, 0, 300)); -/// # } -/// ~~~~ -impl Sub for NaiveDateTime { - type Output = NaiveDateTime; - - #[inline] - fn sub(self, rhs: OldDuration) -> NaiveDateTime { - self.checked_sub_signed(rhs).expect("`NaiveDateTime - Duration` overflowed") - } -} - -impl SubAssign for NaiveDateTime { - #[inline] - fn sub_assign(&mut self, rhs: OldDuration) { - *self = self.sub(rhs); - } -} - -/// Subtracts another `NaiveDateTime` from the current date and time. -/// This does not overflow or underflow at all. -/// -/// As a part of Chrono's [leap second handling](./struct.NaiveTime.html#leap-second-handling), -/// the subtraction assumes that **there is no leap second ever**, -/// except when any of the `NaiveDateTime`s themselves represents a leap second -/// in which case the assumption becomes that -/// **there are exactly one (or two) leap second(s) ever**. -/// -/// The implementation is a wrapper around -/// [`NaiveDateTime::signed_duration_since`](#method.signed_duration_since). -/// -/// # Example -/// -/// ~~~~ -/// # extern crate chrono; fn main() { -/// use chrono::{Duration, NaiveDate}; -/// -/// let from_ymd = NaiveDate::from_ymd; -/// -/// let d = from_ymd(2016, 7, 8); -/// assert_eq!(d.and_hms(3, 5, 7) - d.and_hms(2, 4, 6), Duration::seconds(3600 + 60 + 1)); -/// -/// // July 8 is 190th day in the year 2016 -/// let d0 = from_ymd(2016, 1, 1); -/// assert_eq!(d.and_hms_milli(0, 7, 6, 500) - d0.and_hms(0, 0, 0), -/// Duration::seconds(189 * 86_400 + 7 * 60 + 6) + Duration::milliseconds(500)); -/// # } -/// ~~~~ -/// -/// Leap seconds are handled, but the subtraction assumes that -/// there were no other leap seconds happened. -/// -/// ~~~~ -/// # extern crate chrono; fn main() { -/// # use chrono::{Duration, NaiveDate}; -/// # let from_ymd = NaiveDate::from_ymd; -/// let leap = from_ymd(2015, 6, 30).and_hms_milli(23, 59, 59, 1_500); -/// assert_eq!(leap - from_ymd(2015, 6, 30).and_hms(23, 0, 0), -/// Duration::seconds(3600) + Duration::milliseconds(500)); -/// assert_eq!(from_ymd(2015, 7, 1).and_hms(1, 0, 0) - leap, -/// Duration::seconds(3600) - Duration::milliseconds(500)); -/// # } -/// ~~~~ -impl Sub for NaiveDateTime { - type Output = OldDuration; - - #[inline] - fn sub(self, rhs: NaiveDateTime) -> OldDuration { - self.signed_duration_since(rhs) - } -} - -/// The `Debug` output of the naive date and time `dt` is the same as -/// [`dt.format("%Y-%m-%dT%H:%M:%S%.f")`](../format/strftime/index.html). -/// -/// The string printed can be readily parsed via the `parse` method on `str`. -/// -/// It should be noted that, for leap seconds not on the minute boundary, -/// it may print a representation not distinguishable from non-leap seconds. -/// This doesn't matter in practice, since such leap seconds never happened. -/// (By the time of the first leap second on 1972-06-30, -/// every time zone offset around the world has standardized to the 5-minute alignment.) -/// -/// # Example -/// -/// ~~~~ -/// use chrono::NaiveDate; -/// -/// let dt = NaiveDate::from_ymd(2016, 11, 15).and_hms(7, 39, 24); -/// assert_eq!(format!("{:?}", dt), "2016-11-15T07:39:24"); -/// ~~~~ -/// -/// Leap seconds may also be used. -/// -/// ~~~~ -/// # use chrono::NaiveDate; -/// let dt = NaiveDate::from_ymd(2015, 6, 30).and_hms_milli(23, 59, 59, 1_500); -/// assert_eq!(format!("{:?}", dt), "2015-06-30T23:59:60.500"); -/// ~~~~ -impl fmt::Debug for NaiveDateTime { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:?}T{:?}", self.date, self.time) - } -} - -/// The `Display` output of the naive date and time `dt` is the same as -/// [`dt.format("%Y-%m-%d %H:%M:%S%.f")`](../format/strftime/index.html). -/// -/// It should be noted that, for leap seconds not on the minute boundary, -/// it may print a representation not distinguishable from non-leap seconds. -/// This doesn't matter in practice, since such leap seconds never happened. -/// (By the time of the first leap second on 1972-06-30, -/// every time zone offset around the world has standardized to the 5-minute alignment.) -/// -/// # Example -/// -/// ~~~~ -/// use chrono::NaiveDate; -/// -/// let dt = NaiveDate::from_ymd(2016, 11, 15).and_hms(7, 39, 24); -/// assert_eq!(format!("{}", dt), "2016-11-15 07:39:24"); -/// ~~~~ -/// -/// Leap seconds may also be used. -/// -/// ~~~~ -/// # use chrono::NaiveDate; -/// let dt = NaiveDate::from_ymd(2015, 6, 30).and_hms_milli(23, 59, 59, 1_500); -/// assert_eq!(format!("{}", dt), "2015-06-30 23:59:60.500"); -/// ~~~~ -impl fmt::Display for NaiveDateTime { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{} {}", self.date, self.time) - } -} - -/// Parsing a `str` into a `NaiveDateTime` uses the same format, -/// [`%Y-%m-%dT%H:%M:%S%.f`](../format/strftime/index.html), as in `Debug`. -/// -/// # Example -/// -/// ~~~~ -/// use chrono::{NaiveDateTime, NaiveDate}; -/// -/// let dt = NaiveDate::from_ymd(2015, 9, 18).and_hms(23, 56, 4); -/// assert_eq!("2015-09-18T23:56:04".parse::(), Ok(dt)); -/// -/// let dt = NaiveDate::from_ymd(12345, 6, 7).and_hms_milli(7, 59, 59, 1_500); // leap second -/// assert_eq!("+12345-6-7T7:59:60.5".parse::(), Ok(dt)); -/// -/// assert!("foo".parse::().is_err()); -/// ~~~~ -impl str::FromStr for NaiveDateTime { - type Err = ParseError; - - fn from_str(s: &str) -> ParseResult { - const ITEMS: &'static [Item<'static>] = &[ - Item::Numeric(Numeric::Year, Pad::Zero), - Item::Space(""), - Item::Literal("-"), - Item::Numeric(Numeric::Month, Pad::Zero), - Item::Space(""), - Item::Literal("-"), - Item::Numeric(Numeric::Day, Pad::Zero), - Item::Space(""), - Item::Literal("T"), // XXX shouldn't this be case-insensitive? - Item::Numeric(Numeric::Hour, Pad::Zero), - Item::Space(""), - Item::Literal(":"), - Item::Numeric(Numeric::Minute, Pad::Zero), - Item::Space(""), - Item::Literal(":"), - Item::Numeric(Numeric::Second, Pad::Zero), - Item::Fixed(Fixed::Nanosecond), - Item::Space(""), - ]; - - let mut parsed = Parsed::new(); - parse(&mut parsed, s, ITEMS.iter())?; - parsed.to_naive_datetime_with_offset(0) - } -} - -#[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))] -fn test_encodable_json(to_string: F) -where - F: Fn(&NaiveDateTime) -> Result, - E: ::std::fmt::Debug, -{ - use naive::{MAX_DATE, MIN_DATE}; - - assert_eq!( - to_string(&NaiveDate::from_ymd(2016, 7, 8).and_hms_milli(9, 10, 48, 90)).ok(), - Some(r#""2016-07-08T09:10:48.090""#.into()) - ); - assert_eq!( - to_string(&NaiveDate::from_ymd(2014, 7, 24).and_hms(12, 34, 6)).ok(), - Some(r#""2014-07-24T12:34:06""#.into()) - ); - assert_eq!( - to_string(&NaiveDate::from_ymd(0, 1, 1).and_hms_milli(0, 0, 59, 1_000)).ok(), - Some(r#""0000-01-01T00:00:60""#.into()) - ); - assert_eq!( - to_string(&NaiveDate::from_ymd(-1, 12, 31).and_hms_nano(23, 59, 59, 7)).ok(), - Some(r#""-0001-12-31T23:59:59.000000007""#.into()) - ); - assert_eq!( - to_string(&MIN_DATE.and_hms(0, 0, 0)).ok(), - Some(r#""-262144-01-01T00:00:00""#.into()) - ); - assert_eq!( - to_string(&MAX_DATE.and_hms_nano(23, 59, 59, 1_999_999_999)).ok(), - Some(r#""+262143-12-31T23:59:60.999999999""#.into()) - ); -} - -#[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))] -fn test_decodable_json(from_str: F) -where - F: Fn(&str) -> Result, - E: ::std::fmt::Debug, -{ - use naive::{MAX_DATE, MIN_DATE}; - - assert_eq!( - from_str(r#""2016-07-08T09:10:48.090""#).ok(), - Some(NaiveDate::from_ymd(2016, 7, 8).and_hms_milli(9, 10, 48, 90)) - ); - assert_eq!( - from_str(r#""2016-7-8T9:10:48.09""#).ok(), - Some(NaiveDate::from_ymd(2016, 7, 8).and_hms_milli(9, 10, 48, 90)) - ); - assert_eq!( - from_str(r#""2014-07-24T12:34:06""#).ok(), - Some(NaiveDate::from_ymd(2014, 7, 24).and_hms(12, 34, 6)) - ); - assert_eq!( - from_str(r#""0000-01-01T00:00:60""#).ok(), - Some(NaiveDate::from_ymd(0, 1, 1).and_hms_milli(0, 0, 59, 1_000)) - ); - assert_eq!( - from_str(r#""0-1-1T0:0:60""#).ok(), - Some(NaiveDate::from_ymd(0, 1, 1).and_hms_milli(0, 0, 59, 1_000)) - ); - assert_eq!( - from_str(r#""-0001-12-31T23:59:59.000000007""#).ok(), - Some(NaiveDate::from_ymd(-1, 12, 31).and_hms_nano(23, 59, 59, 7)) - ); - assert_eq!(from_str(r#""-262144-01-01T00:00:00""#).ok(), Some(MIN_DATE.and_hms(0, 0, 0))); - assert_eq!( - from_str(r#""+262143-12-31T23:59:60.999999999""#).ok(), - Some(MAX_DATE.and_hms_nano(23, 59, 59, 1_999_999_999)) - ); - assert_eq!( - from_str(r#""+262143-12-31T23:59:60.9999999999997""#).ok(), // excess digits are ignored - Some(MAX_DATE.and_hms_nano(23, 59, 59, 1_999_999_999)) - ); - - // bad formats - assert!(from_str(r#""""#).is_err()); - assert!(from_str(r#""2016-07-08""#).is_err()); - assert!(from_str(r#""09:10:48.090""#).is_err()); - assert!(from_str(r#""20160708T091048.090""#).is_err()); - assert!(from_str(r#""2000-00-00T00:00:00""#).is_err()); - assert!(from_str(r#""2000-02-30T00:00:00""#).is_err()); - assert!(from_str(r#""2001-02-29T00:00:00""#).is_err()); - assert!(from_str(r#""2002-02-28T24:00:00""#).is_err()); - assert!(from_str(r#""2002-02-28T23:60:00""#).is_err()); - assert!(from_str(r#""2002-02-28T23:59:61""#).is_err()); - assert!(from_str(r#""2016-07-08T09:10:48,090""#).is_err()); - assert!(from_str(r#""2016-07-08 09:10:48.090""#).is_err()); - assert!(from_str(r#""2016-007-08T09:10:48.090""#).is_err()); - assert!(from_str(r#""yyyy-mm-ddThh:mm:ss.fffffffff""#).is_err()); - assert!(from_str(r#"20160708000000"#).is_err()); - assert!(from_str(r#"{}"#).is_err()); - // pre-0.3.0 rustc-serialize format is now invalid - assert!(from_str(r#"{"date":{"ymdf":20},"time":{"secs":0,"frac":0}}"#).is_err()); - assert!(from_str(r#"null"#).is_err()); -} - -#[cfg(all(test, feature = "rustc-serialize"))] -fn test_decodable_json_timestamp(from_str: F) -where - F: Fn(&str) -> Result, - E: ::std::fmt::Debug, -{ - assert_eq!( - *from_str("0").unwrap(), - NaiveDate::from_ymd(1970, 1, 1).and_hms(0, 0, 0), - "should parse integers as timestamps" - ); - assert_eq!( - *from_str("-1").unwrap(), - NaiveDate::from_ymd(1969, 12, 31).and_hms(23, 59, 59), - "should parse integers as timestamps" - ); -} - -#[cfg(feature = "rustc-serialize")] -pub mod rustc_serialize { - use super::NaiveDateTime; - use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; - use std::ops::Deref; - - impl Encodable for NaiveDateTime { - fn encode(&self, s: &mut S) -> Result<(), S::Error> { - format!("{:?}", self).encode(s) - } - } - - impl Decodable for NaiveDateTime { - fn decode(d: &mut D) -> Result { - d.read_str()?.parse().map_err(|_| d.error("invalid date time string")) - } - } - - /// A `DateTime` that can be deserialized from a seconds-based timestamp - #[derive(Debug)] - #[deprecated( - since = "1.4.2", - note = "RustcSerialize will be removed before chrono 1.0, use Serde instead" - )] - pub struct TsSeconds(NaiveDateTime); - - #[allow(deprecated)] - impl From for NaiveDateTime { - /// Pull the internal NaiveDateTime out - #[allow(deprecated)] - fn from(obj: TsSeconds) -> NaiveDateTime { - obj.0 - } - } - - #[allow(deprecated)] - impl Deref for TsSeconds { - type Target = NaiveDateTime; - - #[allow(deprecated)] - fn deref(&self) -> &Self::Target { - &self.0 - } - } - - #[allow(deprecated)] - impl Decodable for TsSeconds { - #[allow(deprecated)] - fn decode(d: &mut D) -> Result { - Ok(TsSeconds( - NaiveDateTime::from_timestamp_opt(d.read_i64()?, 0) - .ok_or_else(|| d.error("invalid timestamp"))?, - )) - } - } - - #[cfg(test)] - use rustc_serialize::json; - - #[test] - fn test_encodable() { - super::test_encodable_json(json::encode); - } - - #[test] - fn test_decodable() { - super::test_decodable_json(json::decode); - } - - #[test] - fn test_decodable_timestamps() { - super::test_decodable_json_timestamp(json::decode); - } -} - -/// Tools to help serializing/deserializing `NaiveDateTime`s -#[cfg(feature = "serde")] -pub mod serde { - use super::NaiveDateTime; - use core::fmt; - use serdelib::{de, ser}; - - /// Serialize a `NaiveDateTime` as an RFC 3339 string - /// - /// See [the `serde` module](./serde/index.html) for alternate - /// serialization formats. - impl ser::Serialize for NaiveDateTime { - fn serialize(&self, serializer: S) -> Result - where - S: ser::Serializer, - { - struct FormatWrapped<'a, D: 'a> { - inner: &'a D, - } - - impl<'a, D: fmt::Debug> fmt::Display for FormatWrapped<'a, D> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.inner.fmt(f) - } - } - - serializer.collect_str(&FormatWrapped { inner: &self }) - } - } - - struct NaiveDateTimeVisitor; - - impl<'de> de::Visitor<'de> for NaiveDateTimeVisitor { - type Value = NaiveDateTime; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - write!(formatter, "a formatted date and time string") - } - - fn visit_str(self, value: &str) -> Result - where - E: de::Error, - { - value.parse().map_err(E::custom) - } - } - - impl<'de> de::Deserialize<'de> for NaiveDateTime { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - deserializer.deserialize_str(NaiveDateTimeVisitor) - } - } - - /// Used to serialize/deserialize from nanosecond-precision timestamps - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # extern crate serde_json; - /// # extern crate serde; - /// # extern crate chrono; - /// # use chrono::{TimeZone, NaiveDate, NaiveDateTime, Utc}; - /// use chrono::naive::serde::ts_nanoseconds; - /// #[derive(Deserialize, Serialize)] - /// struct S { - /// #[serde(with = "ts_nanoseconds")] - /// time: NaiveDateTime - /// } - /// - /// # fn example() -> Result { - /// let time = NaiveDate::from_ymd(2018, 5, 17).and_hms_nano(02, 04, 59, 918355733); - /// let my_s = S { - /// time: time.clone(), - /// }; - /// - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); - /// let my_s: S = serde_json::from_str(&as_string)?; - /// assert_eq!(my_s.time, time); - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub mod ts_nanoseconds { - use core::fmt; - use serdelib::{de, ser}; - - use {ne_timestamp, NaiveDateTime}; - - /// Serialize a UTC datetime into an integer number of nanoseconds since the epoch - /// - /// Intended for use with `serde`s `serialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # #[macro_use] extern crate serde; - /// # extern crate chrono; - /// # use chrono::{TimeZone, NaiveDate, NaiveDateTime, Utc}; - /// # use serde::Serialize; - /// use chrono::naive::serde::ts_nanoseconds::serialize as to_nano_ts; - /// #[derive(Serialize)] - /// struct S { - /// #[serde(serialize_with = "to_nano_ts")] - /// time: NaiveDateTime - /// } - /// - /// # fn example() -> Result { - /// let my_s = S { - /// time: NaiveDate::from_ymd(2018, 5, 17).and_hms_nano(02, 04, 59, 918355733), - /// }; - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); - /// # Ok(as_string) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn serialize(dt: &NaiveDateTime, serializer: S) -> Result - where - S: ser::Serializer, - { - serializer.serialize_i64(dt.timestamp_nanos()) - } - - /// Deserialize a `DateTime` from a nanoseconds timestamp - /// - /// Intended for use with `serde`s `deserialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate serde; - /// # extern crate chrono; - /// # use chrono::{NaiveDateTime, Utc}; - /// # use serde::Deserialize; - /// use chrono::naive::serde::ts_nanoseconds::deserialize as from_nano_ts; - /// #[derive(Deserialize)] - /// struct S { - /// #[serde(deserialize_with = "from_nano_ts")] - /// time: NaiveDateTime - /// } - /// - /// # fn example() -> Result { - /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355733 }"#)?; - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn deserialize<'de, D>(d: D) -> Result - where - D: de::Deserializer<'de>, - { - Ok(d.deserialize_i64(NaiveDateTimeFromNanoSecondsVisitor)?) - } - - struct NaiveDateTimeFromNanoSecondsVisitor; - - impl<'de> de::Visitor<'de> for NaiveDateTimeFromNanoSecondsVisitor { - type Value = NaiveDateTime; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a unix timestamp") - } - - fn visit_i64(self, value: i64) -> Result - where - E: de::Error, - { - NaiveDateTime::from_timestamp_opt( - value / 1_000_000_000, - (value % 1_000_000_000) as u32, - ) - .ok_or_else(|| E::custom(ne_timestamp(value))) - } - - fn visit_u64(self, value: u64) -> Result - where - E: de::Error, - { - NaiveDateTime::from_timestamp_opt( - value as i64 / 1_000_000_000, - (value as i64 % 1_000_000_000) as u32, - ) - .ok_or_else(|| E::custom(ne_timestamp(value))) - } - } - } - - /// Used to serialize/deserialize from millisecond-precision timestamps - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # extern crate serde_json; - /// # extern crate serde; - /// # extern crate chrono; - /// # use chrono::{TimeZone, NaiveDate, NaiveDateTime, Utc}; - /// use chrono::naive::serde::ts_milliseconds; - /// #[derive(Deserialize, Serialize)] - /// struct S { - /// #[serde(with = "ts_milliseconds")] - /// time: NaiveDateTime - /// } - /// - /// # fn example() -> Result { - /// let time = NaiveDate::from_ymd(2018, 5, 17).and_hms_milli(02, 04, 59, 918); - /// let my_s = S { - /// time: time.clone(), - /// }; - /// - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1526522699918}"#); - /// let my_s: S = serde_json::from_str(&as_string)?; - /// assert_eq!(my_s.time, time); - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub mod ts_milliseconds { - use core::fmt; - use serdelib::{de, ser}; - - use {ne_timestamp, NaiveDateTime}; - - /// Serialize a UTC datetime into an integer number of milliseconds since the epoch - /// - /// Intended for use with `serde`s `serialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # #[macro_use] extern crate serde; - /// # extern crate chrono; - /// # use chrono::{TimeZone, NaiveDate, NaiveDateTime, Utc}; - /// # use serde::Serialize; - /// use chrono::naive::serde::ts_milliseconds::serialize as to_milli_ts; - /// #[derive(Serialize)] - /// struct S { - /// #[serde(serialize_with = "to_milli_ts")] - /// time: NaiveDateTime - /// } - /// - /// # fn example() -> Result { - /// let my_s = S { - /// time: NaiveDate::from_ymd(2018, 5, 17).and_hms_milli(02, 04, 59, 918), - /// }; - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1526522699918}"#); - /// # Ok(as_string) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn serialize(dt: &NaiveDateTime, serializer: S) -> Result - where - S: ser::Serializer, - { - serializer.serialize_i64(dt.timestamp_millis()) - } - - /// Deserialize a `DateTime` from a milliseconds timestamp - /// - /// Intended for use with `serde`s `deserialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate serde; - /// # extern crate chrono; - /// # use chrono::{NaiveDateTime, Utc}; - /// # use serde::Deserialize; - /// use chrono::naive::serde::ts_milliseconds::deserialize as from_milli_ts; - /// #[derive(Deserialize)] - /// struct S { - /// #[serde(deserialize_with = "from_milli_ts")] - /// time: NaiveDateTime - /// } - /// - /// # fn example() -> Result { - /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918 }"#)?; - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn deserialize<'de, D>(d: D) -> Result - where - D: de::Deserializer<'de>, - { - Ok(d.deserialize_i64(NaiveDateTimeFromMilliSecondsVisitor)?) - } - - struct NaiveDateTimeFromMilliSecondsVisitor; - - impl<'de> de::Visitor<'de> for NaiveDateTimeFromMilliSecondsVisitor { - type Value = NaiveDateTime; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a unix timestamp") - } - - fn visit_i64(self, value: i64) -> Result - where - E: de::Error, - { - NaiveDateTime::from_timestamp_opt(value / 1000, ((value % 1000) * 1_000_000) as u32) - .ok_or_else(|| E::custom(ne_timestamp(value))) - } - - fn visit_u64(self, value: u64) -> Result - where - E: de::Error, - { - NaiveDateTime::from_timestamp_opt( - (value / 1000) as i64, - ((value % 1000) * 1_000_000) as u32, - ) - .ok_or_else(|| E::custom(ne_timestamp(value))) - } - } - } - - /// Used to serialize/deserialize from second-precision timestamps - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # extern crate serde_json; - /// # extern crate serde; - /// # extern crate chrono; - /// # use chrono::{TimeZone, NaiveDate, NaiveDateTime, Utc}; - /// use chrono::naive::serde::ts_seconds; - /// #[derive(Deserialize, Serialize)] - /// struct S { - /// #[serde(with = "ts_seconds")] - /// time: NaiveDateTime - /// } - /// - /// # fn example() -> Result { - /// let time = NaiveDate::from_ymd(2015, 5, 15).and_hms(10, 0, 0); - /// let my_s = S { - /// time: time.clone(), - /// }; - /// - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1431684000}"#); - /// let my_s: S = serde_json::from_str(&as_string)?; - /// assert_eq!(my_s.time, time); - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub mod ts_seconds { - use core::fmt; - use serdelib::{de, ser}; - - use {ne_timestamp, NaiveDateTime}; - - /// Serialize a UTC datetime into an integer number of seconds since the epoch - /// - /// Intended for use with `serde`s `serialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # #[macro_use] extern crate serde; - /// # extern crate chrono; - /// # use chrono::{TimeZone, NaiveDate, NaiveDateTime, Utc}; - /// # use serde::Serialize; - /// use chrono::naive::serde::ts_seconds::serialize as to_ts; - /// #[derive(Serialize)] - /// struct S { - /// #[serde(serialize_with = "to_ts")] - /// time: NaiveDateTime - /// } - /// - /// # fn example() -> Result { - /// let my_s = S { - /// time: NaiveDate::from_ymd(2015, 5, 15).and_hms(10, 0, 0), - /// }; - /// let as_string = serde_json::to_string(&my_s)?; - /// assert_eq!(as_string, r#"{"time":1431684000}"#); - /// # Ok(as_string) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn serialize(dt: &NaiveDateTime, serializer: S) -> Result - where - S: ser::Serializer, - { - serializer.serialize_i64(dt.timestamp()) - } - - /// Deserialize a `DateTime` from a seconds timestamp - /// - /// Intended for use with `serde`s `deserialize_with` attribute. - /// - /// # Example: - /// - /// ```rust - /// # // We mark this ignored so that we can test on 1.13 (which does not - /// # // support custom derive), and run tests with --ignored on beta and - /// # // nightly to actually trigger these. - /// # - /// # #[macro_use] extern crate serde_derive; - /// # #[macro_use] extern crate serde_json; - /// # extern crate serde; - /// # extern crate chrono; - /// # use chrono::{NaiveDateTime, Utc}; - /// # use serde::Deserialize; - /// use chrono::naive::serde::ts_seconds::deserialize as from_ts; - /// #[derive(Deserialize)] - /// struct S { - /// #[serde(deserialize_with = "from_ts")] - /// time: NaiveDateTime - /// } - /// - /// # fn example() -> Result { - /// let my_s: S = serde_json::from_str(r#"{ "time": 1431684000 }"#)?; - /// # Ok(my_s) - /// # } - /// # fn main() { example().unwrap(); } - /// ``` - pub fn deserialize<'de, D>(d: D) -> Result - where - D: de::Deserializer<'de>, - { - Ok(d.deserialize_i64(NaiveDateTimeFromSecondsVisitor)?) - } - - struct NaiveDateTimeFromSecondsVisitor; - - impl<'de> de::Visitor<'de> for NaiveDateTimeFromSecondsVisitor { - type Value = NaiveDateTime; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a unix timestamp") - } - - fn visit_i64(self, value: i64) -> Result - where - E: de::Error, - { - NaiveDateTime::from_timestamp_opt(value, 0) - .ok_or_else(|| E::custom(ne_timestamp(value))) - } - - fn visit_u64(self, value: u64) -> Result - where - E: de::Error, - { - NaiveDateTime::from_timestamp_opt(value as i64, 0) - .ok_or_else(|| E::custom(ne_timestamp(value))) - } - } - } - - #[cfg(test)] - extern crate bincode; - #[cfg(test)] - extern crate serde_derive; - #[cfg(test)] - extern crate serde_json; - - #[test] - fn test_serde_serialize() { - super::test_encodable_json(self::serde_json::to_string); - } - - #[test] - fn test_serde_deserialize() { - super::test_decodable_json(|input| self::serde_json::from_str(&input)); - } - - // Bincode is relevant to test separately from JSON because - // it is not self-describing. - #[test] - fn test_serde_bincode() { - use self::bincode::{deserialize, serialize, Infinite}; - use naive::NaiveDate; - - let dt = NaiveDate::from_ymd(2016, 7, 8).and_hms_milli(9, 10, 48, 90); - let encoded = serialize(&dt, Infinite).unwrap(); - let decoded: NaiveDateTime = deserialize(&encoded).unwrap(); - assert_eq!(dt, decoded); - } - - #[test] - fn test_serde_bincode_optional() { - use self::bincode::{deserialize, serialize, Infinite}; - use self::serde_derive::{Deserialize, Serialize}; - use prelude::*; - use serde::ts_nanoseconds_option; - - #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] - struct Test { - one: Option, - #[serde(with = "ts_nanoseconds_option")] - two: Option>, - } - - let expected = Test { one: Some(1), two: Some(Utc.ymd(1970, 1, 1).and_hms(0, 1, 1)) }; - let bytes: Vec = serialize(&expected, Infinite).unwrap(); - let actual = deserialize::(&(bytes)).unwrap(); - - assert_eq!(expected, actual); - } -} - -#[cfg(test)] -mod tests { - use super::NaiveDateTime; - use naive::{NaiveDate, MAX_DATE, MIN_DATE}; - use oldtime::Duration; - use std::i64; - use Datelike; - - #[test] - fn test_datetime_from_timestamp() { - let from_timestamp = |secs| NaiveDateTime::from_timestamp_opt(secs, 0); - let ymdhms = |y, m, d, h, n, s| NaiveDate::from_ymd(y, m, d).and_hms(h, n, s); - assert_eq!(from_timestamp(-1), Some(ymdhms(1969, 12, 31, 23, 59, 59))); - assert_eq!(from_timestamp(0), Some(ymdhms(1970, 1, 1, 0, 0, 0))); - assert_eq!(from_timestamp(1), Some(ymdhms(1970, 1, 1, 0, 0, 1))); - assert_eq!(from_timestamp(1_000_000_000), Some(ymdhms(2001, 9, 9, 1, 46, 40))); - assert_eq!(from_timestamp(0x7fffffff), Some(ymdhms(2038, 1, 19, 3, 14, 7))); - assert_eq!(from_timestamp(i64::MIN), None); - assert_eq!(from_timestamp(i64::MAX), None); - } - - #[test] - fn test_datetime_add() { - fn check( - (y, m, d, h, n, s): (i32, u32, u32, u32, u32, u32), - rhs: Duration, - result: Option<(i32, u32, u32, u32, u32, u32)>, - ) { - let lhs = NaiveDate::from_ymd(y, m, d).and_hms(h, n, s); - let sum = - result.map(|(y, m, d, h, n, s)| NaiveDate::from_ymd(y, m, d).and_hms(h, n, s)); - assert_eq!(lhs.checked_add_signed(rhs), sum); - assert_eq!(lhs.checked_sub_signed(-rhs), sum); - }; - - check( - (2014, 5, 6, 7, 8, 9), - Duration::seconds(3600 + 60 + 1), - Some((2014, 5, 6, 8, 9, 10)), - ); - check( - (2014, 5, 6, 7, 8, 9), - Duration::seconds(-(3600 + 60 + 1)), - Some((2014, 5, 6, 6, 7, 8)), - ); - check((2014, 5, 6, 7, 8, 9), Duration::seconds(86399), Some((2014, 5, 7, 7, 8, 8))); - check((2014, 5, 6, 7, 8, 9), Duration::seconds(86_400 * 10), Some((2014, 5, 16, 7, 8, 9))); - check((2014, 5, 6, 7, 8, 9), Duration::seconds(-86_400 * 10), Some((2014, 4, 26, 7, 8, 9))); - check((2014, 5, 6, 7, 8, 9), Duration::seconds(86_400 * 10), Some((2014, 5, 16, 7, 8, 9))); - - // overflow check - // assumes that we have correct values for MAX/MIN_DAYS_FROM_YEAR_0 from `naive::date`. - // (they are private constants, but the equivalence is tested in that module.) - let max_days_from_year_0 = MAX_DATE.signed_duration_since(NaiveDate::from_ymd(0, 1, 1)); - check((0, 1, 1, 0, 0, 0), max_days_from_year_0, Some((MAX_DATE.year(), 12, 31, 0, 0, 0))); - check( - (0, 1, 1, 0, 0, 0), - max_days_from_year_0 + Duration::seconds(86399), - Some((MAX_DATE.year(), 12, 31, 23, 59, 59)), - ); - check((0, 1, 1, 0, 0, 0), max_days_from_year_0 + Duration::seconds(86_400), None); - check((0, 1, 1, 0, 0, 0), Duration::max_value(), None); - - let min_days_from_year_0 = MIN_DATE.signed_duration_since(NaiveDate::from_ymd(0, 1, 1)); - check((0, 1, 1, 0, 0, 0), min_days_from_year_0, Some((MIN_DATE.year(), 1, 1, 0, 0, 0))); - check((0, 1, 1, 0, 0, 0), min_days_from_year_0 - Duration::seconds(1), None); - check((0, 1, 1, 0, 0, 0), Duration::min_value(), None); - } - - #[test] - fn test_datetime_sub() { - let ymdhms = |y, m, d, h, n, s| NaiveDate::from_ymd(y, m, d).and_hms(h, n, s); - let since = NaiveDateTime::signed_duration_since; - assert_eq!( - since(ymdhms(2014, 5, 6, 7, 8, 9), ymdhms(2014, 5, 6, 7, 8, 9)), - Duration::zero() - ); - assert_eq!( - since(ymdhms(2014, 5, 6, 7, 8, 10), ymdhms(2014, 5, 6, 7, 8, 9)), - Duration::seconds(1) - ); - assert_eq!( - since(ymdhms(2014, 5, 6, 7, 8, 9), ymdhms(2014, 5, 6, 7, 8, 10)), - Duration::seconds(-1) - ); - assert_eq!( - since(ymdhms(2014, 5, 7, 7, 8, 9), ymdhms(2014, 5, 6, 7, 8, 10)), - Duration::seconds(86399) - ); - assert_eq!( - since(ymdhms(2001, 9, 9, 1, 46, 39), ymdhms(1970, 1, 1, 0, 0, 0)), - Duration::seconds(999_999_999) - ); - } - - #[test] - fn test_datetime_addassignment() { - let ymdhms = |y, m, d, h, n, s| NaiveDate::from_ymd(y, m, d).and_hms(h, n, s); - let mut date = ymdhms(2016, 10, 1, 10, 10, 10); - date += Duration::minutes(10_000_000); - assert_eq!(date, ymdhms(2035, 10, 6, 20, 50, 10)); - date += Duration::days(10); - assert_eq!(date, ymdhms(2035, 10, 16, 20, 50, 10)); - } - - #[test] - fn test_datetime_subassignment() { - let ymdhms = |y, m, d, h, n, s| NaiveDate::from_ymd(y, m, d).and_hms(h, n, s); - let mut date = ymdhms(2016, 10, 1, 10, 10, 10); - date -= Duration::minutes(10_000_000); - assert_eq!(date, ymdhms(1997, 9, 26, 23, 30, 10)); - date -= Duration::days(10); - assert_eq!(date, ymdhms(1997, 9, 16, 23, 30, 10)); - } - - #[test] - fn test_datetime_timestamp() { - let to_timestamp = - |y, m, d, h, n, s| NaiveDate::from_ymd(y, m, d).and_hms(h, n, s).timestamp(); - assert_eq!(to_timestamp(1969, 12, 31, 23, 59, 59), -1); - assert_eq!(to_timestamp(1970, 1, 1, 0, 0, 0), 0); - assert_eq!(to_timestamp(1970, 1, 1, 0, 0, 1), 1); - assert_eq!(to_timestamp(2001, 9, 9, 1, 46, 40), 1_000_000_000); - assert_eq!(to_timestamp(2038, 1, 19, 3, 14, 7), 0x7fffffff); - } - - #[test] - fn test_datetime_from_str() { - // valid cases - let valid = [ - "2015-2-18T23:16:9.15", - "-77-02-18T23:16:09", - " +82701 - 05 - 6 T 15 : 9 : 60.898989898989 ", - ]; - for &s in &valid { - let d = match s.parse::() { - Ok(d) => d, - Err(e) => panic!("parsing `{}` has failed: {}", s, e), - }; - let s_ = format!("{:?}", d); - // `s` and `s_` may differ, but `s.parse()` and `s_.parse()` must be same - let d_ = match s_.parse::() { - Ok(d) => d, - Err(e) => { - panic!("`{}` is parsed into `{:?}`, but reparsing that has failed: {}", s, d, e) - } - }; - assert!( - d == d_, - "`{}` is parsed into `{:?}`, but reparsed result \ - `{:?}` does not match", - s, - d, - d_ - ); - } - - // some invalid cases - // since `ParseErrorKind` is private, all we can do is to check if there was an error - assert!("".parse::().is_err()); - assert!("x".parse::().is_err()); - assert!("15".parse::().is_err()); - assert!("15:8:9".parse::().is_err()); - assert!("15-8-9".parse::().is_err()); - assert!("2015-15-15T15:15:15".parse::().is_err()); - assert!("2012-12-12T12:12:12x".parse::().is_err()); - assert!("2012-123-12T12:12:12".parse::().is_err()); - assert!("+ 82701-123-12T12:12:12".parse::().is_err()); - assert!("+802701-123-12T12:12:12".parse::().is_err()); // out-of-bound - } - - #[test] - fn test_datetime_parse_from_str() { - let ymdhms = |y, m, d, h, n, s| NaiveDate::from_ymd(y, m, d).and_hms(h, n, s); - let ymdhmsn = - |y, m, d, h, n, s, nano| NaiveDate::from_ymd(y, m, d).and_hms_nano(h, n, s, nano); - assert_eq!( - NaiveDateTime::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), - Ok(ymdhms(2014, 5, 7, 12, 34, 56)) - ); // ignore offset - assert_eq!( - NaiveDateTime::parse_from_str("2015-W06-1 000000", "%G-W%V-%u%H%M%S"), - Ok(ymdhms(2015, 2, 2, 0, 0, 0)) - ); - assert_eq!( - NaiveDateTime::parse_from_str( - "Fri, 09 Aug 2013 23:54:35 GMT", - "%a, %d %b %Y %H:%M:%S GMT" - ), - Ok(ymdhms(2013, 8, 9, 23, 54, 35)) - ); - assert!(NaiveDateTime::parse_from_str( - "Sat, 09 Aug 2013 23:54:35 GMT", - "%a, %d %b %Y %H:%M:%S GMT" - ) - .is_err()); - assert!(NaiveDateTime::parse_from_str("2014-5-7 12:3456", "%Y-%m-%d %H:%M:%S").is_err()); - assert!(NaiveDateTime::parse_from_str("12:34:56", "%H:%M:%S").is_err()); // insufficient - assert_eq!( - NaiveDateTime::parse_from_str("1441497364", "%s"), - Ok(ymdhms(2015, 9, 5, 23, 56, 4)) - ); - assert_eq!( - NaiveDateTime::parse_from_str("1283929614.1234", "%s.%f"), - Ok(ymdhmsn(2010, 9, 8, 7, 6, 54, 1234)) - ); - assert_eq!( - NaiveDateTime::parse_from_str("1441497364.649", "%s%.3f"), - Ok(ymdhmsn(2015, 9, 5, 23, 56, 4, 649000000)) - ); - assert_eq!( - NaiveDateTime::parse_from_str("1497854303.087654", "%s%.6f"), - Ok(ymdhmsn(2017, 6, 19, 6, 38, 23, 87654000)) - ); - assert_eq!( - NaiveDateTime::parse_from_str("1437742189.918273645", "%s%.9f"), - Ok(ymdhmsn(2015, 7, 24, 12, 49, 49, 918273645)) - ); - } - - #[test] - fn test_datetime_format() { - let dt = NaiveDate::from_ymd(2010, 9, 8).and_hms_milli(7, 6, 54, 321); - assert_eq!(dt.format("%c").to_string(), "Wed Sep 8 07:06:54 2010"); - assert_eq!(dt.format("%s").to_string(), "1283929614"); - assert_eq!(dt.format("%t%n%%%n%t").to_string(), "\t\n%\n\t"); - - // a horror of leap second: coming near to you. - let dt = NaiveDate::from_ymd(2012, 6, 30).and_hms_milli(23, 59, 59, 1_000); - assert_eq!(dt.format("%c").to_string(), "Sat Jun 30 23:59:60 2012"); - assert_eq!(dt.format("%s").to_string(), "1341100799"); // not 1341100800, it's intentional. - } - - #[test] - fn test_datetime_add_sub_invariant() { - // issue #37 - let base = NaiveDate::from_ymd(2000, 1, 1).and_hms(0, 0, 0); - let t = -946684799990000; - let time = base + Duration::microseconds(t); - assert_eq!(t, time.signed_duration_since(base).num_microseconds().unwrap()); - } - - #[test] - fn test_nanosecond_range() { - const A_BILLION: i64 = 1_000_000_000; - let maximum = "2262-04-11T23:47:16.854775804"; - let parsed: NaiveDateTime = maximum.parse().unwrap(); - let nanos = parsed.timestamp_nanos(); - assert_eq!( - parsed, - NaiveDateTime::from_timestamp(nanos / A_BILLION, (nanos % A_BILLION) as u32) - ); - - let minimum = "1677-09-21T00:12:44.000000000"; - let parsed: NaiveDateTime = minimum.parse().unwrap(); - let nanos = parsed.timestamp_nanos(); - assert_eq!( - parsed, - NaiveDateTime::from_timestamp(nanos / A_BILLION, (nanos % A_BILLION) as u32) - ); - } -} diff --git a/third_party/rust/chrono/src/naive/datetime/mod.rs b/third_party/rust/chrono/src/naive/datetime/mod.rs new file mode 100644 index 00000000000..b634f8f4324 --- /dev/null +++ b/third_party/rust/chrono/src/naive/datetime/mod.rs @@ -0,0 +1,2151 @@ +// This is a part of Chrono. +// See README.md and LICENSE.txt for details. + +//! ISO 8601 date and time without timezone. + +#[cfg(feature = "alloc")] +use core::borrow::Borrow; +use core::fmt::Write; +use core::ops::{Add, AddAssign, Sub, SubAssign}; +use core::time::Duration; +use core::{fmt, str}; + +#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] +use rkyv::{Archive, Deserialize, Serialize}; + +#[cfg(feature = "alloc")] +use crate::format::DelayedFormat; +use crate::format::{Fixed, Item, Numeric, Pad}; +use crate::format::{ParseError, ParseResult, Parsed, StrftimeItems, parse, parse_and_remainder}; +use crate::naive::{Days, IsoWeek, NaiveDate, NaiveTime}; +use crate::offset::Utc; +use crate::time_delta::NANOS_PER_SEC; +use crate::{ + DateTime, Datelike, FixedOffset, MappedLocalTime, Months, TimeDelta, TimeZone, Timelike, + Weekday, expect, try_opt, +}; + +/// Tools to help serializing/deserializing `NaiveDateTime`s +#[cfg(feature = "serde")] +pub(crate) mod serde; + +#[cfg(test)] +mod tests; + +/// The minimum possible `NaiveDateTime`. +#[deprecated(since = "0.4.20", note = "Use NaiveDateTime::MIN instead")] +pub const MIN_DATETIME: NaiveDateTime = NaiveDateTime::MIN; +/// The maximum possible `NaiveDateTime`. +#[deprecated(since = "0.4.20", note = "Use NaiveDateTime::MAX instead")] +pub const MAX_DATETIME: NaiveDateTime = NaiveDateTime::MAX; + +/// ISO 8601 combined date and time without timezone. +/// +/// # Example +/// +/// `NaiveDateTime` is commonly created from [`NaiveDate`]. +/// +/// ``` +/// use chrono::{NaiveDate, NaiveDateTime}; +/// +/// let dt: NaiveDateTime = +/// NaiveDate::from_ymd_opt(2016, 7, 8).unwrap().and_hms_opt(9, 10, 11).unwrap(); +/// # let _ = dt; +/// ``` +/// +/// You can use typical [date-like](Datelike) and [time-like](Timelike) methods, +/// provided that relevant traits are in the scope. +/// +/// ``` +/// # use chrono::{NaiveDate, NaiveDateTime}; +/// # let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2016, 7, 8).unwrap().and_hms_opt(9, 10, 11).unwrap(); +/// use chrono::{Datelike, Timelike, Weekday}; +/// +/// assert_eq!(dt.weekday(), Weekday::Fri); +/// assert_eq!(dt.num_seconds_from_midnight(), 33011); +/// ``` +#[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)] +#[cfg_attr( + any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), + derive(Archive, Deserialize, Serialize), + archive(compare(PartialEq, PartialOrd)), + archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) +)] +#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] +#[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(arbitrary::Arbitrary))] +pub struct NaiveDateTime { + date: NaiveDate, + time: NaiveTime, +} + +impl NaiveDateTime { + /// Makes a new `NaiveDateTime` from date and time components. + /// Equivalent to [`date.and_time(time)`](./struct.NaiveDate.html#method.and_time) + /// and many other helper constructors on `NaiveDate`. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, NaiveDateTime, NaiveTime}; + /// + /// let d = NaiveDate::from_ymd_opt(2015, 6, 3).unwrap(); + /// let t = NaiveTime::from_hms_milli_opt(12, 34, 56, 789).unwrap(); + /// + /// let dt = NaiveDateTime::new(d, t); + /// assert_eq!(dt.date(), d); + /// assert_eq!(dt.time(), t); + /// ``` + #[inline] + pub const fn new(date: NaiveDate, time: NaiveTime) -> NaiveDateTime { + NaiveDateTime { date, time } + } + + /// Makes a new `NaiveDateTime` corresponding to a UTC date and time, + /// from the number of non-leap seconds + /// since the midnight UTC on January 1, 1970 (aka "UNIX timestamp") + /// and the number of nanoseconds since the last whole non-leap second. + /// + /// For a non-naive version of this function see [`TimeZone::timestamp`]. + /// + /// The nanosecond part can exceed 1,000,000,000 in order to represent a + /// [leap second](NaiveTime#leap-second-handling), but only when `secs % 60 == 59`. + /// (The true "UNIX timestamp" cannot represent a leap second unambiguously.) + /// + /// # Panics + /// + /// Panics if the number of seconds would be out of range for a `NaiveDateTime` (more than + /// ca. 262,000 years away from common era), and panics on an invalid nanosecond (2 seconds or + /// more). + #[deprecated(since = "0.4.23", note = "use `DateTime::from_timestamp` instead")] + #[inline] + #[must_use] + pub const fn from_timestamp(secs: i64, nsecs: u32) -> NaiveDateTime { + let datetime = + expect(DateTime::from_timestamp(secs, nsecs), "invalid or out-of-range datetime"); + datetime.naive_utc() + } + + /// Creates a new [NaiveDateTime] from milliseconds since the UNIX epoch. + /// + /// The UNIX epoch starts on midnight, January 1, 1970, UTC. + /// + /// # Errors + /// + /// Returns `None` if the number of milliseconds would be out of range for a `NaiveDateTime` + /// (more than ca. 262,000 years away from common era) + #[deprecated(since = "0.4.35", note = "use `DateTime::from_timestamp_millis` instead")] + #[inline] + #[must_use] + pub const fn from_timestamp_millis(millis: i64) -> Option { + Some(try_opt!(DateTime::from_timestamp_millis(millis)).naive_utc()) + } + + /// Creates a new [NaiveDateTime] from microseconds since the UNIX epoch. + /// + /// The UNIX epoch starts on midnight, January 1, 1970, UTC. + /// + /// # Errors + /// + /// Returns `None` if the number of microseconds would be out of range for a `NaiveDateTime` + /// (more than ca. 262,000 years away from common era) + #[deprecated(since = "0.4.35", note = "use `DateTime::from_timestamp_micros` instead")] + #[inline] + #[must_use] + pub const fn from_timestamp_micros(micros: i64) -> Option { + let secs = micros.div_euclid(1_000_000); + let nsecs = micros.rem_euclid(1_000_000) as u32 * 1000; + Some(try_opt!(DateTime::::from_timestamp(secs, nsecs)).naive_utc()) + } + + /// Creates a new [NaiveDateTime] from nanoseconds since the UNIX epoch. + /// + /// The UNIX epoch starts on midnight, January 1, 1970, UTC. + /// + /// # Errors + /// + /// Returns `None` if the number of nanoseconds would be out of range for a `NaiveDateTime` + /// (more than ca. 262,000 years away from common era) + #[deprecated(since = "0.4.35", note = "use `DateTime::from_timestamp_nanos` instead")] + #[inline] + #[must_use] + pub const fn from_timestamp_nanos(nanos: i64) -> Option { + let secs = nanos.div_euclid(NANOS_PER_SEC as i64); + let nsecs = nanos.rem_euclid(NANOS_PER_SEC as i64) as u32; + Some(try_opt!(DateTime::from_timestamp(secs, nsecs)).naive_utc()) + } + + /// Makes a new `NaiveDateTime` corresponding to a UTC date and time, + /// from the number of non-leap seconds + /// since the midnight UTC on January 1, 1970 (aka "UNIX timestamp") + /// and the number of nanoseconds since the last whole non-leap second. + /// + /// The nanosecond part can exceed 1,000,000,000 in order to represent a + /// [leap second](NaiveTime#leap-second-handling), but only when `secs % 60 == 59`. + /// (The true "UNIX timestamp" cannot represent a leap second unambiguously.) + /// + /// # Errors + /// + /// Returns `None` if the number of seconds would be out of range for a `NaiveDateTime` (more + /// than ca. 262,000 years away from common era), and panics on an invalid nanosecond + /// (2 seconds or more). + #[deprecated(since = "0.4.35", note = "use `DateTime::from_timestamp` instead")] + #[inline] + #[must_use] + pub const fn from_timestamp_opt(secs: i64, nsecs: u32) -> Option { + Some(try_opt!(DateTime::from_timestamp(secs, nsecs)).naive_utc()) + } + + /// Parses a string with the specified format string and returns a new `NaiveDateTime`. + /// See the [`format::strftime` module](crate::format::strftime) + /// on the supported escape sequences. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, NaiveDateTime}; + /// + /// let parse_from_str = NaiveDateTime::parse_from_str; + /// + /// assert_eq!( + /// parse_from_str("2015-09-05 23:56:04", "%Y-%m-%d %H:%M:%S"), + /// Ok(NaiveDate::from_ymd_opt(2015, 9, 5).unwrap().and_hms_opt(23, 56, 4).unwrap()) + /// ); + /// assert_eq!( + /// parse_from_str("5sep2015pm012345.6789", "%d%b%Y%p%I%M%S%.f"), + /// Ok(NaiveDate::from_ymd_opt(2015, 9, 5) + /// .unwrap() + /// .and_hms_micro_opt(13, 23, 45, 678_900) + /// .unwrap()) + /// ); + /// ``` + /// + /// Offset is ignored for the purpose of parsing. + /// + /// ``` + /// # use chrono::{NaiveDateTime, NaiveDate}; + /// # let parse_from_str = NaiveDateTime::parse_from_str; + /// assert_eq!( + /// parse_from_str("2014-5-17T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), + /// Ok(NaiveDate::from_ymd_opt(2014, 5, 17).unwrap().and_hms_opt(12, 34, 56).unwrap()) + /// ); + /// ``` + /// + /// [Leap seconds](./struct.NaiveTime.html#leap-second-handling) are correctly handled by + /// treating any time of the form `hh:mm:60` as a leap second. + /// (This equally applies to the formatting, so the round trip is possible.) + /// + /// ``` + /// # use chrono::{NaiveDateTime, NaiveDate}; + /// # let parse_from_str = NaiveDateTime::parse_from_str; + /// assert_eq!( + /// parse_from_str("2015-07-01 08:59:60.123", "%Y-%m-%d %H:%M:%S%.f"), + /// Ok(NaiveDate::from_ymd_opt(2015, 7, 1) + /// .unwrap() + /// .and_hms_milli_opt(8, 59, 59, 1_123) + /// .unwrap()) + /// ); + /// ``` + /// + /// Missing seconds are assumed to be zero, + /// but out-of-bound times or insufficient fields are errors otherwise. + /// + /// ``` + /// # use chrono::{NaiveDateTime, NaiveDate}; + /// # let parse_from_str = NaiveDateTime::parse_from_str; + /// assert_eq!( + /// parse_from_str("94/9/4 7:15", "%y/%m/%d %H:%M"), + /// Ok(NaiveDate::from_ymd_opt(1994, 9, 4).unwrap().and_hms_opt(7, 15, 0).unwrap()) + /// ); + /// + /// assert!(parse_from_str("04m33s", "%Mm%Ss").is_err()); + /// assert!(parse_from_str("94/9/4 12", "%y/%m/%d %H").is_err()); + /// assert!(parse_from_str("94/9/4 17:60", "%y/%m/%d %H:%M").is_err()); + /// assert!(parse_from_str("94/9/4 24:00:00", "%y/%m/%d %H:%M:%S").is_err()); + /// ``` + /// + /// All parsed fields should be consistent to each other, otherwise it's an error. + /// + /// ``` + /// # use chrono::NaiveDateTime; + /// # let parse_from_str = NaiveDateTime::parse_from_str; + /// let fmt = "%Y-%m-%d %H:%M:%S = UNIX timestamp %s"; + /// assert!(parse_from_str("2001-09-09 01:46:39 = UNIX timestamp 999999999", fmt).is_ok()); + /// assert!(parse_from_str("1970-01-01 00:00:00 = UNIX timestamp 1", fmt).is_err()); + /// ``` + /// + /// Years before 1 BCE or after 9999 CE, require an initial sign + /// + ///``` + /// # use chrono::NaiveDateTime; + /// # let parse_from_str = NaiveDateTime::parse_from_str; + /// let fmt = "%Y-%m-%d %H:%M:%S"; + /// assert!(parse_from_str("10000-09-09 01:46:39", fmt).is_err()); + /// assert!(parse_from_str("+10000-09-09 01:46:39", fmt).is_ok()); + /// ``` + pub fn parse_from_str(s: &str, fmt: &str) -> ParseResult { + let mut parsed = Parsed::new(); + parse(&mut parsed, s, StrftimeItems::new(fmt))?; + parsed.to_naive_datetime_with_offset(0) // no offset adjustment + } + + /// Parses a string with the specified format string and returns a new `NaiveDateTime`, and a + /// slice with the remaining portion of the string. + /// See the [`format::strftime` module](crate::format::strftime) + /// on the supported escape sequences. + /// + /// Similar to [`parse_from_str`](#method.parse_from_str). + /// + /// # Example + /// + /// ```rust + /// # use chrono::{NaiveDate, NaiveDateTime}; + /// let (datetime, remainder) = NaiveDateTime::parse_and_remainder( + /// "2015-02-18 23:16:09 trailing text", + /// "%Y-%m-%d %H:%M:%S", + /// ) + /// .unwrap(); + /// assert_eq!( + /// datetime, + /// NaiveDate::from_ymd_opt(2015, 2, 18).unwrap().and_hms_opt(23, 16, 9).unwrap() + /// ); + /// assert_eq!(remainder, " trailing text"); + /// ``` + pub fn parse_and_remainder<'a>(s: &'a str, fmt: &str) -> ParseResult<(NaiveDateTime, &'a str)> { + let mut parsed = Parsed::new(); + let remainder = parse_and_remainder(&mut parsed, s, StrftimeItems::new(fmt))?; + parsed.to_naive_datetime_with_offset(0).map(|d| (d, remainder)) // no offset adjustment + } + + /// Retrieves a date component. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveDate; + /// + /// let dt = NaiveDate::from_ymd_opt(2016, 7, 8).unwrap().and_hms_opt(9, 10, 11).unwrap(); + /// assert_eq!(dt.date(), NaiveDate::from_ymd_opt(2016, 7, 8).unwrap()); + /// ``` + #[inline] + pub const fn date(&self) -> NaiveDate { + self.date + } + + /// Retrieves a time component. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, NaiveTime}; + /// + /// let dt = NaiveDate::from_ymd_opt(2016, 7, 8).unwrap().and_hms_opt(9, 10, 11).unwrap(); + /// assert_eq!(dt.time(), NaiveTime::from_hms_opt(9, 10, 11).unwrap()); + /// ``` + #[inline] + pub const fn time(&self) -> NaiveTime { + self.time + } + + /// Returns the number of non-leap seconds since the midnight on January 1, 1970. + /// + /// Note that this does *not* account for the timezone! + /// The true "UNIX timestamp" would count seconds since the midnight *UTC* on the epoch. + #[deprecated(since = "0.4.35", note = "use `.and_utc().timestamp()` instead")] + #[inline] + #[must_use] + pub const fn timestamp(&self) -> i64 { + self.and_utc().timestamp() + } + + /// Returns the number of non-leap *milliseconds* since midnight on January 1, 1970. + /// + /// Note that this does *not* account for the timezone! + /// The true "UNIX timestamp" would count seconds since the midnight *UTC* on the epoch. + #[deprecated(since = "0.4.35", note = "use `.and_utc().timestamp_millis()` instead")] + #[inline] + #[must_use] + pub const fn timestamp_millis(&self) -> i64 { + self.and_utc().timestamp_millis() + } + + /// Returns the number of non-leap *microseconds* since midnight on January 1, 1970. + /// + /// Note that this does *not* account for the timezone! + /// The true "UNIX timestamp" would count seconds since the midnight *UTC* on the epoch. + #[deprecated(since = "0.4.35", note = "use `.and_utc().timestamp_micros()` instead")] + #[inline] + #[must_use] + pub const fn timestamp_micros(&self) -> i64 { + self.and_utc().timestamp_micros() + } + + /// Returns the number of non-leap *nanoseconds* since midnight on January 1, 1970. + /// + /// Note that this does *not* account for the timezone! + /// The true "UNIX timestamp" would count seconds since the midnight *UTC* on the epoch. + /// + /// # Panics + /// + /// An `i64` with nanosecond precision can span a range of ~584 years. This function panics on + /// an out of range `NaiveDateTime`. + /// + /// The dates that can be represented as nanoseconds are between 1677-09-21T00:12:43.145224192 + /// and 2262-04-11T23:47:16.854775807. + #[deprecated(since = "0.4.31", note = "use `.and_utc().timestamp_nanos_opt()` instead")] + #[inline] + #[must_use] + #[allow(deprecated)] + pub const fn timestamp_nanos(&self) -> i64 { + self.and_utc().timestamp_nanos() + } + + /// Returns the number of non-leap *nanoseconds* since midnight on January 1, 1970. + /// + /// Note that this does *not* account for the timezone! + /// The true "UNIX timestamp" would count seconds since the midnight *UTC* on the epoch. + /// + /// # Errors + /// + /// An `i64` with nanosecond precision can span a range of ~584 years. This function returns + /// `None` on an out of range `NaiveDateTime`. + /// + /// The dates that can be represented as nanoseconds are between 1677-09-21T00:12:43.145224192 + /// and 2262-04-11T23:47:16.854775807. + #[deprecated(since = "0.4.35", note = "use `.and_utc().timestamp_nanos_opt()` instead")] + #[inline] + #[must_use] + pub const fn timestamp_nanos_opt(&self) -> Option { + self.and_utc().timestamp_nanos_opt() + } + + /// Returns the number of milliseconds since the last whole non-leap second. + /// + /// The return value ranges from 0 to 999, + /// or for [leap seconds](./struct.NaiveTime.html#leap-second-handling), to 1,999. + #[deprecated(since = "0.4.35", note = "use `.and_utc().timestamp_subsec_millis()` instead")] + #[inline] + #[must_use] + pub const fn timestamp_subsec_millis(&self) -> u32 { + self.and_utc().timestamp_subsec_millis() + } + + /// Returns the number of microseconds since the last whole non-leap second. + /// + /// The return value ranges from 0 to 999,999, + /// or for [leap seconds](./struct.NaiveTime.html#leap-second-handling), to 1,999,999. + #[deprecated(since = "0.4.35", note = "use `.and_utc().timestamp_subsec_micros()` instead")] + #[inline] + #[must_use] + pub const fn timestamp_subsec_micros(&self) -> u32 { + self.and_utc().timestamp_subsec_micros() + } + + /// Returns the number of nanoseconds since the last whole non-leap second. + /// + /// The return value ranges from 0 to 999,999,999, + /// or for [leap seconds](./struct.NaiveTime.html#leap-second-handling), to 1,999,999,999. + #[deprecated(since = "0.4.36", note = "use `.and_utc().timestamp_subsec_nanos()` instead")] + pub const fn timestamp_subsec_nanos(&self) -> u32 { + self.and_utc().timestamp_subsec_nanos() + } + + /// Adds given `TimeDelta` to the current date and time. + /// + /// As a part of Chrono's [leap second handling](./struct.NaiveTime.html#leap-second-handling), + /// the addition assumes that **there is no leap second ever**, + /// except when the `NaiveDateTime` itself represents a leap second + /// in which case the assumption becomes that **there is exactly a single leap second ever**. + /// + /// # Errors + /// + /// Returns `None` if the resulting date would be out of range. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, TimeDelta}; + /// + /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + /// + /// let d = from_ymd(2016, 7, 8); + /// let hms = |h, m, s| d.and_hms_opt(h, m, s).unwrap(); + /// assert_eq!(hms(3, 5, 7).checked_add_signed(TimeDelta::zero()), Some(hms(3, 5, 7))); + /// assert_eq!( + /// hms(3, 5, 7).checked_add_signed(TimeDelta::try_seconds(1).unwrap()), + /// Some(hms(3, 5, 8)) + /// ); + /// assert_eq!( + /// hms(3, 5, 7).checked_add_signed(TimeDelta::try_seconds(-1).unwrap()), + /// Some(hms(3, 5, 6)) + /// ); + /// assert_eq!( + /// hms(3, 5, 7).checked_add_signed(TimeDelta::try_seconds(3600 + 60).unwrap()), + /// Some(hms(4, 6, 7)) + /// ); + /// assert_eq!( + /// hms(3, 5, 7).checked_add_signed(TimeDelta::try_seconds(86_400).unwrap()), + /// Some(from_ymd(2016, 7, 9).and_hms_opt(3, 5, 7).unwrap()) + /// ); + /// + /// let hmsm = |h, m, s, milli| d.and_hms_milli_opt(h, m, s, milli).unwrap(); + /// assert_eq!( + /// hmsm(3, 5, 7, 980).checked_add_signed(TimeDelta::try_milliseconds(450).unwrap()), + /// Some(hmsm(3, 5, 8, 430)) + /// ); + /// ``` + /// + /// Overflow returns `None`. + /// + /// ``` + /// # use chrono::{TimeDelta, NaiveDate}; + /// # let hms = |h, m, s| NaiveDate::from_ymd_opt(2016, 7, 8).unwrap().and_hms_opt(h, m, s).unwrap(); + /// assert_eq!(hms(3, 5, 7).checked_add_signed(TimeDelta::try_days(1_000_000_000).unwrap()), None); + /// ``` + /// + /// Leap seconds are handled, + /// but the addition assumes that it is the only leap second happened. + /// + /// ``` + /// # use chrono::{TimeDelta, NaiveDate}; + /// # let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + /// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli_opt(h, m, s, milli).unwrap(); + /// let leap = hmsm(3, 5, 59, 1_300); + /// assert_eq!(leap.checked_add_signed(TimeDelta::zero()), + /// Some(hmsm(3, 5, 59, 1_300))); + /// assert_eq!(leap.checked_add_signed(TimeDelta::try_milliseconds(-500).unwrap()), + /// Some(hmsm(3, 5, 59, 800))); + /// assert_eq!(leap.checked_add_signed(TimeDelta::try_milliseconds(500).unwrap()), + /// Some(hmsm(3, 5, 59, 1_800))); + /// assert_eq!(leap.checked_add_signed(TimeDelta::try_milliseconds(800).unwrap()), + /// Some(hmsm(3, 6, 0, 100))); + /// assert_eq!(leap.checked_add_signed(TimeDelta::try_seconds(10).unwrap()), + /// Some(hmsm(3, 6, 9, 300))); + /// assert_eq!(leap.checked_add_signed(TimeDelta::try_seconds(-10).unwrap()), + /// Some(hmsm(3, 5, 50, 300))); + /// assert_eq!(leap.checked_add_signed(TimeDelta::try_days(1).unwrap()), + /// Some(from_ymd(2016, 7, 9).and_hms_milli_opt(3, 5, 59, 300).unwrap())); + /// ``` + #[must_use] + pub const fn checked_add_signed(self, rhs: TimeDelta) -> Option { + let (time, remainder) = self.time.overflowing_add_signed(rhs); + let remainder = try_opt!(TimeDelta::try_seconds(remainder)); + let date = try_opt!(self.date.checked_add_signed(remainder)); + Some(NaiveDateTime { date, time }) + } + + /// Adds given `Months` to the current date and time. + /// + /// Uses the last day of the month if the day does not exist in the resulting month. + /// + /// # Errors + /// + /// Returns `None` if the resulting date would be out of range. + /// + /// # Example + /// + /// ``` + /// use chrono::{Months, NaiveDate}; + /// + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2014, 1, 1) + /// .unwrap() + /// .and_hms_opt(1, 0, 0) + /// .unwrap() + /// .checked_add_months(Months::new(1)), + /// Some(NaiveDate::from_ymd_opt(2014, 2, 1).unwrap().and_hms_opt(1, 0, 0).unwrap()) + /// ); + /// + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2014, 1, 1) + /// .unwrap() + /// .and_hms_opt(1, 0, 0) + /// .unwrap() + /// .checked_add_months(Months::new(core::i32::MAX as u32 + 1)), + /// None + /// ); + /// ``` + #[must_use] + pub const fn checked_add_months(self, rhs: Months) -> Option { + Some(Self { date: try_opt!(self.date.checked_add_months(rhs)), time: self.time }) + } + + /// Adds given `FixedOffset` to the current datetime. + /// Returns `None` if the result would be outside the valid range for [`NaiveDateTime`]. + /// + /// This method is similar to [`checked_add_signed`](#method.checked_add_offset), but preserves + /// leap seconds. + #[must_use] + pub const fn checked_add_offset(self, rhs: FixedOffset) -> Option { + let (time, days) = self.time.overflowing_add_offset(rhs); + let date = match days { + -1 => try_opt!(self.date.pred_opt()), + 1 => try_opt!(self.date.succ_opt()), + _ => self.date, + }; + Some(NaiveDateTime { date, time }) + } + + /// Subtracts given `FixedOffset` from the current datetime. + /// Returns `None` if the result would be outside the valid range for [`NaiveDateTime`]. + /// + /// This method is similar to [`checked_sub_signed`](#method.checked_sub_signed), but preserves + /// leap seconds. + pub const fn checked_sub_offset(self, rhs: FixedOffset) -> Option { + let (time, days) = self.time.overflowing_sub_offset(rhs); + let date = match days { + -1 => try_opt!(self.date.pred_opt()), + 1 => try_opt!(self.date.succ_opt()), + _ => self.date, + }; + Some(NaiveDateTime { date, time }) + } + + /// Adds given `FixedOffset` to the current datetime. + /// The resulting value may be outside the valid range of [`NaiveDateTime`]. + /// + /// This can be useful for intermediate values, but the resulting out-of-range `NaiveDate` + /// should not be exposed to library users. + #[must_use] + pub(crate) fn overflowing_add_offset(self, rhs: FixedOffset) -> NaiveDateTime { + let (time, days) = self.time.overflowing_add_offset(rhs); + let date = match days { + -1 => self.date.pred_opt().unwrap_or(NaiveDate::BEFORE_MIN), + 1 => self.date.succ_opt().unwrap_or(NaiveDate::AFTER_MAX), + _ => self.date, + }; + NaiveDateTime { date, time } + } + + /// Subtracts given `FixedOffset` from the current datetime. + /// The resulting value may be outside the valid range of [`NaiveDateTime`]. + /// + /// This can be useful for intermediate values, but the resulting out-of-range `NaiveDate` + /// should not be exposed to library users. + #[must_use] + #[allow(unused)] // currently only used in `Local` but not on all platforms + pub(crate) fn overflowing_sub_offset(self, rhs: FixedOffset) -> NaiveDateTime { + let (time, days) = self.time.overflowing_sub_offset(rhs); + let date = match days { + -1 => self.date.pred_opt().unwrap_or(NaiveDate::BEFORE_MIN), + 1 => self.date.succ_opt().unwrap_or(NaiveDate::AFTER_MAX), + _ => self.date, + }; + NaiveDateTime { date, time } + } + + /// Subtracts given `TimeDelta` from the current date and time. + /// + /// As a part of Chrono's [leap second handling](./struct.NaiveTime.html#leap-second-handling), + /// the subtraction assumes that **there is no leap second ever**, + /// except when the `NaiveDateTime` itself represents a leap second + /// in which case the assumption becomes that **there is exactly a single leap second ever**. + /// + /// # Errors + /// + /// Returns `None` if the resulting date would be out of range. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, TimeDelta}; + /// + /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + /// + /// let d = from_ymd(2016, 7, 8); + /// let hms = |h, m, s| d.and_hms_opt(h, m, s).unwrap(); + /// assert_eq!(hms(3, 5, 7).checked_sub_signed(TimeDelta::zero()), Some(hms(3, 5, 7))); + /// assert_eq!( + /// hms(3, 5, 7).checked_sub_signed(TimeDelta::try_seconds(1).unwrap()), + /// Some(hms(3, 5, 6)) + /// ); + /// assert_eq!( + /// hms(3, 5, 7).checked_sub_signed(TimeDelta::try_seconds(-1).unwrap()), + /// Some(hms(3, 5, 8)) + /// ); + /// assert_eq!( + /// hms(3, 5, 7).checked_sub_signed(TimeDelta::try_seconds(3600 + 60).unwrap()), + /// Some(hms(2, 4, 7)) + /// ); + /// assert_eq!( + /// hms(3, 5, 7).checked_sub_signed(TimeDelta::try_seconds(86_400).unwrap()), + /// Some(from_ymd(2016, 7, 7).and_hms_opt(3, 5, 7).unwrap()) + /// ); + /// + /// let hmsm = |h, m, s, milli| d.and_hms_milli_opt(h, m, s, milli).unwrap(); + /// assert_eq!( + /// hmsm(3, 5, 7, 450).checked_sub_signed(TimeDelta::try_milliseconds(670).unwrap()), + /// Some(hmsm(3, 5, 6, 780)) + /// ); + /// ``` + /// + /// Overflow returns `None`. + /// + /// ``` + /// # use chrono::{TimeDelta, NaiveDate}; + /// # let hms = |h, m, s| NaiveDate::from_ymd_opt(2016, 7, 8).unwrap().and_hms_opt(h, m, s).unwrap(); + /// assert_eq!(hms(3, 5, 7).checked_sub_signed(TimeDelta::try_days(1_000_000_000).unwrap()), None); + /// ``` + /// + /// Leap seconds are handled, + /// but the subtraction assumes that it is the only leap second happened. + /// + /// ``` + /// # use chrono::{TimeDelta, NaiveDate}; + /// # let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + /// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli_opt(h, m, s, milli).unwrap(); + /// let leap = hmsm(3, 5, 59, 1_300); + /// assert_eq!(leap.checked_sub_signed(TimeDelta::zero()), + /// Some(hmsm(3, 5, 59, 1_300))); + /// assert_eq!(leap.checked_sub_signed(TimeDelta::try_milliseconds(200).unwrap()), + /// Some(hmsm(3, 5, 59, 1_100))); + /// assert_eq!(leap.checked_sub_signed(TimeDelta::try_milliseconds(500).unwrap()), + /// Some(hmsm(3, 5, 59, 800))); + /// assert_eq!(leap.checked_sub_signed(TimeDelta::try_seconds(60).unwrap()), + /// Some(hmsm(3, 5, 0, 300))); + /// assert_eq!(leap.checked_sub_signed(TimeDelta::try_days(1).unwrap()), + /// Some(from_ymd(2016, 7, 7).and_hms_milli_opt(3, 6, 0, 300).unwrap())); + /// ``` + #[must_use] + pub const fn checked_sub_signed(self, rhs: TimeDelta) -> Option { + let (time, remainder) = self.time.overflowing_sub_signed(rhs); + let remainder = try_opt!(TimeDelta::try_seconds(remainder)); + let date = try_opt!(self.date.checked_sub_signed(remainder)); + Some(NaiveDateTime { date, time }) + } + + /// Subtracts given `Months` from the current date and time. + /// + /// Uses the last day of the month if the day does not exist in the resulting month. + /// + /// # Errors + /// + /// Returns `None` if the resulting date would be out of range. + /// + /// # Example + /// + /// ``` + /// use chrono::{Months, NaiveDate}; + /// + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2014, 1, 1) + /// .unwrap() + /// .and_hms_opt(1, 0, 0) + /// .unwrap() + /// .checked_sub_months(Months::new(1)), + /// Some(NaiveDate::from_ymd_opt(2013, 12, 1).unwrap().and_hms_opt(1, 0, 0).unwrap()) + /// ); + /// + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2014, 1, 1) + /// .unwrap() + /// .and_hms_opt(1, 0, 0) + /// .unwrap() + /// .checked_sub_months(Months::new(core::i32::MAX as u32 + 1)), + /// None + /// ); + /// ``` + #[must_use] + pub const fn checked_sub_months(self, rhs: Months) -> Option { + Some(Self { date: try_opt!(self.date.checked_sub_months(rhs)), time: self.time }) + } + + /// Add a duration in [`Days`] to the date part of the `NaiveDateTime` + /// + /// Returns `None` if the resulting date would be out of range. + #[must_use] + pub const fn checked_add_days(self, days: Days) -> Option { + Some(Self { date: try_opt!(self.date.checked_add_days(days)), ..self }) + } + + /// Subtract a duration in [`Days`] from the date part of the `NaiveDateTime` + /// + /// Returns `None` if the resulting date would be out of range. + #[must_use] + pub const fn checked_sub_days(self, days: Days) -> Option { + Some(Self { date: try_opt!(self.date.checked_sub_days(days)), ..self }) + } + + /// Subtracts another `NaiveDateTime` from the current date and time. + /// This does not overflow or underflow at all. + /// + /// As a part of Chrono's [leap second handling](./struct.NaiveTime.html#leap-second-handling), + /// the subtraction assumes that **there is no leap second ever**, + /// except when any of the `NaiveDateTime`s themselves represents a leap second + /// in which case the assumption becomes that + /// **there are exactly one (or two) leap second(s) ever**. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, TimeDelta}; + /// + /// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + /// + /// let d = from_ymd(2016, 7, 8); + /// assert_eq!( + /// d.and_hms_opt(3, 5, 7).unwrap().signed_duration_since(d.and_hms_opt(2, 4, 6).unwrap()), + /// TimeDelta::try_seconds(3600 + 60 + 1).unwrap() + /// ); + /// + /// // July 8 is 190th day in the year 2016 + /// let d0 = from_ymd(2016, 1, 1); + /// assert_eq!( + /// d.and_hms_milli_opt(0, 7, 6, 500) + /// .unwrap() + /// .signed_duration_since(d0.and_hms_opt(0, 0, 0).unwrap()), + /// TimeDelta::try_seconds(189 * 86_400 + 7 * 60 + 6).unwrap() + /// + TimeDelta::try_milliseconds(500).unwrap() + /// ); + /// ``` + /// + /// Leap seconds are handled, but the subtraction assumes that + /// there were no other leap seconds happened. + /// + /// ``` + /// # use chrono::{TimeDelta, NaiveDate}; + /// # let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); + /// let leap = from_ymd(2015, 6, 30).and_hms_milli_opt(23, 59, 59, 1_500).unwrap(); + /// assert_eq!( + /// leap.signed_duration_since(from_ymd(2015, 6, 30).and_hms_opt(23, 0, 0).unwrap()), + /// TimeDelta::try_seconds(3600).unwrap() + TimeDelta::try_milliseconds(500).unwrap() + /// ); + /// assert_eq!( + /// from_ymd(2015, 7, 1).and_hms_opt(1, 0, 0).unwrap().signed_duration_since(leap), + /// TimeDelta::try_seconds(3600).unwrap() - TimeDelta::try_milliseconds(500).unwrap() + /// ); + /// ``` + #[must_use] + pub const fn signed_duration_since(self, rhs: NaiveDateTime) -> TimeDelta { + expect( + self.date + .signed_duration_since(rhs.date) + .checked_add(&self.time.signed_duration_since(rhs.time)), + "always in range", + ) + } + + /// Formats the combined date and time with the specified formatting items. + /// Otherwise it is the same as the ordinary [`format`](#method.format) method. + /// + /// The `Iterator` of items should be `Clone`able, + /// since the resulting `DelayedFormat` value may be formatted multiple times. + /// + /// # Example + /// + /// ``` + /// use chrono::format::strftime::StrftimeItems; + /// use chrono::NaiveDate; + /// + /// let fmt = StrftimeItems::new("%Y-%m-%d %H:%M:%S"); + /// let dt = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap().and_hms_opt(23, 56, 4).unwrap(); + /// assert_eq!(dt.format_with_items(fmt.clone()).to_string(), "2015-09-05 23:56:04"); + /// assert_eq!(dt.format("%Y-%m-%d %H:%M:%S").to_string(), "2015-09-05 23:56:04"); + /// ``` + /// + /// The resulting `DelayedFormat` can be formatted directly via the `Display` trait. + /// + /// ``` + /// # use chrono::NaiveDate; + /// # use chrono::format::strftime::StrftimeItems; + /// # let fmt = StrftimeItems::new("%Y-%m-%d %H:%M:%S").clone(); + /// # let dt = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap().and_hms_opt(23, 56, 4).unwrap(); + /// assert_eq!(format!("{}", dt.format_with_items(fmt)), "2015-09-05 23:56:04"); + /// ``` + #[cfg(feature = "alloc")] + #[inline] + #[must_use] + pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat + where + I: Iterator + Clone, + B: Borrow>, + { + DelayedFormat::new(Some(self.date), Some(self.time), items) + } + + /// Formats the combined date and time with the specified format string. + /// See the [`format::strftime` module](crate::format::strftime) + /// on the supported escape sequences. + /// + /// This returns a `DelayedFormat`, + /// which gets converted to a string only when actual formatting happens. + /// You may use the `to_string` method to get a `String`, + /// or just feed it into `print!` and other formatting macros. + /// (In this way it avoids the redundant memory allocation.) + /// + /// A wrong format string does *not* issue an error immediately. + /// Rather, converting or formatting the `DelayedFormat` fails. + /// You are recommended to immediately use `DelayedFormat` for this reason. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveDate; + /// + /// let dt = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap().and_hms_opt(23, 56, 4).unwrap(); + /// assert_eq!(dt.format("%Y-%m-%d %H:%M:%S").to_string(), "2015-09-05 23:56:04"); + /// assert_eq!(dt.format("around %l %p on %b %-d").to_string(), "around 11 PM on Sep 5"); + /// ``` + /// + /// The resulting `DelayedFormat` can be formatted directly via the `Display` trait. + /// + /// ``` + /// # use chrono::NaiveDate; + /// # let dt = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap().and_hms_opt(23, 56, 4).unwrap(); + /// assert_eq!(format!("{}", dt.format("%Y-%m-%d %H:%M:%S")), "2015-09-05 23:56:04"); + /// assert_eq!(format!("{}", dt.format("around %l %p on %b %-d")), "around 11 PM on Sep 5"); + /// ``` + #[cfg(feature = "alloc")] + #[inline] + #[must_use] + pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { + self.format_with_items(StrftimeItems::new(fmt)) + } + + /// Converts the `NaiveDateTime` into a timezone-aware `DateTime` with the provided + /// time zone. + /// + /// # Example + /// + /// ``` + /// use chrono::{FixedOffset, NaiveDate}; + /// let hour = 3600; + /// let tz = FixedOffset::east_opt(5 * hour).unwrap(); + /// let dt = NaiveDate::from_ymd_opt(2015, 9, 5) + /// .unwrap() + /// .and_hms_opt(23, 56, 4) + /// .unwrap() + /// .and_local_timezone(tz) + /// .unwrap(); + /// assert_eq!(dt.timezone(), tz); + /// ``` + #[must_use] + pub fn and_local_timezone(&self, tz: Tz) -> MappedLocalTime> { + tz.from_local_datetime(self) + } + + /// Converts the `NaiveDateTime` into the timezone-aware `DateTime`. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, Utc}; + /// let dt = + /// NaiveDate::from_ymd_opt(2023, 1, 30).unwrap().and_hms_opt(19, 32, 33).unwrap().and_utc(); + /// assert_eq!(dt.timezone(), Utc); + /// ``` + #[must_use] + pub const fn and_utc(&self) -> DateTime { + DateTime::from_naive_utc_and_offset(*self, Utc) + } + + /// The minimum possible `NaiveDateTime`. + pub const MIN: Self = Self { date: NaiveDate::MIN, time: NaiveTime::MIN }; + + /// The maximum possible `NaiveDateTime`. + pub const MAX: Self = Self { date: NaiveDate::MAX, time: NaiveTime::MAX }; + + /// The Unix Epoch, 1970-01-01 00:00:00. + pub const UNIX_EPOCH: Self = + expect(NaiveDate::from_ymd_opt(1970, 1, 1), "").and_time(NaiveTime::MIN); +} + +impl From for NaiveDateTime { + /// Converts a `NaiveDate` to a `NaiveDateTime` of the same date but at midnight. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, NaiveDateTime}; + /// + /// let nd = NaiveDate::from_ymd_opt(2016, 5, 28).unwrap(); + /// let ndt = NaiveDate::from_ymd_opt(2016, 5, 28).unwrap().and_hms_opt(0, 0, 0).unwrap(); + /// assert_eq!(ndt, NaiveDateTime::from(nd)); + fn from(date: NaiveDate) -> Self { + date.and_hms_opt(0, 0, 0).unwrap() + } +} + +impl Datelike for NaiveDateTime { + /// Returns the year number in the [calendar date](./struct.NaiveDate.html#calendar-date). + /// + /// See also the [`NaiveDate::year`](./struct.NaiveDate.html#method.year) method. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate, NaiveDateTime}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!(dt.year(), 2015); + /// ``` + #[inline] + fn year(&self) -> i32 { + self.date.year() + } + + /// Returns the month number starting from 1. + /// + /// The return value ranges from 1 to 12. + /// + /// See also the [`NaiveDate::month`](./struct.NaiveDate.html#method.month) method. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate, NaiveDateTime}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!(dt.month(), 9); + /// ``` + #[inline] + fn month(&self) -> u32 { + self.date.month() + } + + /// Returns the month number starting from 0. + /// + /// The return value ranges from 0 to 11. + /// + /// See also the [`NaiveDate::month0`] method. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate, NaiveDateTime}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!(dt.month0(), 8); + /// ``` + #[inline] + fn month0(&self) -> u32 { + self.date.month0() + } + + /// Returns the day of month starting from 1. + /// + /// The return value ranges from 1 to 31. (The last day of month differs by months.) + /// + /// See also the [`NaiveDate::day`](./struct.NaiveDate.html#method.day) method. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate, NaiveDateTime}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!(dt.day(), 25); + /// ``` + #[inline] + fn day(&self) -> u32 { + self.date.day() + } + + /// Returns the day of month starting from 0. + /// + /// The return value ranges from 0 to 30. (The last day of month differs by months.) + /// + /// See also the [`NaiveDate::day0`] method. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate, NaiveDateTime}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!(dt.day0(), 24); + /// ``` + #[inline] + fn day0(&self) -> u32 { + self.date.day0() + } + + /// Returns the day of year starting from 1. + /// + /// The return value ranges from 1 to 366. (The last day of year differs by years.) + /// + /// See also the [`NaiveDate::ordinal`](./struct.NaiveDate.html#method.ordinal) method. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate, NaiveDateTime}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!(dt.ordinal(), 268); + /// ``` + #[inline] + fn ordinal(&self) -> u32 { + self.date.ordinal() + } + + /// Returns the day of year starting from 0. + /// + /// The return value ranges from 0 to 365. (The last day of year differs by years.) + /// + /// See also the [`NaiveDate::ordinal0`] method. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate, NaiveDateTime}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!(dt.ordinal0(), 267); + /// ``` + #[inline] + fn ordinal0(&self) -> u32 { + self.date.ordinal0() + } + + /// Returns the day of week. + /// + /// See also the [`NaiveDate::weekday`](./struct.NaiveDate.html#method.weekday) method. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate, NaiveDateTime, Weekday}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!(dt.weekday(), Weekday::Fri); + /// ``` + #[inline] + fn weekday(&self) -> Weekday { + self.date.weekday() + } + + #[inline] + fn iso_week(&self) -> IsoWeek { + self.date.iso_week() + } + + /// Makes a new `NaiveDateTime` with the year number changed, while keeping the same month and + /// day. + /// + /// See also the [`NaiveDate::with_year`] method. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist (February 29 in a non-leap year). + /// - The year is out of range for a `NaiveDate`. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate, NaiveDateTime}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!( + /// dt.with_year(2016), + /// Some(NaiveDate::from_ymd_opt(2016, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap()) + /// ); + /// assert_eq!( + /// dt.with_year(-308), + /// Some(NaiveDate::from_ymd_opt(-308, 9, 25).unwrap().and_hms_opt(12, 34, 56).unwrap()) + /// ); + /// ``` + #[inline] + fn with_year(&self, year: i32) -> Option { + self.date.with_year(year).map(|d| NaiveDateTime { date: d, ..*self }) + } + + /// Makes a new `NaiveDateTime` with the month number (starting from 1) changed. + /// + /// Don't combine multiple `Datelike::with_*` methods. The intermediate value may not exist. + /// + /// See also the [`NaiveDate::with_month`] method. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist (for example `month(4)` when day of the month is 31). + /// - The value for `month` is invalid. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate, NaiveDateTime}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 30).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!( + /// dt.with_month(10), + /// Some(NaiveDate::from_ymd_opt(2015, 10, 30).unwrap().and_hms_opt(12, 34, 56).unwrap()) + /// ); + /// assert_eq!(dt.with_month(13), None); // No month 13 + /// assert_eq!(dt.with_month(2), None); // No February 30 + /// ``` + #[inline] + fn with_month(&self, month: u32) -> Option { + self.date.with_month(month).map(|d| NaiveDateTime { date: d, ..*self }) + } + + /// Makes a new `NaiveDateTime` with the month number (starting from 0) changed. + /// + /// See also the [`NaiveDate::with_month0`] method. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist (for example `month0(3)` when day of the month is 31). + /// - The value for `month0` is invalid. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate, NaiveDateTime}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 30).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!( + /// dt.with_month0(9), + /// Some(NaiveDate::from_ymd_opt(2015, 10, 30).unwrap().and_hms_opt(12, 34, 56).unwrap()) + /// ); + /// assert_eq!(dt.with_month0(12), None); // No month 13 + /// assert_eq!(dt.with_month0(1), None); // No February 30 + /// ``` + #[inline] + fn with_month0(&self, month0: u32) -> Option { + self.date.with_month0(month0).map(|d| NaiveDateTime { date: d, ..*self }) + } + + /// Makes a new `NaiveDateTime` with the day of month (starting from 1) changed. + /// + /// See also the [`NaiveDate::with_day`] method. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist (for example `day(31)` in April). + /// - The value for `day` is invalid. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate, NaiveDateTime}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!( + /// dt.with_day(30), + /// Some(NaiveDate::from_ymd_opt(2015, 9, 30).unwrap().and_hms_opt(12, 34, 56).unwrap()) + /// ); + /// assert_eq!(dt.with_day(31), None); // no September 31 + /// ``` + #[inline] + fn with_day(&self, day: u32) -> Option { + self.date.with_day(day).map(|d| NaiveDateTime { date: d, ..*self }) + } + + /// Makes a new `NaiveDateTime` with the day of month (starting from 0) changed. + /// + /// See also the [`NaiveDate::with_day0`] method. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist (for example `day(30)` in April). + /// - The value for `day0` is invalid. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate, NaiveDateTime}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!( + /// dt.with_day0(29), + /// Some(NaiveDate::from_ymd_opt(2015, 9, 30).unwrap().and_hms_opt(12, 34, 56).unwrap()) + /// ); + /// assert_eq!(dt.with_day0(30), None); // no September 31 + /// ``` + #[inline] + fn with_day0(&self, day0: u32) -> Option { + self.date.with_day0(day0).map(|d| NaiveDateTime { date: d, ..*self }) + } + + /// Makes a new `NaiveDateTime` with the day of year (starting from 1) changed. + /// + /// See also the [`NaiveDate::with_ordinal`] method. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist (`with_ordinal(366)` in a non-leap year). + /// - The value for `ordinal` is invalid. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate, NaiveDateTime}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!( + /// dt.with_ordinal(60), + /// Some(NaiveDate::from_ymd_opt(2015, 3, 1).unwrap().and_hms_opt(12, 34, 56).unwrap()) + /// ); + /// assert_eq!(dt.with_ordinal(366), None); // 2015 had only 365 days + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2016, 9, 8).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!( + /// dt.with_ordinal(60), + /// Some(NaiveDate::from_ymd_opt(2016, 2, 29).unwrap().and_hms_opt(12, 34, 56).unwrap()) + /// ); + /// assert_eq!( + /// dt.with_ordinal(366), + /// Some(NaiveDate::from_ymd_opt(2016, 12, 31).unwrap().and_hms_opt(12, 34, 56).unwrap()) + /// ); + /// ``` + #[inline] + fn with_ordinal(&self, ordinal: u32) -> Option { + self.date.with_ordinal(ordinal).map(|d| NaiveDateTime { date: d, ..*self }) + } + + /// Makes a new `NaiveDateTime` with the day of year (starting from 0) changed. + /// + /// See also the [`NaiveDate::with_ordinal0`] method. + /// + /// # Errors + /// + /// Returns `None` if: + /// - The resulting date does not exist (`with_ordinal0(365)` in a non-leap year). + /// - The value for `ordinal0` is invalid. + /// + /// # Example + /// + /// ``` + /// use chrono::{Datelike, NaiveDate, NaiveDateTime}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!( + /// dt.with_ordinal0(59), + /// Some(NaiveDate::from_ymd_opt(2015, 3, 1).unwrap().and_hms_opt(12, 34, 56).unwrap()) + /// ); + /// assert_eq!(dt.with_ordinal0(365), None); // 2015 had only 365 days + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2016, 9, 8).unwrap().and_hms_opt(12, 34, 56).unwrap(); + /// assert_eq!( + /// dt.with_ordinal0(59), + /// Some(NaiveDate::from_ymd_opt(2016, 2, 29).unwrap().and_hms_opt(12, 34, 56).unwrap()) + /// ); + /// assert_eq!( + /// dt.with_ordinal0(365), + /// Some(NaiveDate::from_ymd_opt(2016, 12, 31).unwrap().and_hms_opt(12, 34, 56).unwrap()) + /// ); + /// ``` + #[inline] + fn with_ordinal0(&self, ordinal0: u32) -> Option { + self.date.with_ordinal0(ordinal0).map(|d| NaiveDateTime { date: d, ..*self }) + } +} + +impl Timelike for NaiveDateTime { + /// Returns the hour number from 0 to 23. + /// + /// See also the [`NaiveTime::hour`] method. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 56, 789).unwrap(); + /// assert_eq!(dt.hour(), 12); + /// ``` + #[inline] + fn hour(&self) -> u32 { + self.time.hour() + } + + /// Returns the minute number from 0 to 59. + /// + /// See also the [`NaiveTime::minute`] method. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 56, 789).unwrap(); + /// assert_eq!(dt.minute(), 34); + /// ``` + #[inline] + fn minute(&self) -> u32 { + self.time.minute() + } + + /// Returns the second number from 0 to 59. + /// + /// See also the [`NaiveTime::second`] method. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 56, 789).unwrap(); + /// assert_eq!(dt.second(), 56); + /// ``` + #[inline] + fn second(&self) -> u32 { + self.time.second() + } + + /// Returns the number of nanoseconds since the whole non-leap second. + /// The range from 1,000,000,000 to 1,999,999,999 represents + /// the [leap second](./struct.NaiveTime.html#leap-second-handling). + /// + /// See also the [`NaiveTime#method.nanosecond`] method. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 56, 789).unwrap(); + /// assert_eq!(dt.nanosecond(), 789_000_000); + /// ``` + #[inline] + fn nanosecond(&self) -> u32 { + self.time.nanosecond() + } + + /// Makes a new `NaiveDateTime` with the hour number changed. + /// + /// See also the [`NaiveTime::with_hour`] method. + /// + /// # Errors + /// + /// Returns `None` if the value for `hour` is invalid. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 56, 789).unwrap(); + /// assert_eq!( + /// dt.with_hour(7), + /// Some( + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(7, 34, 56, 789).unwrap() + /// ) + /// ); + /// assert_eq!(dt.with_hour(24), None); + /// ``` + #[inline] + fn with_hour(&self, hour: u32) -> Option { + self.time.with_hour(hour).map(|t| NaiveDateTime { time: t, ..*self }) + } + + /// Makes a new `NaiveDateTime` with the minute number changed. + /// + /// See also the [`NaiveTime::with_minute`] method. + /// + /// # Errors + /// + /// Returns `None` if the value for `minute` is invalid. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 56, 789).unwrap(); + /// assert_eq!( + /// dt.with_minute(45), + /// Some( + /// NaiveDate::from_ymd_opt(2015, 9, 8) + /// .unwrap() + /// .and_hms_milli_opt(12, 45, 56, 789) + /// .unwrap() + /// ) + /// ); + /// assert_eq!(dt.with_minute(60), None); + /// ``` + #[inline] + fn with_minute(&self, min: u32) -> Option { + self.time.with_minute(min).map(|t| NaiveDateTime { time: t, ..*self }) + } + + /// Makes a new `NaiveDateTime` with the second number changed. + /// + /// As with the [`second`](#method.second) method, + /// the input range is restricted to 0 through 59. + /// + /// See also the [`NaiveTime::with_second`] method. + /// + /// # Errors + /// + /// Returns `None` if the value for `second` is invalid. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 56, 789).unwrap(); + /// assert_eq!( + /// dt.with_second(17), + /// Some( + /// NaiveDate::from_ymd_opt(2015, 9, 8) + /// .unwrap() + /// .and_hms_milli_opt(12, 34, 17, 789) + /// .unwrap() + /// ) + /// ); + /// assert_eq!(dt.with_second(60), None); + /// ``` + #[inline] + fn with_second(&self, sec: u32) -> Option { + self.time.with_second(sec).map(|t| NaiveDateTime { time: t, ..*self }) + } + + /// Makes a new `NaiveDateTime` with nanoseconds since the whole non-leap second changed. + /// + /// Returns `None` when the resulting `NaiveDateTime` would be invalid. + /// As with the [`NaiveDateTime::nanosecond`] method, + /// the input range can exceed 1,000,000,000 for leap seconds. + /// + /// See also the [`NaiveTime::with_nanosecond`] method. + /// + /// # Errors + /// + /// Returns `None` if `nanosecond >= 2,000,000,000`. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveDate, NaiveDateTime, Timelike}; + /// + /// let dt: NaiveDateTime = + /// NaiveDate::from_ymd_opt(2015, 9, 8).unwrap().and_hms_milli_opt(12, 34, 59, 789).unwrap(); + /// assert_eq!( + /// dt.with_nanosecond(333_333_333), + /// Some( + /// NaiveDate::from_ymd_opt(2015, 9, 8) + /// .unwrap() + /// .and_hms_nano_opt(12, 34, 59, 333_333_333) + /// .unwrap() + /// ) + /// ); + /// assert_eq!( + /// dt.with_nanosecond(1_333_333_333), // leap second + /// Some( + /// NaiveDate::from_ymd_opt(2015, 9, 8) + /// .unwrap() + /// .and_hms_nano_opt(12, 34, 59, 1_333_333_333) + /// .unwrap() + /// ) + /// ); + /// assert_eq!(dt.with_nanosecond(2_000_000_000), None); + /// ``` + #[inline] + fn with_nanosecond(&self, nano: u32) -> Option { + self.time.with_nanosecond(nano).map(|t| NaiveDateTime { time: t, ..*self }) + } +} + +/// Add `TimeDelta` to `NaiveDateTime`. +/// +/// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap +/// second ever**, except when the `NaiveDateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDateTime::checked_add_signed`] to get an `Option` instead. +/// +/// # Example +/// +/// ``` +/// use chrono::{NaiveDate, TimeDelta}; +/// +/// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); +/// +/// let d = from_ymd(2016, 7, 8); +/// let hms = |h, m, s| d.and_hms_opt(h, m, s).unwrap(); +/// assert_eq!(hms(3, 5, 7) + TimeDelta::zero(), hms(3, 5, 7)); +/// assert_eq!(hms(3, 5, 7) + TimeDelta::try_seconds(1).unwrap(), hms(3, 5, 8)); +/// assert_eq!(hms(3, 5, 7) + TimeDelta::try_seconds(-1).unwrap(), hms(3, 5, 6)); +/// assert_eq!(hms(3, 5, 7) + TimeDelta::try_seconds(3600 + 60).unwrap(), hms(4, 6, 7)); +/// assert_eq!( +/// hms(3, 5, 7) + TimeDelta::try_seconds(86_400).unwrap(), +/// from_ymd(2016, 7, 9).and_hms_opt(3, 5, 7).unwrap() +/// ); +/// assert_eq!( +/// hms(3, 5, 7) + TimeDelta::try_days(365).unwrap(), +/// from_ymd(2017, 7, 8).and_hms_opt(3, 5, 7).unwrap() +/// ); +/// +/// let hmsm = |h, m, s, milli| d.and_hms_milli_opt(h, m, s, milli).unwrap(); +/// assert_eq!(hmsm(3, 5, 7, 980) + TimeDelta::try_milliseconds(450).unwrap(), hmsm(3, 5, 8, 430)); +/// ``` +/// +/// Leap seconds are handled, +/// but the addition assumes that it is the only leap second happened. +/// +/// ``` +/// # use chrono::{TimeDelta, NaiveDate}; +/// # let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); +/// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli_opt(h, m, s, milli).unwrap(); +/// let leap = hmsm(3, 5, 59, 1_300); +/// assert_eq!(leap + TimeDelta::zero(), hmsm(3, 5, 59, 1_300)); +/// assert_eq!(leap + TimeDelta::try_milliseconds(-500).unwrap(), hmsm(3, 5, 59, 800)); +/// assert_eq!(leap + TimeDelta::try_milliseconds(500).unwrap(), hmsm(3, 5, 59, 1_800)); +/// assert_eq!(leap + TimeDelta::try_milliseconds(800).unwrap(), hmsm(3, 6, 0, 100)); +/// assert_eq!(leap + TimeDelta::try_seconds(10).unwrap(), hmsm(3, 6, 9, 300)); +/// assert_eq!(leap + TimeDelta::try_seconds(-10).unwrap(), hmsm(3, 5, 50, 300)); +/// assert_eq!(leap + TimeDelta::try_days(1).unwrap(), +/// from_ymd(2016, 7, 9).and_hms_milli_opt(3, 5, 59, 300).unwrap()); +/// ``` +/// +/// [leap second handling]: crate::NaiveTime#leap-second-handling +impl Add for NaiveDateTime { + type Output = NaiveDateTime; + + #[inline] + fn add(self, rhs: TimeDelta) -> NaiveDateTime { + self.checked_add_signed(rhs).expect("`NaiveDateTime + TimeDelta` overflowed") + } +} + +/// Add `std::time::Duration` to `NaiveDateTime`. +/// +/// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap +/// second ever**, except when the `NaiveDateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDateTime::checked_add_signed`] to get an `Option` instead. +impl Add for NaiveDateTime { + type Output = NaiveDateTime; + + #[inline] + fn add(self, rhs: Duration) -> NaiveDateTime { + let rhs = TimeDelta::from_std(rhs) + .expect("overflow converting from core::time::Duration to TimeDelta"); + self.checked_add_signed(rhs).expect("`NaiveDateTime + TimeDelta` overflowed") + } +} + +/// Add-assign `TimeDelta` to `NaiveDateTime`. +/// +/// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap +/// second ever**, except when the `NaiveDateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDateTime::checked_add_signed`] to get an `Option` instead. +impl AddAssign for NaiveDateTime { + #[inline] + fn add_assign(&mut self, rhs: TimeDelta) { + *self = self.add(rhs); + } +} + +/// Add-assign `std::time::Duration` to `NaiveDateTime`. +/// +/// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap +/// second ever**, except when the `NaiveDateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDateTime::checked_add_signed`] to get an `Option` instead. +impl AddAssign for NaiveDateTime { + #[inline] + fn add_assign(&mut self, rhs: Duration) { + *self = self.add(rhs); + } +} + +/// Add `FixedOffset` to `NaiveDateTime`. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using `checked_add_offset` to get an `Option` instead. +impl Add for NaiveDateTime { + type Output = NaiveDateTime; + + #[inline] + fn add(self, rhs: FixedOffset) -> NaiveDateTime { + self.checked_add_offset(rhs).expect("`NaiveDateTime + FixedOffset` out of range") + } +} + +/// Add `Months` to `NaiveDateTime`. +/// +/// The result will be clamped to valid days in the resulting month, see `checked_add_months` for +/// details. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using `checked_add_months` to get an `Option` instead. +/// +/// # Example +/// +/// ``` +/// use chrono::{Months, NaiveDate}; +/// +/// assert_eq!( +/// NaiveDate::from_ymd_opt(2014, 1, 1).unwrap().and_hms_opt(1, 0, 0).unwrap() + Months::new(1), +/// NaiveDate::from_ymd_opt(2014, 2, 1).unwrap().and_hms_opt(1, 0, 0).unwrap() +/// ); +/// assert_eq!( +/// NaiveDate::from_ymd_opt(2014, 1, 1).unwrap().and_hms_opt(0, 2, 0).unwrap() +/// + Months::new(11), +/// NaiveDate::from_ymd_opt(2014, 12, 1).unwrap().and_hms_opt(0, 2, 0).unwrap() +/// ); +/// assert_eq!( +/// NaiveDate::from_ymd_opt(2014, 1, 1).unwrap().and_hms_opt(0, 0, 3).unwrap() +/// + Months::new(12), +/// NaiveDate::from_ymd_opt(2015, 1, 1).unwrap().and_hms_opt(0, 0, 3).unwrap() +/// ); +/// assert_eq!( +/// NaiveDate::from_ymd_opt(2014, 1, 1).unwrap().and_hms_opt(0, 0, 4).unwrap() +/// + Months::new(13), +/// NaiveDate::from_ymd_opt(2015, 2, 1).unwrap().and_hms_opt(0, 0, 4).unwrap() +/// ); +/// assert_eq!( +/// NaiveDate::from_ymd_opt(2014, 1, 31).unwrap().and_hms_opt(0, 5, 0).unwrap() +/// + Months::new(1), +/// NaiveDate::from_ymd_opt(2014, 2, 28).unwrap().and_hms_opt(0, 5, 0).unwrap() +/// ); +/// assert_eq!( +/// NaiveDate::from_ymd_opt(2020, 1, 31).unwrap().and_hms_opt(6, 0, 0).unwrap() +/// + Months::new(1), +/// NaiveDate::from_ymd_opt(2020, 2, 29).unwrap().and_hms_opt(6, 0, 0).unwrap() +/// ); +/// ``` +impl Add for NaiveDateTime { + type Output = NaiveDateTime; + + fn add(self, rhs: Months) -> Self::Output { + self.checked_add_months(rhs).expect("`NaiveDateTime + Months` out of range") + } +} + +/// Subtract `TimeDelta` from `NaiveDateTime`. +/// +/// This is the same as the addition with a negated `TimeDelta`. +/// +/// As a part of Chrono's [leap second handling] the subtraction assumes that **there is no leap +/// second ever**, except when the `NaiveDateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDateTime::checked_sub_signed`] to get an `Option` instead. +/// +/// # Example +/// +/// ``` +/// use chrono::{NaiveDate, TimeDelta}; +/// +/// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); +/// +/// let d = from_ymd(2016, 7, 8); +/// let hms = |h, m, s| d.and_hms_opt(h, m, s).unwrap(); +/// assert_eq!(hms(3, 5, 7) - TimeDelta::zero(), hms(3, 5, 7)); +/// assert_eq!(hms(3, 5, 7) - TimeDelta::try_seconds(1).unwrap(), hms(3, 5, 6)); +/// assert_eq!(hms(3, 5, 7) - TimeDelta::try_seconds(-1).unwrap(), hms(3, 5, 8)); +/// assert_eq!(hms(3, 5, 7) - TimeDelta::try_seconds(3600 + 60).unwrap(), hms(2, 4, 7)); +/// assert_eq!( +/// hms(3, 5, 7) - TimeDelta::try_seconds(86_400).unwrap(), +/// from_ymd(2016, 7, 7).and_hms_opt(3, 5, 7).unwrap() +/// ); +/// assert_eq!( +/// hms(3, 5, 7) - TimeDelta::try_days(365).unwrap(), +/// from_ymd(2015, 7, 9).and_hms_opt(3, 5, 7).unwrap() +/// ); +/// +/// let hmsm = |h, m, s, milli| d.and_hms_milli_opt(h, m, s, milli).unwrap(); +/// assert_eq!(hmsm(3, 5, 7, 450) - TimeDelta::try_milliseconds(670).unwrap(), hmsm(3, 5, 6, 780)); +/// ``` +/// +/// Leap seconds are handled, +/// but the subtraction assumes that it is the only leap second happened. +/// +/// ``` +/// # use chrono::{TimeDelta, NaiveDate}; +/// # let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); +/// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli_opt(h, m, s, milli).unwrap(); +/// let leap = hmsm(3, 5, 59, 1_300); +/// assert_eq!(leap - TimeDelta::zero(), hmsm(3, 5, 59, 1_300)); +/// assert_eq!(leap - TimeDelta::try_milliseconds(200).unwrap(), hmsm(3, 5, 59, 1_100)); +/// assert_eq!(leap - TimeDelta::try_milliseconds(500).unwrap(), hmsm(3, 5, 59, 800)); +/// assert_eq!(leap - TimeDelta::try_seconds(60).unwrap(), hmsm(3, 5, 0, 300)); +/// assert_eq!(leap - TimeDelta::try_days(1).unwrap(), +/// from_ymd(2016, 7, 7).and_hms_milli_opt(3, 6, 0, 300).unwrap()); +/// ``` +/// +/// [leap second handling]: crate::NaiveTime#leap-second-handling +impl Sub for NaiveDateTime { + type Output = NaiveDateTime; + + #[inline] + fn sub(self, rhs: TimeDelta) -> NaiveDateTime { + self.checked_sub_signed(rhs).expect("`NaiveDateTime - TimeDelta` overflowed") + } +} + +/// Subtract `std::time::Duration` from `NaiveDateTime`. +/// +/// As a part of Chrono's [leap second handling] the subtraction assumes that **there is no leap +/// second ever**, except when the `NaiveDateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDateTime::checked_sub_signed`] to get an `Option` instead. +impl Sub for NaiveDateTime { + type Output = NaiveDateTime; + + #[inline] + fn sub(self, rhs: Duration) -> NaiveDateTime { + let rhs = TimeDelta::from_std(rhs) + .expect("overflow converting from core::time::Duration to TimeDelta"); + self.checked_sub_signed(rhs).expect("`NaiveDateTime - TimeDelta` overflowed") + } +} + +/// Subtract-assign `TimeDelta` from `NaiveDateTime`. +/// +/// This is the same as the addition with a negated `TimeDelta`. +/// +/// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap +/// second ever**, except when the `NaiveDateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDateTime::checked_sub_signed`] to get an `Option` instead. +impl SubAssign for NaiveDateTime { + #[inline] + fn sub_assign(&mut self, rhs: TimeDelta) { + *self = self.sub(rhs); + } +} + +/// Subtract-assign `std::time::Duration` from `NaiveDateTime`. +/// +/// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap +/// second ever**, except when the `NaiveDateTime` itself represents a leap second in which case +/// the assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDateTime::checked_sub_signed`] to get an `Option` instead. +impl SubAssign for NaiveDateTime { + #[inline] + fn sub_assign(&mut self, rhs: Duration) { + *self = self.sub(rhs); + } +} + +/// Subtract `FixedOffset` from `NaiveDateTime`. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using `checked_sub_offset` to get an `Option` instead. +impl Sub for NaiveDateTime { + type Output = NaiveDateTime; + + #[inline] + fn sub(self, rhs: FixedOffset) -> NaiveDateTime { + self.checked_sub_offset(rhs).expect("`NaiveDateTime - FixedOffset` out of range") + } +} + +/// Subtract `Months` from `NaiveDateTime`. +/// +/// The result will be clamped to valid days in the resulting month, see +/// [`NaiveDateTime::checked_sub_months`] for details. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using [`NaiveDateTime::checked_sub_months`] to get an `Option` instead. +/// +/// # Example +/// +/// ``` +/// use chrono::{Months, NaiveDate}; +/// +/// assert_eq!( +/// NaiveDate::from_ymd_opt(2014, 01, 01).unwrap().and_hms_opt(01, 00, 00).unwrap() +/// - Months::new(11), +/// NaiveDate::from_ymd_opt(2013, 02, 01).unwrap().and_hms_opt(01, 00, 00).unwrap() +/// ); +/// assert_eq!( +/// NaiveDate::from_ymd_opt(2014, 01, 01).unwrap().and_hms_opt(00, 02, 00).unwrap() +/// - Months::new(12), +/// NaiveDate::from_ymd_opt(2013, 01, 01).unwrap().and_hms_opt(00, 02, 00).unwrap() +/// ); +/// assert_eq!( +/// NaiveDate::from_ymd_opt(2014, 01, 01).unwrap().and_hms_opt(00, 00, 03).unwrap() +/// - Months::new(13), +/// NaiveDate::from_ymd_opt(2012, 12, 01).unwrap().and_hms_opt(00, 00, 03).unwrap() +/// ); +/// ``` +impl Sub for NaiveDateTime { + type Output = NaiveDateTime; + + fn sub(self, rhs: Months) -> Self::Output { + self.checked_sub_months(rhs).expect("`NaiveDateTime - Months` out of range") + } +} + +/// Subtracts another `NaiveDateTime` from the current date and time. +/// This does not overflow or underflow at all. +/// +/// As a part of Chrono's [leap second handling](./struct.NaiveTime.html#leap-second-handling), +/// the subtraction assumes that **there is no leap second ever**, +/// except when any of the `NaiveDateTime`s themselves represents a leap second +/// in which case the assumption becomes that +/// **there are exactly one (or two) leap second(s) ever**. +/// +/// The implementation is a wrapper around [`NaiveDateTime::signed_duration_since`]. +/// +/// # Example +/// +/// ``` +/// use chrono::{NaiveDate, TimeDelta}; +/// +/// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); +/// +/// let d = from_ymd(2016, 7, 8); +/// assert_eq!( +/// d.and_hms_opt(3, 5, 7).unwrap() - d.and_hms_opt(2, 4, 6).unwrap(), +/// TimeDelta::try_seconds(3600 + 60 + 1).unwrap() +/// ); +/// +/// // July 8 is 190th day in the year 2016 +/// let d0 = from_ymd(2016, 1, 1); +/// assert_eq!( +/// d.and_hms_milli_opt(0, 7, 6, 500).unwrap() - d0.and_hms_opt(0, 0, 0).unwrap(), +/// TimeDelta::try_seconds(189 * 86_400 + 7 * 60 + 6).unwrap() +/// + TimeDelta::try_milliseconds(500).unwrap() +/// ); +/// ``` +/// +/// Leap seconds are handled, but the subtraction assumes that no other leap +/// seconds happened. +/// +/// ``` +/// # use chrono::{TimeDelta, NaiveDate}; +/// # let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap(); +/// let leap = from_ymd(2015, 6, 30).and_hms_milli_opt(23, 59, 59, 1_500).unwrap(); +/// assert_eq!( +/// leap - from_ymd(2015, 6, 30).and_hms_opt(23, 0, 0).unwrap(), +/// TimeDelta::try_seconds(3600).unwrap() + TimeDelta::try_milliseconds(500).unwrap() +/// ); +/// assert_eq!( +/// from_ymd(2015, 7, 1).and_hms_opt(1, 0, 0).unwrap() - leap, +/// TimeDelta::try_seconds(3600).unwrap() - TimeDelta::try_milliseconds(500).unwrap() +/// ); +/// ``` +impl Sub for NaiveDateTime { + type Output = TimeDelta; + + #[inline] + fn sub(self, rhs: NaiveDateTime) -> TimeDelta { + self.signed_duration_since(rhs) + } +} + +/// Add `Days` to `NaiveDateTime`. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using `checked_add_days` to get an `Option` instead. +impl Add for NaiveDateTime { + type Output = NaiveDateTime; + + fn add(self, days: Days) -> Self::Output { + self.checked_add_days(days).expect("`NaiveDateTime + Days` out of range") + } +} + +/// Subtract `Days` from `NaiveDateTime`. +/// +/// # Panics +/// +/// Panics if the resulting date would be out of range. +/// Consider using `checked_sub_days` to get an `Option` instead. +impl Sub for NaiveDateTime { + type Output = NaiveDateTime; + + fn sub(self, days: Days) -> Self::Output { + self.checked_sub_days(days).expect("`NaiveDateTime - Days` out of range") + } +} + +/// The `Debug` output of the naive date and time `dt` is the same as +/// [`dt.format("%Y-%m-%dT%H:%M:%S%.f")`](crate::format::strftime). +/// +/// The string printed can be readily parsed via the `parse` method on `str`. +/// +/// It should be noted that, for leap seconds not on the minute boundary, +/// it may print a representation not distinguishable from non-leap seconds. +/// This doesn't matter in practice, since such leap seconds never happened. +/// (By the time of the first leap second on 1972-06-30, +/// every time zone offset around the world has standardized to the 5-minute alignment.) +/// +/// # Example +/// +/// ``` +/// use chrono::NaiveDate; +/// +/// let dt = NaiveDate::from_ymd_opt(2016, 11, 15).unwrap().and_hms_opt(7, 39, 24).unwrap(); +/// assert_eq!(format!("{:?}", dt), "2016-11-15T07:39:24"); +/// ``` +/// +/// Leap seconds may also be used. +/// +/// ``` +/// # use chrono::NaiveDate; +/// let dt = +/// NaiveDate::from_ymd_opt(2015, 6, 30).unwrap().and_hms_milli_opt(23, 59, 59, 1_500).unwrap(); +/// assert_eq!(format!("{:?}", dt), "2015-06-30T23:59:60.500"); +/// ``` +impl fmt::Debug for NaiveDateTime { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.date.fmt(f)?; + f.write_char('T')?; + self.time.fmt(f) + } +} + +/// The `Display` output of the naive date and time `dt` is the same as +/// [`dt.format("%Y-%m-%d %H:%M:%S%.f")`](crate::format::strftime). +/// +/// It should be noted that, for leap seconds not on the minute boundary, +/// it may print a representation not distinguishable from non-leap seconds. +/// This doesn't matter in practice, since such leap seconds never happened. +/// (By the time of the first leap second on 1972-06-30, +/// every time zone offset around the world has standardized to the 5-minute alignment.) +/// +/// # Example +/// +/// ``` +/// use chrono::NaiveDate; +/// +/// let dt = NaiveDate::from_ymd_opt(2016, 11, 15).unwrap().and_hms_opt(7, 39, 24).unwrap(); +/// assert_eq!(format!("{}", dt), "2016-11-15 07:39:24"); +/// ``` +/// +/// Leap seconds may also be used. +/// +/// ``` +/// # use chrono::NaiveDate; +/// let dt = +/// NaiveDate::from_ymd_opt(2015, 6, 30).unwrap().and_hms_milli_opt(23, 59, 59, 1_500).unwrap(); +/// assert_eq!(format!("{}", dt), "2015-06-30 23:59:60.500"); +/// ``` +impl fmt::Display for NaiveDateTime { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.date.fmt(f)?; + f.write_char(' ')?; + self.time.fmt(f) + } +} + +/// Parsing a `str` into a `NaiveDateTime` uses the same format, +/// [`%Y-%m-%dT%H:%M:%S%.f`](crate::format::strftime), as in `Debug`. +/// +/// # Example +/// +/// ``` +/// use chrono::{NaiveDateTime, NaiveDate}; +/// +/// let dt = NaiveDate::from_ymd_opt(2015, 9, 18).unwrap().and_hms_opt(23, 56, 4).unwrap(); +/// assert_eq!("2015-09-18T23:56:04".parse::(), Ok(dt)); +/// +/// let dt = NaiveDate::from_ymd_opt(12345, 6, 7).unwrap().and_hms_milli_opt(7, 59, 59, 1_500).unwrap(); // leap second +/// assert_eq!("+12345-6-7T7:59:60.5".parse::(), Ok(dt)); +/// +/// assert!("foo".parse::().is_err()); +/// ``` +impl str::FromStr for NaiveDateTime { + type Err = ParseError; + + fn from_str(s: &str) -> ParseResult { + const ITEMS: &[Item<'static>] = &[ + Item::Numeric(Numeric::Year, Pad::Zero), + Item::Space(""), + Item::Literal("-"), + Item::Numeric(Numeric::Month, Pad::Zero), + Item::Space(""), + Item::Literal("-"), + Item::Numeric(Numeric::Day, Pad::Zero), + Item::Space(""), + Item::Literal("T"), // XXX shouldn't this be case-insensitive? + Item::Numeric(Numeric::Hour, Pad::Zero), + Item::Space(""), + Item::Literal(":"), + Item::Numeric(Numeric::Minute, Pad::Zero), + Item::Space(""), + Item::Literal(":"), + Item::Numeric(Numeric::Second, Pad::Zero), + Item::Fixed(Fixed::Nanosecond), + Item::Space(""), + ]; + + let mut parsed = Parsed::new(); + parse(&mut parsed, s, ITEMS.iter())?; + parsed.to_naive_datetime_with_offset(0) + } +} + +/// The default value for a NaiveDateTime is one with epoch 0 +/// that is, 1st of January 1970 at 00:00:00. +/// +/// # Example +/// +/// ```rust +/// use chrono::NaiveDateTime; +/// +/// assert_eq!(NaiveDateTime::default(), NaiveDateTime::UNIX_EPOCH); +/// ``` +impl Default for NaiveDateTime { + fn default() -> Self { + Self::UNIX_EPOCH + } +} diff --git a/third_party/rust/chrono/src/naive/datetime/serde.rs b/third_party/rust/chrono/src/naive/datetime/serde.rs new file mode 100644 index 00000000000..85fcb94f29e --- /dev/null +++ b/third_party/rust/chrono/src/naive/datetime/serde.rs @@ -0,0 +1,1304 @@ +use core::fmt; +use serde::{de, ser}; + +use super::NaiveDateTime; + +/// Serialize a `NaiveDateTime` as an ISO 8601 string +/// +/// See [the `naive::serde` module](crate::naive::serde) for alternate serialization formats. +impl ser::Serialize for NaiveDateTime { + fn serialize(&self, serializer: S) -> Result + where + S: ser::Serializer, + { + struct FormatWrapped<'a, D: 'a> { + inner: &'a D, + } + + impl fmt::Display for FormatWrapped<'_, D> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.inner.fmt(f) + } + } + + serializer.collect_str(&FormatWrapped { inner: &self }) + } +} + +struct NaiveDateTimeVisitor; + +impl de::Visitor<'_> for NaiveDateTimeVisitor { + type Value = NaiveDateTime; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a formatted date and time string") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + value.parse().map_err(E::custom) + } +} + +impl<'de> de::Deserialize<'de> for NaiveDateTime { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + deserializer.deserialize_str(NaiveDateTimeVisitor) + } +} + +/// Used to serialize/deserialize from nanosecond-precision timestamps +/// +/// # Example: +/// +/// ```rust +/// # use chrono::{NaiveDate, NaiveDateTime}; +/// # use serde_derive::{Deserialize, Serialize}; +/// use chrono::naive::serde::ts_nanoseconds; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_nanoseconds")] +/// time: NaiveDateTime, +/// } +/// +/// let time = NaiveDate::from_ymd_opt(2018, 5, 17) +/// .unwrap() +/// .and_hms_nano_opt(02, 04, 59, 918355733) +/// .unwrap(); +/// let my_s = S { time: time.clone() }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok::<(), serde_json::Error>(()) +/// ``` +pub mod ts_nanoseconds { + use core::fmt; + use serde::{de, ser}; + + use crate::serde::invalid_ts; + use crate::{DateTime, NaiveDateTime}; + + /// Serialize a datetime into an integer number of nanoseconds since the epoch + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Errors + /// + /// An `i64` with nanosecond precision can span a range of ~584 years. This function returns an + /// error on an out of range `DateTime`. + /// + /// The dates that can be represented as nanoseconds are between 1677-09-21T00:12:44.0 and + /// 2262-04-11T23:47:16.854775804. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{NaiveDate, NaiveDateTime}; + /// # use serde_derive::Serialize; + /// use chrono::naive::serde::ts_nanoseconds::serialize as to_nano_ts; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_nano_ts")] + /// time: NaiveDateTime, + /// } + /// + /// let my_s = S { + /// time: NaiveDate::from_ymd_opt(2018, 5, 17) + /// .unwrap() + /// .and_hms_nano_opt(02, 04, 59, 918355733) + /// .unwrap(), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn serialize(dt: &NaiveDateTime, serializer: S) -> Result + where + S: ser::Serializer, + { + serializer.serialize_i64(dt.and_utc().timestamp_nanos_opt().ok_or(ser::Error::custom( + "value out of range for a timestamp with nanosecond precision", + ))?) + } + + /// Deserialize a `NaiveDateTime` from a nanoseconds timestamp + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{DateTime, NaiveDateTime}; + /// # use serde_derive::Deserialize; + /// use chrono::naive::serde::ts_nanoseconds::deserialize as from_nano_ts; + /// #[derive(Debug, PartialEq, Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_nano_ts")] + /// time: NaiveDateTime, + /// } + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355733 }"#)?; + /// let expected = DateTime::from_timestamp(1526522699, 918355733).unwrap().naive_utc(); + /// assert_eq!(my_s, S { time: expected }); + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": -1 }"#)?; + /// let expected = DateTime::from_timestamp(-1, 999_999_999).unwrap().naive_utc(); + /// assert_eq!(my_s, S { time: expected }); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(NanoSecondsTimestampVisitor) + } + + pub(super) struct NanoSecondsTimestampVisitor; + + impl de::Visitor<'_> for NanoSecondsTimestampVisitor { + type Value = NaiveDateTime; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp") + } + + fn visit_i64(self, value: i64) -> Result + where + E: de::Error, + { + DateTime::from_timestamp( + value.div_euclid(1_000_000_000), + (value.rem_euclid(1_000_000_000)) as u32, + ) + .map(|dt| dt.naive_utc()) + .ok_or_else(|| invalid_ts(value)) + } + + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + DateTime::from_timestamp((value / 1_000_000_000) as i64, (value % 1_000_000_000) as u32) + .map(|dt| dt.naive_utc()) + .ok_or_else(|| invalid_ts(value)) + } + } +} + +/// Ser/de to/from optional timestamps in nanoseconds +/// +/// Intended for use with `serde`'s `with` attribute. +/// +/// # Example: +/// +/// ```rust +/// # use chrono::naive::{NaiveDate, NaiveDateTime}; +/// # use serde_derive::{Deserialize, Serialize}; +/// use chrono::naive::serde::ts_nanoseconds_option; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_nanoseconds_option")] +/// time: Option, +/// } +/// +/// let time = Some( +/// NaiveDate::from_ymd_opt(2018, 5, 17) +/// .unwrap() +/// .and_hms_nano_opt(02, 04, 59, 918355733) +/// .unwrap(), +/// ); +/// let my_s = S { time: time.clone() }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok::<(), serde_json::Error>(()) +/// ``` +pub mod ts_nanoseconds_option { + use core::fmt; + use serde::{de, ser}; + + use super::ts_nanoseconds::NanoSecondsTimestampVisitor; + use crate::NaiveDateTime; + + /// Serialize a datetime into an integer number of nanoseconds since the epoch or none + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Errors + /// + /// An `i64` with nanosecond precision can span a range of ~584 years. This function returns an + /// error on an out of range `DateTime`. + /// + /// The dates that can be represented as nanoseconds are between 1677-09-21T00:12:44.0 and + /// 2262-04-11T23:47:16.854775804. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::naive::{NaiveDate, NaiveDateTime}; + /// # use serde_derive::Serialize; + /// use chrono::naive::serde::ts_nanoseconds_option::serialize as to_nano_tsopt; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_nano_tsopt")] + /// time: Option, + /// } + /// + /// let my_s = S { + /// time: Some( + /// NaiveDate::from_ymd_opt(2018, 5, 17) + /// .unwrap() + /// .and_hms_nano_opt(02, 04, 59, 918355733) + /// .unwrap(), + /// ), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1526522699918355733}"#); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn serialize(opt: &Option, serializer: S) -> Result + where + S: ser::Serializer, + { + match *opt { + Some(ref dt) => serializer.serialize_some(&dt.and_utc().timestamp_nanos_opt().ok_or( + ser::Error::custom("value out of range for a timestamp with nanosecond precision"), + )?), + None => serializer.serialize_none(), + } + } + + /// Deserialize a `NaiveDateTime` from a nanosecond timestamp or none + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{DateTime, NaiveDateTime}; + /// # use serde_derive::Deserialize; + /// use chrono::naive::serde::ts_nanoseconds_option::deserialize as from_nano_tsopt; + /// #[derive(Debug, PartialEq, Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_nano_tsopt")] + /// time: Option, + /// } + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355733 }"#)?; + /// let expected = DateTime::from_timestamp(1526522699, 918355733).unwrap().naive_utc(); + /// assert_eq!(my_s, S { time: Some(expected) }); + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": -1 }"#)?; + /// let expected = DateTime::from_timestamp(-1, 999_999_999).unwrap().naive_utc(); + /// assert_eq!(my_s, S { time: Some(expected) }); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result, D::Error> + where + D: de::Deserializer<'de>, + { + d.deserialize_option(OptionNanoSecondsTimestampVisitor) + } + + struct OptionNanoSecondsTimestampVisitor; + + impl<'de> de::Visitor<'de> for OptionNanoSecondsTimestampVisitor { + type Value = Option; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp in nanoseconds or none") + } + + /// Deserialize a timestamp in nanoseconds since the epoch + fn visit_some(self, d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(NanoSecondsTimestampVisitor).map(Some) + } + + /// Deserialize a timestamp in nanoseconds since the epoch + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(None) + } + + /// Deserialize a timestamp in nanoseconds since the epoch + fn visit_unit(self) -> Result + where + E: de::Error, + { + Ok(None) + } + } +} + +/// Used to serialize/deserialize from microsecond-precision timestamps +/// +/// # Example: +/// +/// ```rust +/// # use chrono::{NaiveDate, NaiveDateTime}; +/// # use serde_derive::{Deserialize, Serialize}; +/// use chrono::naive::serde::ts_microseconds; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_microseconds")] +/// time: NaiveDateTime, +/// } +/// +/// let time = NaiveDate::from_ymd_opt(2018, 5, 17) +/// .unwrap() +/// .and_hms_micro_opt(02, 04, 59, 918355) +/// .unwrap(); +/// let my_s = S { time: time.clone() }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":1526522699918355}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok::<(), serde_json::Error>(()) +/// ``` +pub mod ts_microseconds { + use core::fmt; + use serde::{de, ser}; + + use crate::serde::invalid_ts; + use crate::{DateTime, NaiveDateTime}; + + /// Serialize a datetime into an integer number of microseconds since the epoch + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{NaiveDate, NaiveDateTime}; + /// # use serde_derive::Serialize; + /// use chrono::naive::serde::ts_microseconds::serialize as to_micro_ts; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_micro_ts")] + /// time: NaiveDateTime, + /// } + /// + /// let my_s = S { + /// time: NaiveDate::from_ymd_opt(2018, 5, 17) + /// .unwrap() + /// .and_hms_micro_opt(02, 04, 59, 918355) + /// .unwrap(), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn serialize(dt: &NaiveDateTime, serializer: S) -> Result + where + S: ser::Serializer, + { + serializer.serialize_i64(dt.and_utc().timestamp_micros()) + } + + /// Deserialize a `NaiveDateTime` from a microseconds timestamp + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{DateTime, NaiveDateTime}; + /// # use serde_derive::Deserialize; + /// use chrono::naive::serde::ts_microseconds::deserialize as from_micro_ts; + /// #[derive(Debug, PartialEq, Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_micro_ts")] + /// time: NaiveDateTime, + /// } + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355 }"#)?; + /// let expected = DateTime::from_timestamp(1526522699, 918355000).unwrap().naive_utc(); + /// assert_eq!(my_s, S { time: expected }); + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": -1 }"#)?; + /// let expected = DateTime::from_timestamp(-1, 999_999_000).unwrap().naive_utc(); + /// assert_eq!(my_s, S { time: expected }); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(MicroSecondsTimestampVisitor) + } + + pub(super) struct MicroSecondsTimestampVisitor; + + impl de::Visitor<'_> for MicroSecondsTimestampVisitor { + type Value = NaiveDateTime; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp") + } + + fn visit_i64(self, value: i64) -> Result + where + E: de::Error, + { + DateTime::from_timestamp_micros(value) + .map(|dt| dt.naive_utc()) + .ok_or_else(|| invalid_ts(value)) + } + + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + DateTime::from_timestamp( + (value / 1_000_000) as i64, + ((value % 1_000_000) * 1_000) as u32, + ) + .map(|dt| dt.naive_utc()) + .ok_or_else(|| invalid_ts(value)) + } + } +} + +/// Ser/de to/from optional timestamps in microseconds +/// +/// Intended for use with `serde`'s `with` attribute. +/// +/// # Example: +/// +/// ```rust +/// # use chrono::naive::{NaiveDate, NaiveDateTime}; +/// # use serde_derive::{Deserialize, Serialize}; +/// use chrono::naive::serde::ts_microseconds_option; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_microseconds_option")] +/// time: Option, +/// } +/// +/// let time = Some( +/// NaiveDate::from_ymd_opt(2018, 5, 17) +/// .unwrap() +/// .and_hms_micro_opt(02, 04, 59, 918355) +/// .unwrap(), +/// ); +/// let my_s = S { time: time.clone() }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":1526522699918355}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok::<(), serde_json::Error>(()) +/// ``` +pub mod ts_microseconds_option { + use core::fmt; + use serde::{de, ser}; + + use super::ts_microseconds::MicroSecondsTimestampVisitor; + use crate::NaiveDateTime; + + /// Serialize a datetime into an integer number of microseconds since the epoch or none + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::naive::{NaiveDate, NaiveDateTime}; + /// # use serde_derive::Serialize; + /// use chrono::naive::serde::ts_microseconds_option::serialize as to_micro_tsopt; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_micro_tsopt")] + /// time: Option, + /// } + /// + /// let my_s = S { + /// time: Some( + /// NaiveDate::from_ymd_opt(2018, 5, 17) + /// .unwrap() + /// .and_hms_micro_opt(02, 04, 59, 918355) + /// .unwrap(), + /// ), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1526522699918355}"#); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn serialize(opt: &Option, serializer: S) -> Result + where + S: ser::Serializer, + { + match *opt { + Some(ref dt) => serializer.serialize_some(&dt.and_utc().timestamp_micros()), + None => serializer.serialize_none(), + } + } + + /// Deserialize a `NaiveDateTime` from a nanosecond timestamp or none + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{DateTime, NaiveDateTime}; + /// # use serde_derive::Deserialize; + /// use chrono::naive::serde::ts_microseconds_option::deserialize as from_micro_tsopt; + /// #[derive(Debug, PartialEq, Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_micro_tsopt")] + /// time: Option, + /// } + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918355 }"#)?; + /// let expected = DateTime::from_timestamp(1526522699, 918355000).unwrap().naive_utc(); + /// assert_eq!(my_s, S { time: Some(expected) }); + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": -1 }"#)?; + /// let expected = DateTime::from_timestamp(-1, 999_999_000).unwrap().naive_utc(); + /// assert_eq!(my_s, S { time: Some(expected) }); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result, D::Error> + where + D: de::Deserializer<'de>, + { + d.deserialize_option(OptionMicroSecondsTimestampVisitor) + } + + struct OptionMicroSecondsTimestampVisitor; + + impl<'de> de::Visitor<'de> for OptionMicroSecondsTimestampVisitor { + type Value = Option; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp in microseconds or none") + } + + /// Deserialize a timestamp in microseconds since the epoch + fn visit_some(self, d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(MicroSecondsTimestampVisitor).map(Some) + } + + /// Deserialize a timestamp in microseconds since the epoch + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(None) + } + + /// Deserialize a timestamp in microseconds since the epoch + fn visit_unit(self) -> Result + where + E: de::Error, + { + Ok(None) + } + } +} + +/// Used to serialize/deserialize from millisecond-precision timestamps +/// +/// # Example: +/// +/// ```rust +/// # use chrono::{NaiveDate, NaiveDateTime}; +/// # use serde_derive::{Deserialize, Serialize}; +/// use chrono::naive::serde::ts_milliseconds; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_milliseconds")] +/// time: NaiveDateTime, +/// } +/// +/// let time = +/// NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_milli_opt(02, 04, 59, 918).unwrap(); +/// let my_s = S { time: time.clone() }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":1526522699918}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok::<(), serde_json::Error>(()) +/// ``` +pub mod ts_milliseconds { + use core::fmt; + use serde::{de, ser}; + + use crate::serde::invalid_ts; + use crate::{DateTime, NaiveDateTime}; + + /// Serialize a datetime into an integer number of milliseconds since the epoch + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{NaiveDate, NaiveDateTime}; + /// # use serde_derive::Serialize; + /// use chrono::naive::serde::ts_milliseconds::serialize as to_milli_ts; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_milli_ts")] + /// time: NaiveDateTime, + /// } + /// + /// let my_s = S { + /// time: NaiveDate::from_ymd_opt(2018, 5, 17) + /// .unwrap() + /// .and_hms_milli_opt(02, 04, 59, 918) + /// .unwrap(), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1526522699918}"#); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn serialize(dt: &NaiveDateTime, serializer: S) -> Result + where + S: ser::Serializer, + { + serializer.serialize_i64(dt.and_utc().timestamp_millis()) + } + + /// Deserialize a `NaiveDateTime` from a milliseconds timestamp + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{DateTime, NaiveDateTime}; + /// # use serde_derive::Deserialize; + /// use chrono::naive::serde::ts_milliseconds::deserialize as from_milli_ts; + /// #[derive(Debug, PartialEq, Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_milli_ts")] + /// time: NaiveDateTime, + /// } + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918 }"#)?; + /// let expected = DateTime::from_timestamp(1526522699, 918000000).unwrap().naive_utc(); + /// assert_eq!(my_s, S { time: expected }); + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": -1 }"#)?; + /// let expected = DateTime::from_timestamp(-1, 999_000_000).unwrap().naive_utc(); + /// assert_eq!(my_s, S { time: expected }); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(MilliSecondsTimestampVisitor) + } + + pub(super) struct MilliSecondsTimestampVisitor; + + impl de::Visitor<'_> for MilliSecondsTimestampVisitor { + type Value = NaiveDateTime; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp") + } + + fn visit_i64(self, value: i64) -> Result + where + E: de::Error, + { + DateTime::from_timestamp_millis(value) + .map(|dt| dt.naive_utc()) + .ok_or_else(|| invalid_ts(value)) + } + + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + DateTime::from_timestamp((value / 1000) as i64, ((value % 1000) * 1_000_000) as u32) + .map(|dt| dt.naive_utc()) + .ok_or_else(|| invalid_ts(value)) + } + } +} + +/// Ser/de to/from optional timestamps in milliseconds +/// +/// Intended for use with `serde`'s `with` attribute. +/// +/// # Example: +/// +/// ```rust +/// # use chrono::naive::{NaiveDate, NaiveDateTime}; +/// # use serde_derive::{Deserialize, Serialize}; +/// use chrono::naive::serde::ts_milliseconds_option; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_milliseconds_option")] +/// time: Option, +/// } +/// +/// let time = Some( +/// NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_milli_opt(02, 04, 59, 918).unwrap(), +/// ); +/// let my_s = S { time: time.clone() }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":1526522699918}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok::<(), serde_json::Error>(()) +/// ``` +pub mod ts_milliseconds_option { + use core::fmt; + use serde::{de, ser}; + + use super::ts_milliseconds::MilliSecondsTimestampVisitor; + use crate::NaiveDateTime; + + /// Serialize a datetime into an integer number of milliseconds since the epoch or none + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::naive::{NaiveDate, NaiveDateTime}; + /// # use serde_derive::Serialize; + /// use chrono::naive::serde::ts_milliseconds_option::serialize as to_milli_tsopt; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_milli_tsopt")] + /// time: Option, + /// } + /// + /// let my_s = S { + /// time: Some( + /// NaiveDate::from_ymd_opt(2018, 5, 17) + /// .unwrap() + /// .and_hms_milli_opt(02, 04, 59, 918) + /// .unwrap(), + /// ), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1526522699918}"#); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn serialize(opt: &Option, serializer: S) -> Result + where + S: ser::Serializer, + { + match *opt { + Some(ref dt) => serializer.serialize_some(&dt.and_utc().timestamp_millis()), + None => serializer.serialize_none(), + } + } + + /// Deserialize a `NaiveDateTime` from a millisecond timestamp or none + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{DateTime, NaiveDateTime}; + /// # use serde_derive::Deserialize; + /// use chrono::naive::serde::ts_milliseconds_option::deserialize as from_milli_tsopt; + /// #[derive(Debug, PartialEq, Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_milli_tsopt")] + /// time: Option, + /// } + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": 1526522699918 }"#)?; + /// let expected = DateTime::from_timestamp(1526522699, 918000000).unwrap().naive_utc(); + /// assert_eq!(my_s, S { time: Some(expected) }); + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": -1 }"#)?; + /// let expected = DateTime::from_timestamp(-1, 999_000_000).unwrap().naive_utc(); + /// assert_eq!(my_s, S { time: Some(expected) }); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result, D::Error> + where + D: de::Deserializer<'de>, + { + d.deserialize_option(OptionMilliSecondsTimestampVisitor) + } + + struct OptionMilliSecondsTimestampVisitor; + + impl<'de> de::Visitor<'de> for OptionMilliSecondsTimestampVisitor { + type Value = Option; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp in milliseconds or none") + } + + /// Deserialize a timestamp in milliseconds since the epoch + fn visit_some(self, d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(MilliSecondsTimestampVisitor).map(Some) + } + + /// Deserialize a timestamp in milliseconds since the epoch + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(None) + } + + /// Deserialize a timestamp in milliseconds since the epoch + fn visit_unit(self) -> Result + where + E: de::Error, + { + Ok(None) + } + } +} + +/// Used to serialize/deserialize from second-precision timestamps +/// +/// # Example: +/// +/// ```rust +/// # use chrono::{NaiveDate, NaiveDateTime}; +/// # use serde_derive::{Deserialize, Serialize}; +/// use chrono::naive::serde::ts_seconds; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_seconds")] +/// time: NaiveDateTime, +/// } +/// +/// let time = NaiveDate::from_ymd_opt(2015, 5, 15).unwrap().and_hms_opt(10, 0, 0).unwrap(); +/// let my_s = S { time: time.clone() }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":1431684000}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok::<(), serde_json::Error>(()) +/// ``` +pub mod ts_seconds { + use core::fmt; + use serde::{de, ser}; + + use crate::serde::invalid_ts; + use crate::{DateTime, NaiveDateTime}; + + /// Serialize a datetime into an integer number of seconds since the epoch + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{NaiveDate, NaiveDateTime}; + /// # use serde_derive::Serialize; + /// use chrono::naive::serde::ts_seconds::serialize as to_ts; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_ts")] + /// time: NaiveDateTime, + /// } + /// + /// let my_s = + /// S { time: NaiveDate::from_ymd_opt(2015, 5, 15).unwrap().and_hms_opt(10, 0, 0).unwrap() }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1431684000}"#); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn serialize(dt: &NaiveDateTime, serializer: S) -> Result + where + S: ser::Serializer, + { + serializer.serialize_i64(dt.and_utc().timestamp()) + } + + /// Deserialize a `NaiveDateTime` from a seconds timestamp + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{DateTime, NaiveDateTime}; + /// # use serde_derive::Deserialize; + /// use chrono::naive::serde::ts_seconds::deserialize as from_ts; + /// #[derive(Debug, PartialEq, Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_ts")] + /// time: NaiveDateTime, + /// } + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": 1431684000 }"#)?; + /// let expected = DateTime::from_timestamp(1431684000, 0).unwrap().naive_utc(); + /// assert_eq!(my_s, S { time: expected }); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(SecondsTimestampVisitor) + } + + pub(super) struct SecondsTimestampVisitor; + + impl de::Visitor<'_> for SecondsTimestampVisitor { + type Value = NaiveDateTime; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp") + } + + fn visit_i64(self, value: i64) -> Result + where + E: de::Error, + { + DateTime::from_timestamp(value, 0) + .map(|dt| dt.naive_utc()) + .ok_or_else(|| invalid_ts(value)) + } + + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + if value > i64::MAX as u64 { + Err(invalid_ts(value)) + } else { + DateTime::from_timestamp(value as i64, 0) + .map(|dt| dt.naive_utc()) + .ok_or_else(|| invalid_ts(value)) + } + } + } +} + +/// Ser/de to/from optional timestamps in seconds +/// +/// Intended for use with `serde`'s `with` attribute. +/// +/// # Example: +/// +/// ```rust +/// # use chrono::naive::{NaiveDate, NaiveDateTime}; +/// # use serde_derive::{Deserialize, Serialize}; +/// use chrono::naive::serde::ts_seconds_option; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_seconds_option")] +/// time: Option, +/// } +/// +/// let time = Some(NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_opt(02, 04, 59).unwrap()); +/// let my_s = S { time: time.clone() }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":1526522699}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok::<(), serde_json::Error>(()) +/// ``` +pub mod ts_seconds_option { + use core::fmt; + use serde::{de, ser}; + + use super::ts_seconds::SecondsTimestampVisitor; + use crate::NaiveDateTime; + + /// Serialize a datetime into an integer number of seconds since the epoch or none + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::naive::{NaiveDate, NaiveDateTime}; + /// # use serde_derive::Serialize; + /// use chrono::naive::serde::ts_seconds_option::serialize as to_tsopt; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_tsopt")] + /// time: Option, + /// } + /// + /// let expected = NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_opt(02, 04, 59).unwrap(); + /// let my_s = S { time: Some(expected) }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":1526522699}"#); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn serialize(opt: &Option, serializer: S) -> Result + where + S: ser::Serializer, + { + match *opt { + Some(ref dt) => serializer.serialize_some(&dt.and_utc().timestamp()), + None => serializer.serialize_none(), + } + } + + /// Deserialize a `NaiveDateTime` from a second timestamp or none + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{DateTime, NaiveDateTime}; + /// # use serde_derive::Deserialize; + /// use chrono::naive::serde::ts_seconds_option::deserialize as from_tsopt; + /// #[derive(Debug, PartialEq, Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_tsopt")] + /// time: Option, + /// } + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": 1431684000 }"#)?; + /// let expected = DateTime::from_timestamp(1431684000, 0).unwrap().naive_utc(); + /// assert_eq!(my_s, S { time: Some(expected) }); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result, D::Error> + where + D: de::Deserializer<'de>, + { + d.deserialize_option(OptionSecondsTimestampVisitor) + } + + struct OptionSecondsTimestampVisitor; + + impl<'de> de::Visitor<'de> for OptionSecondsTimestampVisitor { + type Value = Option; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp in seconds or none") + } + + /// Deserialize a timestamp in seconds since the epoch + fn visit_some(self, d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(SecondsTimestampVisitor).map(Some) + } + + /// Deserialize a timestamp in seconds since the epoch + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(None) + } + + /// Deserialize a timestamp in seconds since the epoch + fn visit_unit(self) -> Result + where + E: de::Error, + { + Ok(None) + } + } +} + +#[cfg(test)] +mod tests { + use crate::serde::ts_nanoseconds_option; + use crate::{DateTime, NaiveDate, NaiveDateTime, TimeZone, Utc}; + + use bincode::{deserialize, serialize}; + use serde_derive::{Deserialize, Serialize}; + + #[test] + fn test_serde_serialize() { + assert_eq!( + serde_json::to_string( + &NaiveDate::from_ymd_opt(2016, 7, 8) + .unwrap() + .and_hms_milli_opt(9, 10, 48, 90) + .unwrap() + ) + .ok(), + Some(r#""2016-07-08T09:10:48.090""#.into()) + ); + assert_eq!( + serde_json::to_string( + &NaiveDate::from_ymd_opt(2014, 7, 24).unwrap().and_hms_opt(12, 34, 6).unwrap() + ) + .ok(), + Some(r#""2014-07-24T12:34:06""#.into()) + ); + assert_eq!( + serde_json::to_string( + &NaiveDate::from_ymd_opt(0, 1, 1) + .unwrap() + .and_hms_milli_opt(0, 0, 59, 1_000) + .unwrap() + ) + .ok(), + Some(r#""0000-01-01T00:00:60""#.into()) + ); + assert_eq!( + serde_json::to_string( + &NaiveDate::from_ymd_opt(-1, 12, 31) + .unwrap() + .and_hms_nano_opt(23, 59, 59, 7) + .unwrap() + ) + .ok(), + Some(r#""-0001-12-31T23:59:59.000000007""#.into()) + ); + assert_eq!( + serde_json::to_string(&NaiveDate::MIN.and_hms_opt(0, 0, 0).unwrap()).ok(), + Some(r#""-262143-01-01T00:00:00""#.into()) + ); + assert_eq!( + serde_json::to_string( + &NaiveDate::MAX.and_hms_nano_opt(23, 59, 59, 1_999_999_999).unwrap() + ) + .ok(), + Some(r#""+262142-12-31T23:59:60.999999999""#.into()) + ); + } + + #[test] + fn test_serde_deserialize() { + let from_str = serde_json::from_str::; + + assert_eq!( + from_str(r#""2016-07-08T09:10:48.090""#).ok(), + Some( + NaiveDate::from_ymd_opt(2016, 7, 8) + .unwrap() + .and_hms_milli_opt(9, 10, 48, 90) + .unwrap() + ) + ); + assert_eq!( + from_str(r#""2016-7-8T9:10:48.09""#).ok(), + Some( + NaiveDate::from_ymd_opt(2016, 7, 8) + .unwrap() + .and_hms_milli_opt(9, 10, 48, 90) + .unwrap() + ) + ); + assert_eq!( + from_str(r#""2014-07-24T12:34:06""#).ok(), + Some(NaiveDate::from_ymd_opt(2014, 7, 24).unwrap().and_hms_opt(12, 34, 6).unwrap()) + ); + assert_eq!( + from_str(r#""0000-01-01T00:00:60""#).ok(), + Some( + NaiveDate::from_ymd_opt(0, 1, 1) + .unwrap() + .and_hms_milli_opt(0, 0, 59, 1_000) + .unwrap() + ) + ); + assert_eq!( + from_str(r#""0-1-1T0:0:60""#).ok(), + Some( + NaiveDate::from_ymd_opt(0, 1, 1) + .unwrap() + .and_hms_milli_opt(0, 0, 59, 1_000) + .unwrap() + ) + ); + assert_eq!( + from_str(r#""-0001-12-31T23:59:59.000000007""#).ok(), + Some( + NaiveDate::from_ymd_opt(-1, 12, 31) + .unwrap() + .and_hms_nano_opt(23, 59, 59, 7) + .unwrap() + ) + ); + assert_eq!( + from_str(r#""-262143-01-01T00:00:00""#).ok(), + Some(NaiveDate::MIN.and_hms_opt(0, 0, 0).unwrap()) + ); + assert_eq!( + from_str(r#""+262142-12-31T23:59:60.999999999""#).ok(), + Some(NaiveDate::MAX.and_hms_nano_opt(23, 59, 59, 1_999_999_999).unwrap()) + ); + assert_eq!( + from_str(r#""+262142-12-31T23:59:60.9999999999997""#).ok(), // excess digits are ignored + Some(NaiveDate::MAX.and_hms_nano_opt(23, 59, 59, 1_999_999_999).unwrap()) + ); + + // bad formats + assert!(from_str(r#""""#).is_err()); + assert!(from_str(r#""2016-07-08""#).is_err()); + assert!(from_str(r#""09:10:48.090""#).is_err()); + assert!(from_str(r#""20160708T091048.090""#).is_err()); + assert!(from_str(r#""2000-00-00T00:00:00""#).is_err()); + assert!(from_str(r#""2000-02-30T00:00:00""#).is_err()); + assert!(from_str(r#""2001-02-29T00:00:00""#).is_err()); + assert!(from_str(r#""2002-02-28T24:00:00""#).is_err()); + assert!(from_str(r#""2002-02-28T23:60:00""#).is_err()); + assert!(from_str(r#""2002-02-28T23:59:61""#).is_err()); + assert!(from_str(r#""2016-07-08T09:10:48,090""#).is_err()); + assert!(from_str(r#""2016-07-08 09:10:48.090""#).is_err()); + assert!(from_str(r#""2016-007-08T09:10:48.090""#).is_err()); + assert!(from_str(r#""yyyy-mm-ddThh:mm:ss.fffffffff""#).is_err()); + assert!(from_str(r#"20160708000000"#).is_err()); + assert!(from_str(r#"{}"#).is_err()); + // pre-0.3.0 rustc-serialize format is now invalid + assert!(from_str(r#"{"date":{"ymdf":20},"time":{"secs":0,"frac":0}}"#).is_err()); + assert!(from_str(r#"null"#).is_err()); + } + + // Bincode is relevant to test separately from JSON because + // it is not self-describing. + #[test] + fn test_serde_bincode() { + let dt = + NaiveDate::from_ymd_opt(2016, 7, 8).unwrap().and_hms_milli_opt(9, 10, 48, 90).unwrap(); + let encoded = serialize(&dt).unwrap(); + let decoded: NaiveDateTime = deserialize(&encoded).unwrap(); + assert_eq!(dt, decoded); + } + + #[test] + fn test_serde_bincode_optional() { + #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] + struct Test { + one: Option, + #[serde(with = "ts_nanoseconds_option")] + two: Option>, + } + + let expected = + Test { one: Some(1), two: Some(Utc.with_ymd_and_hms(1970, 1, 1, 0, 1, 1).unwrap()) }; + let bytes: Vec = serialize(&expected).unwrap(); + let actual = deserialize::(&(bytes)).unwrap(); + + assert_eq!(expected, actual); + } +} diff --git a/third_party/rust/chrono/src/naive/datetime/tests.rs b/third_party/rust/chrono/src/naive/datetime/tests.rs new file mode 100644 index 00000000000..75a168a7194 --- /dev/null +++ b/third_party/rust/chrono/src/naive/datetime/tests.rs @@ -0,0 +1,408 @@ +use super::NaiveDateTime; +use crate::{Datelike, FixedOffset, MappedLocalTime, NaiveDate, TimeDelta, Utc}; + +#[test] +fn test_datetime_add() { + fn check( + (y, m, d, h, n, s): (i32, u32, u32, u32, u32, u32), + rhs: TimeDelta, + result: Option<(i32, u32, u32, u32, u32, u32)>, + ) { + let lhs = NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap(); + let sum = result.map(|(y, m, d, h, n, s)| { + NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap() + }); + assert_eq!(lhs.checked_add_signed(rhs), sum); + assert_eq!(lhs.checked_sub_signed(-rhs), sum); + } + let seconds = |s| TimeDelta::try_seconds(s).unwrap(); + + check((2014, 5, 6, 7, 8, 9), seconds(3600 + 60 + 1), Some((2014, 5, 6, 8, 9, 10))); + check((2014, 5, 6, 7, 8, 9), seconds(-(3600 + 60 + 1)), Some((2014, 5, 6, 6, 7, 8))); + check((2014, 5, 6, 7, 8, 9), seconds(86399), Some((2014, 5, 7, 7, 8, 8))); + check((2014, 5, 6, 7, 8, 9), seconds(86_400 * 10), Some((2014, 5, 16, 7, 8, 9))); + check((2014, 5, 6, 7, 8, 9), seconds(-86_400 * 10), Some((2014, 4, 26, 7, 8, 9))); + check((2014, 5, 6, 7, 8, 9), seconds(86_400 * 10), Some((2014, 5, 16, 7, 8, 9))); + + // overflow check + // assumes that we have correct values for MAX/MIN_DAYS_FROM_YEAR_0 from `naive::date`. + // (they are private constants, but the equivalence is tested in that module.) + let max_days_from_year_0 = + NaiveDate::MAX.signed_duration_since(NaiveDate::from_ymd_opt(0, 1, 1).unwrap()); + check((0, 1, 1, 0, 0, 0), max_days_from_year_0, Some((NaiveDate::MAX.year(), 12, 31, 0, 0, 0))); + check( + (0, 1, 1, 0, 0, 0), + max_days_from_year_0 + seconds(86399), + Some((NaiveDate::MAX.year(), 12, 31, 23, 59, 59)), + ); + check((0, 1, 1, 0, 0, 0), max_days_from_year_0 + seconds(86_400), None); + check((0, 1, 1, 0, 0, 0), TimeDelta::MAX, None); + + let min_days_from_year_0 = + NaiveDate::MIN.signed_duration_since(NaiveDate::from_ymd_opt(0, 1, 1).unwrap()); + check((0, 1, 1, 0, 0, 0), min_days_from_year_0, Some((NaiveDate::MIN.year(), 1, 1, 0, 0, 0))); + check((0, 1, 1, 0, 0, 0), min_days_from_year_0 - seconds(1), None); + check((0, 1, 1, 0, 0, 0), TimeDelta::MIN, None); +} + +#[test] +fn test_datetime_sub() { + let ymdhms = + |y, m, d, h, n, s| NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap(); + let since = NaiveDateTime::signed_duration_since; + assert_eq!(since(ymdhms(2014, 5, 6, 7, 8, 9), ymdhms(2014, 5, 6, 7, 8, 9)), TimeDelta::zero()); + assert_eq!( + since(ymdhms(2014, 5, 6, 7, 8, 10), ymdhms(2014, 5, 6, 7, 8, 9)), + TimeDelta::try_seconds(1).unwrap() + ); + assert_eq!( + since(ymdhms(2014, 5, 6, 7, 8, 9), ymdhms(2014, 5, 6, 7, 8, 10)), + TimeDelta::try_seconds(-1).unwrap() + ); + assert_eq!( + since(ymdhms(2014, 5, 7, 7, 8, 9), ymdhms(2014, 5, 6, 7, 8, 10)), + TimeDelta::try_seconds(86399).unwrap() + ); + assert_eq!( + since(ymdhms(2001, 9, 9, 1, 46, 39), ymdhms(1970, 1, 1, 0, 0, 0)), + TimeDelta::try_seconds(999_999_999).unwrap() + ); +} + +#[test] +fn test_datetime_addassignment() { + let ymdhms = + |y, m, d, h, n, s| NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap(); + let mut date = ymdhms(2016, 10, 1, 10, 10, 10); + date += TimeDelta::try_minutes(10_000_000).unwrap(); + assert_eq!(date, ymdhms(2035, 10, 6, 20, 50, 10)); + date += TimeDelta::try_days(10).unwrap(); + assert_eq!(date, ymdhms(2035, 10, 16, 20, 50, 10)); +} + +#[test] +fn test_datetime_subassignment() { + let ymdhms = + |y, m, d, h, n, s| NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap(); + let mut date = ymdhms(2016, 10, 1, 10, 10, 10); + date -= TimeDelta::try_minutes(10_000_000).unwrap(); + assert_eq!(date, ymdhms(1997, 9, 26, 23, 30, 10)); + date -= TimeDelta::try_days(10).unwrap(); + assert_eq!(date, ymdhms(1997, 9, 16, 23, 30, 10)); +} + +#[test] +fn test_core_duration_ops() { + use core::time::Duration; + + let mut dt = NaiveDate::from_ymd_opt(2023, 8, 29).unwrap().and_hms_opt(11, 34, 12).unwrap(); + let same = dt + Duration::ZERO; + assert_eq!(dt, same); + + dt += Duration::new(3600, 0); + assert_eq!(dt, NaiveDate::from_ymd_opt(2023, 8, 29).unwrap().and_hms_opt(12, 34, 12).unwrap()); +} + +#[test] +#[should_panic] +fn test_core_duration_max() { + use core::time::Duration; + + let mut utc_dt = NaiveDate::from_ymd_opt(2023, 8, 29).unwrap().and_hms_opt(11, 34, 12).unwrap(); + utc_dt += Duration::MAX; +} + +#[test] +fn test_datetime_from_str() { + // valid cases + let valid = [ + "2001-02-03T04:05:06", + "2012-12-12T12:12:12", + "2015-02-18T23:16:09.153", + "2015-2-18T23:16:09.153", + "-77-02-18T23:16:09", + "+82701-05-6T15:9:60.898989898989", + " +82701 - 05 - 6 T 15 : 9 : 60.898989898989 ", + ]; + for &s in &valid { + eprintln!("test_parse_naivedatetime valid {:?}", s); + let d = match s.parse::() { + Ok(d) => d, + Err(e) => panic!("parsing `{}` has failed: {}", s, e), + }; + let s_ = format!("{:?}", d); + // `s` and `s_` may differ, but `s.parse()` and `s_.parse()` must be same + let d_ = match s_.parse::() { + Ok(d) => d, + Err(e) => { + panic!("`{}` is parsed into `{:?}`, but reparsing that has failed: {}", s, d, e) + } + }; + assert!( + d == d_, + "`{}` is parsed into `{:?}`, but reparsed result \ + `{:?}` does not match", + s, + d, + d_ + ); + } + + // some invalid cases + // since `ParseErrorKind` is private, all we can do is to check if there was an error + let invalid = [ + "", // empty + "x", // invalid / missing data + "15", // missing data + "15:8:9", // looks like a time (invalid date) + "15-8-9", // looks like a date (invalid) + "Fri, 09 Aug 2013 23:54:35 GMT", // valid date, wrong format + "Sat Jun 30 23:59:60 2012", // valid date, wrong format + "1441497364.649", // valid date, wrong format + "+1441497364.649", // valid date, wrong format + "+1441497364", // valid date, wrong format + "2014/02/03 04:05:06", // valid date, wrong format + "2015-15-15T15:15:15", // invalid date + "2012-12-12T12:12:12x", // bad timezone / trailing literal + "2012-12-12T12:12:12+00:00", // unexpected timezone / trailing literal + "2012-12-12T12:12:12 +00:00", // unexpected timezone / trailing literal + "2012-12-12T12:12:12 GMT", // unexpected timezone / trailing literal + "2012-123-12T12:12:12", // invalid month + "2012-12-12t12:12:12", // bad divider 't' + "2012-12-12 12:12:12", // missing divider 'T' + "2012-12-12T12:12:12Z", // trailing char 'Z' + "+ 82701-123-12T12:12:12", // strange year, invalid month + "+802701-123-12T12:12:12", // out-of-bound year, invalid month + ]; + for &s in &invalid { + eprintln!("test_datetime_from_str invalid {:?}", s); + assert!(s.parse::().is_err()); + } +} + +#[test] +fn test_datetime_parse_from_str() { + let ymdhms = + |y, m, d, h, n, s| NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap(); + let ymdhmsn = |y, m, d, h, n, s, nano| { + NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_nano_opt(h, n, s, nano).unwrap() + }; + assert_eq!( + NaiveDateTime::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), + Ok(ymdhms(2014, 5, 7, 12, 34, 56)) + ); // ignore offset + assert_eq!( + NaiveDateTime::parse_from_str("2015-W06-1 000000", "%G-W%V-%u%H%M%S"), + Ok(ymdhms(2015, 2, 2, 0, 0, 0)) + ); + assert_eq!( + NaiveDateTime::parse_from_str("Fri, 09 Aug 2013 23:54:35 GMT", "%a, %d %b %Y %H:%M:%S GMT"), + Ok(ymdhms(2013, 8, 9, 23, 54, 35)) + ); + assert!( + NaiveDateTime::parse_from_str("Sat, 09 Aug 2013 23:54:35 GMT", "%a, %d %b %Y %H:%M:%S GMT") + .is_err() + ); + assert!(NaiveDateTime::parse_from_str("2014-5-7 Q2 12:3456", "%Y-%m-%d Q%q %H:%M:%S").is_err()); + assert!(NaiveDateTime::parse_from_str("12:34:56", "%H:%M:%S").is_err()); // insufficient + assert_eq!( + NaiveDateTime::parse_from_str("1441497364", "%s"), + Ok(ymdhms(2015, 9, 5, 23, 56, 4)) + ); + assert_eq!( + NaiveDateTime::parse_from_str("1283929614.1234", "%s.%f"), + Ok(ymdhmsn(2010, 9, 8, 7, 6, 54, 1234)) + ); + assert_eq!( + NaiveDateTime::parse_from_str("1441497364.649", "%s%.3f"), + Ok(ymdhmsn(2015, 9, 5, 23, 56, 4, 649000000)) + ); + assert_eq!( + NaiveDateTime::parse_from_str("1497854303.087654", "%s%.6f"), + Ok(ymdhmsn(2017, 6, 19, 6, 38, 23, 87654000)) + ); + assert_eq!( + NaiveDateTime::parse_from_str("1437742189.918273645", "%s%.9f"), + Ok(ymdhmsn(2015, 7, 24, 12, 49, 49, 918273645)) + ); +} + +#[test] +fn test_datetime_parse_from_str_with_spaces() { + let parse_from_str = NaiveDateTime::parse_from_str; + let dt = NaiveDate::from_ymd_opt(2013, 8, 9).unwrap().and_hms_opt(23, 54, 35).unwrap(); + // with varying spaces - should succeed + assert_eq!(parse_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt)); + assert_eq!(parse_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S "), Ok(dt)); + assert_eq!(parse_from_str(" Aug 09 2013 23:54:35 ", " %b %d %Y %H:%M:%S "), Ok(dt)); + assert_eq!(parse_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt)); + assert_eq!(parse_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt)); + assert_eq!(parse_from_str("\n\tAug 09 2013 23:54:35 ", "\n\t%b %d %Y %H:%M:%S "), Ok(dt)); + assert_eq!(parse_from_str("\tAug 09 2013 23:54:35\t", "\t%b %d %Y %H:%M:%S\t"), Ok(dt)); + assert_eq!(parse_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S"), Ok(dt)); + assert_eq!(parse_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S"), Ok(dt)); + assert_eq!(parse_from_str("Aug 09 2013\t23:54:35", "%b %d %Y\t%H:%M:%S"), Ok(dt)); + assert_eq!(parse_from_str("Aug 09 2013\t\t23:54:35", "%b %d %Y\t\t%H:%M:%S"), Ok(dt)); + assert_eq!(parse_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S\n"), Ok(dt)); + assert_eq!(parse_from_str("Aug 09 2013 23:54:35", "%b %d %Y\t%H:%M:%S"), Ok(dt)); + assert_eq!(parse_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S "), Ok(dt)); + assert_eq!(parse_from_str("Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt)); + assert_eq!(parse_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S\n"), Ok(dt)); + // with varying spaces - should fail + // leading space in data + assert!(parse_from_str(" Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S").is_err()); + // trailing space in data + assert!(parse_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S").is_err()); + // trailing tab in data + assert!(parse_from_str("Aug 09 2013 23:54:35\t", "%b %d %Y %H:%M:%S").is_err()); + // mismatched newlines + assert!(parse_from_str("\nAug 09 2013 23:54:35", "%b %d %Y %H:%M:%S\n").is_err()); + // trailing literal in data + assert!(parse_from_str("Aug 09 2013 23:54:35 !!!", "%b %d %Y %H:%M:%S ").is_err()); +} + +#[test] +fn test_datetime_add_sub_invariant() { + // issue #37 + let base = NaiveDate::from_ymd_opt(2000, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); + let t = -946684799990000; + let time = base + TimeDelta::microseconds(t); + assert_eq!(t, time.signed_duration_since(base).num_microseconds().unwrap()); +} + +#[test] +fn test_and_local_timezone() { + let ndt = NaiveDate::from_ymd_opt(2022, 6, 15).unwrap().and_hms_opt(18, 59, 36).unwrap(); + let dt_utc = ndt.and_utc(); + assert_eq!(dt_utc.naive_local(), ndt); + assert_eq!(dt_utc.timezone(), Utc); + + let offset_tz = FixedOffset::west_opt(4 * 3600).unwrap(); + let dt_offset = ndt.and_local_timezone(offset_tz).unwrap(); + assert_eq!(dt_offset.naive_local(), ndt); + assert_eq!(dt_offset.timezone(), offset_tz); +} + +#[test] +fn test_and_utc() { + let ndt = NaiveDate::from_ymd_opt(2023, 1, 30).unwrap().and_hms_opt(19, 32, 33).unwrap(); + let dt_utc = ndt.and_utc(); + assert_eq!(dt_utc.naive_local(), ndt); + assert_eq!(dt_utc.timezone(), Utc); +} + +#[test] +fn test_checked_add_offset() { + let ymdhmsm = |y, m, d, h, mn, s, mi| { + NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_milli_opt(h, mn, s, mi) + }; + + let positive_offset = FixedOffset::east_opt(2 * 60 * 60).unwrap(); + // regular date + let dt = ymdhmsm(2023, 5, 5, 20, 10, 0, 0).unwrap(); + assert_eq!(dt.checked_add_offset(positive_offset), ymdhmsm(2023, 5, 5, 22, 10, 0, 0)); + // leap second is preserved + let dt = ymdhmsm(2023, 6, 30, 23, 59, 59, 1_000).unwrap(); + assert_eq!(dt.checked_add_offset(positive_offset), ymdhmsm(2023, 7, 1, 1, 59, 59, 1_000)); + // out of range + assert!(NaiveDateTime::MAX.checked_add_offset(positive_offset).is_none()); + + let negative_offset = FixedOffset::west_opt(2 * 60 * 60).unwrap(); + // regular date + let dt = ymdhmsm(2023, 5, 5, 20, 10, 0, 0).unwrap(); + assert_eq!(dt.checked_add_offset(negative_offset), ymdhmsm(2023, 5, 5, 18, 10, 0, 0)); + // leap second is preserved + let dt = ymdhmsm(2023, 6, 30, 23, 59, 59, 1_000).unwrap(); + assert_eq!(dt.checked_add_offset(negative_offset), ymdhmsm(2023, 6, 30, 21, 59, 59, 1_000)); + // out of range + assert!(NaiveDateTime::MIN.checked_add_offset(negative_offset).is_none()); +} + +#[test] +fn test_checked_sub_offset() { + let ymdhmsm = |y, m, d, h, mn, s, mi| { + NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_milli_opt(h, mn, s, mi) + }; + + let positive_offset = FixedOffset::east_opt(2 * 60 * 60).unwrap(); + // regular date + let dt = ymdhmsm(2023, 5, 5, 20, 10, 0, 0).unwrap(); + assert_eq!(dt.checked_sub_offset(positive_offset), ymdhmsm(2023, 5, 5, 18, 10, 0, 0)); + // leap second is preserved + let dt = ymdhmsm(2023, 6, 30, 23, 59, 59, 1_000).unwrap(); + assert_eq!(dt.checked_sub_offset(positive_offset), ymdhmsm(2023, 6, 30, 21, 59, 59, 1_000)); + // out of range + assert!(NaiveDateTime::MIN.checked_sub_offset(positive_offset).is_none()); + + let negative_offset = FixedOffset::west_opt(2 * 60 * 60).unwrap(); + // regular date + let dt = ymdhmsm(2023, 5, 5, 20, 10, 0, 0).unwrap(); + assert_eq!(dt.checked_sub_offset(negative_offset), ymdhmsm(2023, 5, 5, 22, 10, 0, 0)); + // leap second is preserved + let dt = ymdhmsm(2023, 6, 30, 23, 59, 59, 1_000).unwrap(); + assert_eq!(dt.checked_sub_offset(negative_offset), ymdhmsm(2023, 7, 1, 1, 59, 59, 1_000)); + // out of range + assert!(NaiveDateTime::MAX.checked_sub_offset(negative_offset).is_none()); + + assert_eq!(dt.checked_add_offset(positive_offset), Some(dt + positive_offset)); + assert_eq!(dt.checked_sub_offset(positive_offset), Some(dt - positive_offset)); +} + +#[test] +fn test_overflowing_add_offset() { + let ymdhmsm = |y, m, d, h, mn, s, mi| { + NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_milli_opt(h, mn, s, mi).unwrap() + }; + let positive_offset = FixedOffset::east_opt(2 * 60 * 60).unwrap(); + // regular date + let dt = ymdhmsm(2023, 5, 5, 20, 10, 0, 0); + assert_eq!(dt.overflowing_add_offset(positive_offset), ymdhmsm(2023, 5, 5, 22, 10, 0, 0)); + // leap second is preserved + let dt = ymdhmsm(2023, 6, 30, 23, 59, 59, 1_000); + assert_eq!(dt.overflowing_add_offset(positive_offset), ymdhmsm(2023, 7, 1, 1, 59, 59, 1_000)); + // out of range + assert!(NaiveDateTime::MAX.overflowing_add_offset(positive_offset) > NaiveDateTime::MAX); + + let negative_offset = FixedOffset::west_opt(2 * 60 * 60).unwrap(); + // regular date + let dt = ymdhmsm(2023, 5, 5, 20, 10, 0, 0); + assert_eq!(dt.overflowing_add_offset(negative_offset), ymdhmsm(2023, 5, 5, 18, 10, 0, 0)); + // leap second is preserved + let dt = ymdhmsm(2023, 6, 30, 23, 59, 59, 1_000); + assert_eq!(dt.overflowing_add_offset(negative_offset), ymdhmsm(2023, 6, 30, 21, 59, 59, 1_000)); + // out of range + assert!(NaiveDateTime::MIN.overflowing_add_offset(negative_offset) < NaiveDateTime::MIN); +} + +#[test] +fn test_and_timezone_min_max_dates() { + for offset_hour in -23..=23 { + dbg!(offset_hour); + let offset = FixedOffset::east_opt(offset_hour * 60 * 60).unwrap(); + + let local_max = NaiveDateTime::MAX.and_local_timezone(offset); + if offset_hour >= 0 { + assert_eq!(local_max.unwrap().naive_local(), NaiveDateTime::MAX); + } else { + assert_eq!(local_max, MappedLocalTime::None); + } + let local_min = NaiveDateTime::MIN.and_local_timezone(offset); + if offset_hour <= 0 { + assert_eq!(local_min.unwrap().naive_local(), NaiveDateTime::MIN); + } else { + assert_eq!(local_min, MappedLocalTime::None); + } + } +} + +#[test] +#[cfg(feature = "rkyv-validation")] +fn test_rkyv_validation() { + let dt_min = NaiveDateTime::MIN; + let bytes = rkyv::to_bytes::<_, 12>(&dt_min).unwrap(); + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), dt_min); + + let dt_max = NaiveDateTime::MAX; + let bytes = rkyv::to_bytes::<_, 12>(&dt_max).unwrap(); + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), dt_max); +} diff --git a/third_party/rust/chrono/src/naive/internals.rs b/third_party/rust/chrono/src/naive/internals.rs index 346063c3754..765a930be8a 100644 --- a/third_party/rust/chrono/src/naive/internals.rs +++ b/third_party/rust/chrono/src/naive/internals.rs @@ -1,59 +1,52 @@ -// This is a part of Chrono. -// See README.md and LICENSE.txt for details. +//! Internal helper types for working with dates. -//! The internal implementation of the calendar and ordinal date. -//! -//! The current implementation is optimized for determining year, month, day and day of week. -//! 4-bit `YearFlags` map to one of 14 possible classes of year in the Gregorian calendar, -//! which are included in every packed `NaiveDate` instance. -//! The conversion between the packed calendar date (`Mdf`) and the ordinal date (`Of`) is -//! based on the moderately-sized lookup table (~1.5KB) -//! and the packed representation is chosen for the efficient lookup. -//! Every internal data structure does not validate its input, -//! but the conversion keeps the valid value valid and the invalid value invalid -//! so that the user-facing `NaiveDate` can validate the input as late as possible. - -#![allow(dead_code)] // some internal methods have been left for consistency #![cfg_attr(feature = "__internal_bench", allow(missing_docs))] -use core::{fmt, i32}; -use div::{div_rem, mod_floor}; -use num_traits::FromPrimitive; -use Weekday; +use core::fmt; -/// The internal date representation. This also includes the packed `Mdf` value. -pub type DateImpl = i32; - -pub const MAX_YEAR: DateImpl = i32::MAX >> 13; -pub const MIN_YEAR: DateImpl = i32::MIN >> 13; - -/// The year flags (aka the dominical letter). +/// Year flags (aka the dominical letter). +/// +/// `YearFlags` are used as the last four bits of `NaiveDate`, `Mdf` and `IsoWeek`. /// /// There are 14 possible classes of year in the Gregorian calendar: /// common and leap years starting with Monday through Sunday. -/// The `YearFlags` stores this information into 4 bits `abbb`, -/// where `a` is `1` for the common year (simplifies the `Of` validation) -/// and `bbb` is a non-zero `Weekday` (mapping `Mon` to 7) of the last day in the past year -/// (simplifies the day of week calculation from the 1-based ordinal). -#[derive(PartialEq, Eq, Copy, Clone)] -pub struct YearFlags(pub u8); +/// +/// The `YearFlags` stores this information into 4 bits `LWWW`. `L` is the leap year flag, with `1` +/// for the common year (this simplifies validating an ordinal in `NaiveDate`). `WWW` is a non-zero +/// `Weekday` of the last day in the preceding year. +#[allow(unreachable_pub)] // public as an alias for benchmarks only +#[derive(PartialEq, Eq, Copy, Clone, Hash)] +pub struct YearFlags(pub(super) u8); -pub const A: YearFlags = YearFlags(0o15); -pub const AG: YearFlags = YearFlags(0o05); -pub const B: YearFlags = YearFlags(0o14); -pub const BA: YearFlags = YearFlags(0o04); -pub const C: YearFlags = YearFlags(0o13); -pub const CB: YearFlags = YearFlags(0o03); -pub const D: YearFlags = YearFlags(0o12); -pub const DC: YearFlags = YearFlags(0o02); -pub const E: YearFlags = YearFlags(0o11); -pub const ED: YearFlags = YearFlags(0o01); -pub const F: YearFlags = YearFlags(0o17); -pub const FE: YearFlags = YearFlags(0o07); -pub const G: YearFlags = YearFlags(0o16); -pub const GF: YearFlags = YearFlags(0o06); +// Weekday of the last day in the preceding year. +// Allows for quick day of week calculation from the 1-based ordinal. +const YEAR_STARTS_AFTER_MONDAY: u8 = 7; // non-zero to allow use with `NonZero*`. +const YEAR_STARTS_AFTER_THUESDAY: u8 = 1; +const YEAR_STARTS_AFTER_WEDNESDAY: u8 = 2; +const YEAR_STARTS_AFTER_THURSDAY: u8 = 3; +const YEAR_STARTS_AFTER_FRIDAY: u8 = 4; +const YEAR_STARTS_AFTER_SATURDAY: u8 = 5; +const YEAR_STARTS_AFTER_SUNDAY: u8 = 6; -static YEAR_TO_FLAGS: [YearFlags; 400] = [ +const COMMON_YEAR: u8 = 1 << 3; +const LEAP_YEAR: u8 = 0 << 3; + +pub(super) const A: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_SATURDAY); +pub(super) const AG: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_SATURDAY); +pub(super) const B: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_FRIDAY); +pub(super) const BA: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_FRIDAY); +pub(super) const C: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_THURSDAY); +pub(super) const CB: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_THURSDAY); +pub(super) const D: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_WEDNESDAY); +pub(super) const DC: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_WEDNESDAY); +pub(super) const E: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_THUESDAY); +pub(super) const ED: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_THUESDAY); +pub(super) const F: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_MONDAY); +pub(super) const FE: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_MONDAY); +pub(super) const G: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_SUNDAY); +pub(super) const GF: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_SUNDAY); + +const YEAR_TO_FLAGS: &[YearFlags; 400] = &[ BA, G, F, E, DC, B, A, G, FE, D, C, B, AG, F, E, D, CB, A, G, F, ED, C, B, A, GF, E, D, C, BA, G, F, E, DC, B, A, G, FE, D, C, B, AG, F, E, D, CB, A, G, F, ED, C, B, A, GF, E, D, C, BA, G, F, E, DC, B, A, G, FE, D, C, B, AG, F, E, D, CB, A, G, F, ED, C, B, A, GF, E, D, C, BA, G, F, @@ -72,66 +65,31 @@ static YEAR_TO_FLAGS: [YearFlags; 400] = [ D, CB, A, G, F, ED, C, B, A, GF, E, D, C, // 400 ]; -static YEAR_DELTAS: [u8; 401] = [ - 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, - 8, 9, 9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 11, 12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, - 15, 15, 15, 15, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19, 20, 20, 20, 20, - 21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 24, 24, 24, 24, 25, 25, 25, // 100 - 25, 25, 25, 25, 25, 26, 26, 26, 26, 27, 27, 27, 27, 28, 28, 28, 28, 29, 29, 29, 29, 30, 30, 30, - 30, 31, 31, 31, 31, 32, 32, 32, 32, 33, 33, 33, 33, 34, 34, 34, 34, 35, 35, 35, 35, 36, 36, 36, - 36, 37, 37, 37, 37, 38, 38, 38, 38, 39, 39, 39, 39, 40, 40, 40, 40, 41, 41, 41, 41, 42, 42, 42, - 42, 43, 43, 43, 43, 44, 44, 44, 44, 45, 45, 45, 45, 46, 46, 46, 46, 47, 47, 47, 47, 48, 48, 48, - 48, 49, 49, 49, // 200 - 49, 49, 49, 49, 49, 50, 50, 50, 50, 51, 51, 51, 51, 52, 52, 52, 52, 53, 53, 53, 53, 54, 54, 54, - 54, 55, 55, 55, 55, 56, 56, 56, 56, 57, 57, 57, 57, 58, 58, 58, 58, 59, 59, 59, 59, 60, 60, 60, - 60, 61, 61, 61, 61, 62, 62, 62, 62, 63, 63, 63, 63, 64, 64, 64, 64, 65, 65, 65, 65, 66, 66, 66, - 66, 67, 67, 67, 67, 68, 68, 68, 68, 69, 69, 69, 69, 70, 70, 70, 70, 71, 71, 71, 71, 72, 72, 72, - 72, 73, 73, 73, // 300 - 73, 73, 73, 73, 73, 74, 74, 74, 74, 75, 75, 75, 75, 76, 76, 76, 76, 77, 77, 77, 77, 78, 78, 78, - 78, 79, 79, 79, 79, 80, 80, 80, 80, 81, 81, 81, 81, 82, 82, 82, 82, 83, 83, 83, 83, 84, 84, 84, - 84, 85, 85, 85, 85, 86, 86, 86, 86, 87, 87, 87, 87, 88, 88, 88, 88, 89, 89, 89, 89, 90, 90, 90, - 90, 91, 91, 91, 91, 92, 92, 92, 92, 93, 93, 93, 93, 94, 94, 94, 94, 95, 95, 95, 95, 96, 96, 96, - 96, 97, 97, 97, 97, // 400+1 -]; - -pub fn cycle_to_yo(cycle: u32) -> (u32, u32) { - let (mut year_mod_400, mut ordinal0) = div_rem(cycle, 365); - let delta = u32::from(YEAR_DELTAS[year_mod_400 as usize]); - if ordinal0 < delta { - year_mod_400 -= 1; - ordinal0 += 365 - u32::from(YEAR_DELTAS[year_mod_400 as usize]); - } else { - ordinal0 -= delta; - } - (year_mod_400, ordinal0 + 1) -} - -pub fn yo_to_cycle(year_mod_400: u32, ordinal: u32) -> u32 { - year_mod_400 * 365 + u32::from(YEAR_DELTAS[year_mod_400 as usize]) + ordinal - 1 -} - impl YearFlags { + #[allow(unreachable_pub)] // public as an alias for benchmarks only + #[doc(hidden)] // for benchmarks only #[inline] - pub fn from_year(year: i32) -> YearFlags { - let year = mod_floor(year, 400); + #[must_use] + pub const fn from_year(year: i32) -> YearFlags { + let year = year.rem_euclid(400); YearFlags::from_year_mod_400(year) } #[inline] - pub fn from_year_mod_400(year: i32) -> YearFlags { + pub(super) const fn from_year_mod_400(year: i32) -> YearFlags { YEAR_TO_FLAGS[year as usize] } #[inline] - pub fn ndays(&self) -> u32 { + pub(super) const fn ndays(&self) -> u32 { let YearFlags(flags) = *self; - 366 - u32::from(flags >> 3) + 366 - (flags >> 3) as u32 } #[inline] - pub fn isoweek_delta(&self) -> u32 { + pub(super) const fn isoweek_delta(&self) -> u32 { let YearFlags(flags) = *self; - let mut delta = u32::from(flags) & 0b0111; + let mut delta = (flags & 0b0111) as u32; if delta < 3 { delta += 7; } @@ -139,7 +97,7 @@ impl YearFlags { } #[inline] - pub fn nisoweeks(&self) -> u32 { + pub(super) const fn nisoweeks(&self) -> u32 { let YearFlags(flags) = *self; 52 + ((0b0000_0100_0000_0110 >> flags as usize) & 1) } @@ -170,13 +128,15 @@ impl fmt::Debug for YearFlags { } } -pub const MIN_OL: u32 = 1 << 1; -pub const MAX_OL: u32 = 366 << 1; // larger than the non-leap last day `(365 << 1) | 1` -pub const MIN_MDL: u32 = (1 << 6) | (1 << 1); -pub const MAX_MDL: u32 = (12 << 6) | (31 << 1) | 1; +// OL: (ordinal << 1) | leap year flag +const MAX_OL: u32 = 366 << 1; // `(366 << 1) | 1` would be day 366 in a non-leap year +const MAX_MDL: u32 = (12 << 6) | (31 << 1) | 1; -const XX: i8 = -128; -static MDL_TO_OL: [i8; MAX_MDL as usize + 1] = [ +// The next table are adjustment values to convert a date encoded as month-day-leapyear to +// ordinal-leapyear. OL = MDL - adjustment. +// Dates that do not exist are encoded as `XX`. +const XX: i8 = 0; +const MDL_TO_OL: &[i8; MAX_MDL as usize + 1] = &[ XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, // 0 @@ -219,7 +179,7 @@ static MDL_TO_OL: [i8; MAX_MDL as usize + 1] = [ 100, // 12 ]; -static OL_TO_MDL: [u8; MAX_OL as usize + 1] = [ +const OL_TO_MDL: &[u8; MAX_OL as usize + 1] = &[ 0, 0, // 0 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, @@ -260,209 +220,147 @@ static OL_TO_MDL: [u8; MAX_OL as usize + 1] = [ 98, // 12 ]; -/// Ordinal (day of year) and year flags: `(ordinal << 4) | flags`. -/// -/// The whole bits except for the least 3 bits are referred as `Ol` (ordinal and leap flag), -/// which is an index to the `OL_TO_MDL` lookup table. -#[derive(PartialEq, PartialOrd, Copy, Clone)] -pub struct Of(pub u32); - -impl Of { - #[inline] - fn clamp_ordinal(ordinal: u32) -> u32 { - if ordinal > 366 { - 0 - } else { - ordinal - } - } - - #[inline] - pub fn new(ordinal: u32, YearFlags(flags): YearFlags) -> Of { - let ordinal = Of::clamp_ordinal(ordinal); - Of((ordinal << 4) | u32::from(flags)) - } - - #[inline] - pub fn from_mdf(Mdf(mdf): Mdf) -> Of { - let mdl = mdf >> 3; - match MDL_TO_OL.get(mdl as usize) { - Some(&v) => Of(mdf.wrapping_sub((i32::from(v) as u32 & 0x3ff) << 3)), - None => Of(0), - } - } - - #[inline] - pub fn valid(&self) -> bool { - let Of(of) = *self; - let ol = of >> 3; - MIN_OL <= ol && ol <= MAX_OL - } - - #[inline] - pub fn ordinal(&self) -> u32 { - let Of(of) = *self; - of >> 4 - } - - #[inline] - pub fn with_ordinal(&self, ordinal: u32) -> Of { - let ordinal = Of::clamp_ordinal(ordinal); - let Of(of) = *self; - Of((of & 0b1111) | (ordinal << 4)) - } - - #[inline] - pub fn flags(&self) -> YearFlags { - let Of(of) = *self; - YearFlags((of & 0b1111) as u8) - } - - #[inline] - pub fn with_flags(&self, YearFlags(flags): YearFlags) -> Of { - let Of(of) = *self; - Of((of & !0b1111) | u32::from(flags)) - } - - #[inline] - pub fn weekday(&self) -> Weekday { - let Of(of) = *self; - Weekday::from_u32(((of >> 4) + (of & 0b111)) % 7).unwrap() - } - - #[inline] - pub fn isoweekdate_raw(&self) -> (u32, Weekday) { - // week ordinal = ordinal + delta - let Of(of) = *self; - let weekord = (of >> 4).wrapping_add(self.flags().isoweek_delta()); - (weekord / 7, Weekday::from_u32(weekord % 7).unwrap()) - } - - #[inline] - pub fn to_mdf(&self) -> Mdf { - Mdf::from_of(*self) - } - - #[inline] - pub fn succ(&self) -> Of { - let Of(of) = *self; - Of(of + (1 << 4)) - } - - #[inline] - pub fn pred(&self) -> Of { - let Of(of) = *self; - Of(of - (1 << 4)) - } -} - -impl fmt::Debug for Of { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let Of(of) = *self; - write!( - f, - "Of(({} << 4) | {:#04o} /*{:?}*/)", - of >> 4, - of & 0b1111, - YearFlags((of & 0b1111) as u8) - ) - } -} - /// Month, day of month and year flags: `(month << 9) | (day << 4) | flags` +/// `M_MMMD_DDDD_LFFF` /// -/// The whole bits except for the least 3 bits are referred as `Mdl` -/// (month, day of month and leap flag), -/// which is an index to the `MDL_TO_OL` lookup table. +/// The whole bits except for the least 3 bits are referred as `Mdl` (month, day of month, and leap +/// year flag), which is an index to the `MDL_TO_OL` lookup table. +/// +/// The conversion between the packed calendar date (`Mdf`) and the ordinal date (`NaiveDate`) is +/// based on the moderately-sized lookup table (~1.5KB) and the packed representation is chosen for +/// efficient lookup. +/// +/// The methods of `Mdf` validate their inputs as late as possible. Dates that can't exist, like +/// February 30, can still be represented. This allows the validation to be combined with the final +/// table lookup, which is good for performance. #[derive(PartialEq, PartialOrd, Copy, Clone)] -pub struct Mdf(pub u32); +pub(super) struct Mdf(u32); impl Mdf { + /// Makes a new `Mdf` value from month, day and `YearFlags`. + /// + /// This method doesn't fully validate the range of the `month` and `day` parameters, only as + /// much as what can't be deferred until later. The year `flags` are trusted to be correct. + /// + /// # Errors + /// + /// Returns `None` if `month > 12` or `day > 31`. #[inline] - fn clamp_month(month: u32) -> u32 { - if month > 12 { - 0 - } else { - month + pub(super) const fn new(month: u32, day: u32, YearFlags(flags): YearFlags) -> Option { + match month <= 12 && day <= 31 { + true => Some(Mdf((month << 9) | (day << 4) | flags as u32)), + false => None, } } + /// Makes a new `Mdf` value from an `i32` with an ordinal and a leap year flag, and year + /// `flags`. + /// + /// The `ol` is trusted to be valid, and the `flags` are trusted to match it. #[inline] - fn clamp_day(day: u32) -> u32 { - if day > 31 { - 0 - } else { - day - } + pub(super) const fn from_ol(ol: i32, YearFlags(flags): YearFlags) -> Mdf { + debug_assert!(ol > 1 && ol <= MAX_OL as i32); + // Array is indexed from `[2..=MAX_OL]`, with a `0` index having a meaningless value. + Mdf(((ol as u32 + OL_TO_MDL[ol as usize] as u32) << 3) | flags as u32) } + /// Returns the month of this `Mdf`. #[inline] - pub fn new(month: u32, day: u32, YearFlags(flags): YearFlags) -> Mdf { - let month = Mdf::clamp_month(month); - let day = Mdf::clamp_day(day); - Mdf((month << 9) | (day << 4) | u32::from(flags)) - } - - #[inline] - pub fn from_of(Of(of): Of) -> Mdf { - let ol = of >> 3; - match OL_TO_MDL.get(ol as usize) { - Some(&v) => Mdf(of + (u32::from(v) << 3)), - None => Mdf(0), - } - } - - #[inline] - pub fn valid(&self) -> bool { - let Mdf(mdf) = *self; - let mdl = mdf >> 3; - match MDL_TO_OL.get(mdl as usize) { - Some(&v) => v >= 0, - None => false, - } - } - - #[inline] - pub fn month(&self) -> u32 { + pub(super) const fn month(&self) -> u32 { let Mdf(mdf) = *self; mdf >> 9 } + /// Replaces the month of this `Mdf`, keeping the day and flags. + /// + /// # Errors + /// + /// Returns `None` if `month > 12`. #[inline] - pub fn with_month(&self, month: u32) -> Mdf { - let month = Mdf::clamp_month(month); + pub(super) const fn with_month(&self, month: u32) -> Option { + if month > 12 { + return None; + } + let Mdf(mdf) = *self; - Mdf((mdf & 0b1_1111_1111) | (month << 9)) + Some(Mdf((mdf & 0b1_1111_1111) | (month << 9))) } + /// Returns the day of this `Mdf`. #[inline] - pub fn day(&self) -> u32 { + pub(super) const fn day(&self) -> u32 { let Mdf(mdf) = *self; (mdf >> 4) & 0b1_1111 } + /// Replaces the day of this `Mdf`, keeping the month and flags. + /// + /// # Errors + /// + /// Returns `None` if `day > 31`. #[inline] - pub fn with_day(&self, day: u32) -> Mdf { - let day = Mdf::clamp_day(day); + pub(super) const fn with_day(&self, day: u32) -> Option { + if day > 31 { + return None; + } + let Mdf(mdf) = *self; - Mdf((mdf & !0b1_1111_0000) | (day << 4)) + Some(Mdf((mdf & !0b1_1111_0000) | (day << 4))) } + /// Replaces the flags of this `Mdf`, keeping the month and day. #[inline] - pub fn flags(&self) -> YearFlags { + pub(super) const fn with_flags(&self, YearFlags(flags): YearFlags) -> Mdf { let Mdf(mdf) = *self; - YearFlags((mdf & 0b1111) as u8) + Mdf((mdf & !0b1111) | flags as u32) } + /// Returns the ordinal that corresponds to this `Mdf`. + /// + /// This does a table lookup to calculate the corresponding ordinal. It will return an error if + /// the `Mdl` turns out not to be a valid date. + /// + /// # Errors + /// + /// Returns `None` if `month == 0` or `day == 0`, or if a the given day does not exist in the + /// given month. #[inline] - pub fn with_flags(&self, YearFlags(flags): YearFlags) -> Mdf { - let Mdf(mdf) = *self; - Mdf((mdf & !0b1111) | u32::from(flags)) + pub(super) const fn ordinal(&self) -> Option { + let mdl = self.0 >> 3; + match MDL_TO_OL[mdl as usize] { + XX => None, + v => Some((mdl - v as u8 as u32) >> 1), + } } + /// Returns the year flags of this `Mdf`. #[inline] - pub fn to_of(&self) -> Of { - Of::from_mdf(*self) + pub(super) const fn year_flags(&self) -> YearFlags { + YearFlags((self.0 & 0b1111) as u8) + } + + /// Returns the ordinal that corresponds to this `Mdf`, encoded as a value including year flags. + /// + /// This does a table lookup to calculate the corresponding ordinal. It will return an error if + /// the `Mdl` turns out not to be a valid date. + /// + /// # Errors + /// + /// Returns `None` if `month == 0` or `day == 0`, or if a the given day does not exist in the + /// given month. + #[inline] + pub(super) const fn ordinal_and_flags(&self) -> Option { + let mdl = self.0 >> 3; + match MDL_TO_OL[mdl as usize] { + XX => None, + v => Some(self.0 as i32 - ((v as i32) << 3)), + } + } + + #[cfg(test)] + fn valid(&self) -> bool { + let mdl = self.0 >> 3; + MDL_TO_OL[mdl as usize] > 0 } } @@ -482,14 +380,8 @@ impl fmt::Debug for Mdf { #[cfg(test)] mod tests { - #[cfg(test)] - extern crate num_iter; - - use self::num_iter::range_inclusive; - use super::{Mdf, Of}; - use super::{YearFlags, A, AG, B, BA, C, CB, D, DC, E, ED, F, FE, G, GF}; - use std::u32; - use Weekday; + use super::Mdf; + use super::{A, AG, B, BA, C, CB, D, DC, E, ED, F, FE, G, GF, YearFlags}; const NONLEAP_FLAGS: [YearFlags; 7] = [A, B, C, D, E, F, G]; const LEAP_FLAGS: [YearFlags; 7] = [AG, BA, CB, DC, ED, FE, GF]; @@ -530,43 +422,17 @@ mod tests { assert_eq!(GF.nisoweeks(), 52); } - #[test] - fn test_of() { - fn check(expected: bool, flags: YearFlags, ordinal1: u32, ordinal2: u32) { - for ordinal in range_inclusive(ordinal1, ordinal2) { - let of = Of::new(ordinal, flags); - assert!( - of.valid() == expected, - "ordinal {} = {:?} should be {} for dominical year {:?}", - ordinal, - of, - if expected { "valid" } else { "invalid" }, - flags - ); - } - } - - for &flags in NONLEAP_FLAGS.iter() { - check(false, flags, 0, 0); - check(true, flags, 1, 365); - check(false, flags, 366, 1024); - check(false, flags, u32::MAX, u32::MAX); - } - - for &flags in LEAP_FLAGS.iter() { - check(false, flags, 0, 0); - check(true, flags, 1, 366); - check(false, flags, 367, 1024); - check(false, flags, u32::MAX, u32::MAX); - } - } - #[test] fn test_mdf_valid() { fn check(expected: bool, flags: YearFlags, month1: u32, day1: u32, month2: u32, day2: u32) { - for month in range_inclusive(month1, month2) { - for day in range_inclusive(day1, day2) { - let mdf = Mdf::new(month, day, flags); + for month in month1..=month2 { + for day in day1..=day2 { + let mdf = match Mdf::new(month, day, flags) { + Some(mdf) => mdf, + None if !expected => continue, + None => panic!("Mdf::new({}, {}, {:?}) returned None", month, day, flags), + }; + assert!( mdf.valid() == expected, "month {} day {} = {:?} should be {} for dominical year {:?}", @@ -647,76 +513,16 @@ mod tests { } } - #[test] - fn test_of_fields() { - for &flags in FLAGS.iter() { - for ordinal in range_inclusive(1u32, 366) { - let of = Of::new(ordinal, flags); - if of.valid() { - assert_eq!(of.ordinal(), ordinal); - } - } - } - } - - #[test] - fn test_of_with_fields() { - fn check(flags: YearFlags, ordinal: u32) { - let of = Of::new(ordinal, flags); - - for ordinal in range_inclusive(0u32, 1024) { - let of = of.with_ordinal(ordinal); - assert_eq!(of.valid(), Of::new(ordinal, flags).valid()); - if of.valid() { - assert_eq!(of.ordinal(), ordinal); - } - } - } - - for &flags in NONLEAP_FLAGS.iter() { - check(flags, 1); - check(flags, 365); - } - for &flags in LEAP_FLAGS.iter() { - check(flags, 1); - check(flags, 366); - } - } - - #[test] - fn test_of_weekday() { - assert_eq!(Of::new(1, A).weekday(), Weekday::Sun); - assert_eq!(Of::new(1, B).weekday(), Weekday::Sat); - assert_eq!(Of::new(1, C).weekday(), Weekday::Fri); - assert_eq!(Of::new(1, D).weekday(), Weekday::Thu); - assert_eq!(Of::new(1, E).weekday(), Weekday::Wed); - assert_eq!(Of::new(1, F).weekday(), Weekday::Tue); - assert_eq!(Of::new(1, G).weekday(), Weekday::Mon); - assert_eq!(Of::new(1, AG).weekday(), Weekday::Sun); - assert_eq!(Of::new(1, BA).weekday(), Weekday::Sat); - assert_eq!(Of::new(1, CB).weekday(), Weekday::Fri); - assert_eq!(Of::new(1, DC).weekday(), Weekday::Thu); - assert_eq!(Of::new(1, ED).weekday(), Weekday::Wed); - assert_eq!(Of::new(1, FE).weekday(), Weekday::Tue); - assert_eq!(Of::new(1, GF).weekday(), Weekday::Mon); - - for &flags in FLAGS.iter() { - let mut prev = Of::new(1, flags).weekday(); - for ordinal in range_inclusive(2u32, flags.ndays()) { - let of = Of::new(ordinal, flags); - let expected = prev.succ(); - assert_eq!(of.weekday(), expected); - prev = expected; - } - } - } - #[test] fn test_mdf_fields() { for &flags in FLAGS.iter() { - for month in range_inclusive(1u32, 12) { - for day in range_inclusive(1u32, 31) { - let mdf = Mdf::new(month, day, flags); + for month in 1u32..=12 { + for day in 1u32..31 { + let mdf = match Mdf::new(month, day, flags) { + Some(mdf) => mdf, + None => continue, + }; + if mdf.valid() { assert_eq!(mdf.month(), month); assert_eq!(mdf.day(), day); @@ -729,20 +535,28 @@ mod tests { #[test] fn test_mdf_with_fields() { fn check(flags: YearFlags, month: u32, day: u32) { - let mdf = Mdf::new(month, day, flags); + let mdf = Mdf::new(month, day, flags).unwrap(); + + for month in 0u32..=16 { + let mdf = match mdf.with_month(month) { + Some(mdf) => mdf, + None if month > 12 => continue, + None => panic!("failed to create Mdf with month {}", month), + }; - for month in range_inclusive(0u32, 16) { - let mdf = mdf.with_month(month); - assert_eq!(mdf.valid(), Mdf::new(month, day, flags).valid()); if mdf.valid() { assert_eq!(mdf.month(), month); assert_eq!(mdf.day(), day); } } - for day in range_inclusive(0u32, 1024) { - let mdf = mdf.with_day(day); - assert_eq!(mdf.valid(), Mdf::new(month, day, flags).valid()); + for day in 0u32..=1024 { + let mdf = match mdf.with_day(day) { + Some(mdf) => mdf, + None if day > 31 => continue, + None => panic!("failed to create Mdf with month {}", month), + }; + if mdf.valid() { assert_eq!(mdf.month(), month); assert_eq!(mdf.day(), day); @@ -769,47 +583,9 @@ mod tests { } #[test] - fn test_of_isoweekdate_raw() { - for &flags in FLAGS.iter() { - // January 4 should be in the first week - let (week, _) = Of::new(4 /* January 4 */, flags).isoweekdate_raw(); - assert_eq!(week, 1); - } - } - - #[test] - fn test_of_to_mdf() { - for i in range_inclusive(0u32, 8192) { - let of = Of(i); - assert_eq!(of.valid(), of.to_mdf().valid()); - } - } - - #[test] - fn test_mdf_to_of() { - for i in range_inclusive(0u32, 8192) { - let mdf = Mdf(i); - assert_eq!(mdf.valid(), mdf.to_of().valid()); - } - } - - #[test] - fn test_of_to_mdf_to_of() { - for i in range_inclusive(0u32, 8192) { - let of = Of(i); - if of.valid() { - assert_eq!(of, of.to_mdf().to_of()); - } - } - } - - #[test] - fn test_mdf_to_of_to_mdf() { - for i in range_inclusive(0u32, 8192) { - let mdf = Mdf(i); - if mdf.valid() { - assert_eq!(mdf, mdf.to_of().to_mdf()); - } - } + fn test_mdf_new_range() { + let flags = YearFlags::from_year(2023); + assert!(Mdf::new(13, 1, flags).is_none()); + assert!(Mdf::new(1, 32, flags).is_none()); } } diff --git a/third_party/rust/chrono/src/naive/isoweek.rs b/third_party/rust/chrono/src/naive/isoweek.rs index ece10f250b1..c572011aed0 100644 --- a/third_party/rust/chrono/src/naive/isoweek.rs +++ b/third_party/rust/chrono/src/naive/isoweek.rs @@ -5,69 +5,80 @@ use core::fmt; -use super::internals::{DateImpl, Of, YearFlags}; +use super::internals::YearFlags; + +#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] +use rkyv::{Archive, Deserialize, Serialize}; /// ISO 8601 week. /// /// This type, combined with [`Weekday`](../enum.Weekday.html), -/// constitues the ISO 8601 [week date](./struct.NaiveDate.html#week-date). +/// constitutes the ISO 8601 [week date](./struct.NaiveDate.html#week-date). /// One can retrieve this type from the existing [`Datelike`](../trait.Datelike.html) types /// via the [`Datelike::iso_week`](../trait.Datelike.html#tymethod.iso_week) method. -#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone)] +#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Hash)] +#[cfg_attr( + any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), + derive(Archive, Deserialize, Serialize), + archive(compare(PartialEq, PartialOrd)), + archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) +)] +#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] pub struct IsoWeek { - // note that this allows for larger year range than `NaiveDate`. - // this is crucial because we have an edge case for the first and last week supported, + // Note that this allows for larger year range than `NaiveDate`. + // This is crucial because we have an edge case for the first and last week supported, // which year number might not match the calendar year number. - ywf: DateImpl, // (year << 10) | (week << 4) | flag -} - -/// Returns the corresponding `IsoWeek` from the year and the `Of` internal value. -// -// internal use only. we don't expose the public constructor for `IsoWeek` for now, -// because the year range for the week date and the calendar date do not match and -// it is confusing to have a date that is out of range in one and not in another. -// currently we sidestep this issue by making `IsoWeek` fully dependent of `Datelike`. -pub fn iso_week_from_yof(year: i32, of: Of) -> IsoWeek { - let (rawweek, _) = of.isoweekdate_raw(); - let (year, week) = if rawweek < 1 { - // previous year - let prevlastweek = YearFlags::from_year(year - 1).nisoweeks(); - (year - 1, prevlastweek) - } else { - let lastweek = of.flags().nisoweeks(); - if rawweek > lastweek { - // next year - (year + 1, 1) - } else { - (year, rawweek) - } - }; - IsoWeek { ywf: (year << 10) | (week << 4) as DateImpl | DateImpl::from(of.flags().0) } + ywf: i32, // (year << 10) | (week << 4) | flag } impl IsoWeek { + /// Returns the corresponding `IsoWeek` from the year and the `Of` internal value. + // + // Internal use only. We don't expose the public constructor for `IsoWeek` for now + // because the year range for the week date and the calendar date do not match, and + // it is confusing to have a date that is out of range in one and not in another. + // Currently we sidestep this issue by making `IsoWeek` fully dependent of `Datelike`. + pub(super) fn from_yof(year: i32, ordinal: u32, year_flags: YearFlags) -> Self { + let rawweek = (ordinal + year_flags.isoweek_delta()) / 7; + let (year, week) = if rawweek < 1 { + // previous year + let prevlastweek = YearFlags::from_year(year - 1).nisoweeks(); + (year - 1, prevlastweek) + } else { + let lastweek = year_flags.nisoweeks(); + if rawweek > lastweek { + // next year + (year + 1, 1) + } else { + (year, rawweek) + } + }; + let flags = YearFlags::from_year(year); + IsoWeek { ywf: (year << 10) | (week << 4) as i32 | i32::from(flags.0) } + } + /// Returns the year number for this ISO week. /// /// # Example /// - /// ~~~~ - /// use chrono::{NaiveDate, Datelike, Weekday}; + /// ``` + /// use chrono::{Datelike, NaiveDate, Weekday}; /// - /// let d = NaiveDate::from_isoywd(2015, 1, Weekday::Mon); + /// let d = NaiveDate::from_isoywd_opt(2015, 1, Weekday::Mon).unwrap(); /// assert_eq!(d.iso_week().year(), 2015); - /// ~~~~ + /// ``` /// /// This year number might not match the calendar year number. /// Continuing the example... /// - /// ~~~~ + /// ``` /// # use chrono::{NaiveDate, Datelike, Weekday}; - /// # let d = NaiveDate::from_isoywd(2015, 1, Weekday::Mon); + /// # let d = NaiveDate::from_isoywd_opt(2015, 1, Weekday::Mon).unwrap(); /// assert_eq!(d.year(), 2014); - /// assert_eq!(d, NaiveDate::from_ymd(2014, 12, 29)); - /// ~~~~ + /// assert_eq!(d, NaiveDate::from_ymd_opt(2014, 12, 29).unwrap()); + /// ``` #[inline] - pub fn year(&self) -> i32 { + pub const fn year(&self) -> i32 { self.ywf >> 10 } @@ -77,14 +88,14 @@ impl IsoWeek { /// /// # Example /// - /// ~~~~ - /// use chrono::{NaiveDate, Datelike, Weekday}; + /// ``` + /// use chrono::{Datelike, NaiveDate, Weekday}; /// - /// let d = NaiveDate::from_isoywd(2015, 15, Weekday::Mon); + /// let d = NaiveDate::from_isoywd_opt(2015, 15, Weekday::Mon).unwrap(); /// assert_eq!(d.iso_week().week(), 15); - /// ~~~~ + /// ``` #[inline] - pub fn week(&self) -> u32 { + pub const fn week(&self) -> u32 { ((self.ywf >> 4) & 0x3f) as u32 } @@ -94,14 +105,14 @@ impl IsoWeek { /// /// # Example /// - /// ~~~~ - /// use chrono::{NaiveDate, Datelike, Weekday}; + /// ``` + /// use chrono::{Datelike, NaiveDate, Weekday}; /// - /// let d = NaiveDate::from_isoywd(2015, 15, Weekday::Mon); + /// let d = NaiveDate::from_isoywd_opt(2015, 15, Weekday::Mon).unwrap(); /// assert_eq!(d.iso_week().week0(), 14); - /// ~~~~ + /// ``` #[inline] - pub fn week0(&self) -> u32 { + pub const fn week0(&self) -> u32 { ((self.ywf >> 4) & 0x3f) as u32 - 1 } } @@ -112,26 +123,35 @@ impl IsoWeek { /// /// # Example /// -/// ~~~~ -/// use chrono::{NaiveDate, Datelike}; +/// ``` +/// use chrono::{Datelike, NaiveDate}; /// -/// assert_eq!(format!("{:?}", NaiveDate::from_ymd(2015, 9, 5).iso_week()), "2015-W36"); -/// assert_eq!(format!("{:?}", NaiveDate::from_ymd( 0, 1, 3).iso_week()), "0000-W01"); -/// assert_eq!(format!("{:?}", NaiveDate::from_ymd(9999, 12, 31).iso_week()), "9999-W52"); -/// ~~~~ +/// assert_eq!( +/// format!("{:?}", NaiveDate::from_ymd_opt(2015, 9, 5).unwrap().iso_week()), +/// "2015-W36" +/// ); +/// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(0, 1, 3).unwrap().iso_week()), "0000-W01"); +/// assert_eq!( +/// format!("{:?}", NaiveDate::from_ymd_opt(9999, 12, 31).unwrap().iso_week()), +/// "9999-W52" +/// ); +/// ``` /// /// ISO 8601 requires an explicit sign for years before 1 BCE or after 9999 CE. /// -/// ~~~~ +/// ``` /// # use chrono::{NaiveDate, Datelike}; -/// assert_eq!(format!("{:?}", NaiveDate::from_ymd( 0, 1, 2).iso_week()), "-0001-W52"); -/// assert_eq!(format!("{:?}", NaiveDate::from_ymd(10000, 12, 31).iso_week()), "+10000-W52"); -/// ~~~~ +/// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(0, 1, 2).unwrap().iso_week()), "-0001-W52"); +/// assert_eq!( +/// format!("{:?}", NaiveDate::from_ymd_opt(10000, 12, 31).unwrap().iso_week()), +/// "+10000-W52" +/// ); +/// ``` impl fmt::Debug for IsoWeek { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let year = self.year(); let week = self.week(); - if 0 <= year && year <= 9999 { + if (0..=9999).contains(&year) { write!(f, "{:04}-W{:02}", year, week) } else { // ISO 8601 requires the explicit sign for out-of-range years @@ -142,22 +162,72 @@ impl fmt::Debug for IsoWeek { #[cfg(test)] mod tests { - use naive::{internals, MAX_DATE, MIN_DATE}; - use Datelike; + #[cfg(feature = "rkyv-validation")] + use super::IsoWeek; + use crate::Datelike; + use crate::naive::date::{self, NaiveDate}; #[test] fn test_iso_week_extremes() { - let minweek = MIN_DATE.iso_week(); - let maxweek = MAX_DATE.iso_week(); + let minweek = NaiveDate::MIN.iso_week(); + let maxweek = NaiveDate::MAX.iso_week(); - assert_eq!(minweek.year(), internals::MIN_YEAR); + assert_eq!(minweek.year(), date::MIN_YEAR); assert_eq!(minweek.week(), 1); assert_eq!(minweek.week0(), 0); - assert_eq!(format!("{:?}", minweek), MIN_DATE.format("%G-W%V").to_string()); + #[cfg(feature = "alloc")] + assert_eq!(format!("{:?}", minweek), NaiveDate::MIN.format("%G-W%V").to_string()); - assert_eq!(maxweek.year(), internals::MAX_YEAR + 1); + assert_eq!(maxweek.year(), date::MAX_YEAR + 1); assert_eq!(maxweek.week(), 1); assert_eq!(maxweek.week0(), 0); - assert_eq!(format!("{:?}", maxweek), MAX_DATE.format("%G-W%V").to_string()); + #[cfg(feature = "alloc")] + assert_eq!(format!("{:?}", maxweek), NaiveDate::MAX.format("%G-W%V").to_string()); + } + + #[test] + fn test_iso_week_equivalence_for_first_week() { + let monday = NaiveDate::from_ymd_opt(2024, 12, 30).unwrap(); + let friday = NaiveDate::from_ymd_opt(2025, 1, 3).unwrap(); + + assert_eq!(monday.iso_week(), friday.iso_week()); + } + + #[test] + fn test_iso_week_equivalence_for_last_week() { + let monday = NaiveDate::from_ymd_opt(2026, 12, 28).unwrap(); + let friday = NaiveDate::from_ymd_opt(2027, 1, 1).unwrap(); + + assert_eq!(monday.iso_week(), friday.iso_week()); + } + + #[test] + fn test_iso_week_ordering_for_first_week() { + let monday = NaiveDate::from_ymd_opt(2024, 12, 30).unwrap(); + let friday = NaiveDate::from_ymd_opt(2025, 1, 3).unwrap(); + + assert!(monday.iso_week() >= friday.iso_week()); + assert!(monday.iso_week() <= friday.iso_week()); + } + + #[test] + fn test_iso_week_ordering_for_last_week() { + let monday = NaiveDate::from_ymd_opt(2026, 12, 28).unwrap(); + let friday = NaiveDate::from_ymd_opt(2027, 1, 1).unwrap(); + + assert!(monday.iso_week() >= friday.iso_week()); + assert!(monday.iso_week() <= friday.iso_week()); + } + + #[test] + #[cfg(feature = "rkyv-validation")] + fn test_rkyv_validation() { + let minweek = NaiveDate::MIN.iso_week(); + let bytes = rkyv::to_bytes::<_, 4>(&minweek).unwrap(); + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), minweek); + + let maxweek = NaiveDate::MAX.iso_week(); + let bytes = rkyv::to_bytes::<_, 4>(&maxweek).unwrap(); + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), maxweek); } } diff --git a/third_party/rust/chrono/src/naive/mod.rs b/third_party/rust/chrono/src/naive/mod.rs new file mode 100644 index 00000000000..5be9a50858b --- /dev/null +++ b/third_party/rust/chrono/src/naive/mod.rs @@ -0,0 +1,281 @@ +//! Date and time types unconcerned with timezones. +//! +//! They are primarily building blocks for other types +//! (e.g. [`TimeZone`](../offset/trait.TimeZone.html)), +//! but can be also used for the simpler date and time handling. + +use core::ops::RangeInclusive; + +use crate::Weekday; +use crate::expect; + +pub(crate) mod date; +pub(crate) mod datetime; +mod internals; +pub(crate) mod isoweek; +pub(crate) mod time; + +#[allow(deprecated)] +pub use self::date::{MAX_DATE, MIN_DATE}; +pub use self::date::{NaiveDate, NaiveDateDaysIterator, NaiveDateWeeksIterator}; +#[allow(deprecated)] +pub use self::datetime::{MAX_DATETIME, MIN_DATETIME, NaiveDateTime}; +pub use self::isoweek::IsoWeek; +pub use self::time::NaiveTime; + +#[cfg(feature = "__internal_bench")] +#[doc(hidden)] +pub use self::internals::YearFlags as __BenchYearFlags; + +/// A week represented by a [`NaiveDate`] and a [`Weekday`] which is the first +/// day of the week. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct NaiveWeek { + date: NaiveDate, + start: Weekday, +} + +impl NaiveWeek { + /// Create a new `NaiveWeek` + pub(crate) const fn new(date: NaiveDate, start: Weekday) -> Self { + Self { date, start } + } + + /// Returns a date representing the first day of the week. + /// + /// # Panics + /// + /// Panics if the first day of the week happens to fall just out of range of `NaiveDate` + /// (more than ca. 262,000 years away from common era). + /// + /// # Examples + /// + /// ``` + /// use chrono::{NaiveDate, Weekday}; + /// + /// let date = NaiveDate::from_ymd_opt(2022, 4, 18).unwrap(); + /// let week = date.week(Weekday::Mon); + /// assert!(week.first_day() <= date); + /// ``` + #[inline] + #[must_use] + pub const fn first_day(&self) -> NaiveDate { + expect(self.checked_first_day(), "first weekday out of range for `NaiveDate`") + } + + /// Returns a date representing the first day of the week or + /// `None` if the date is out of `NaiveDate`'s range + /// (more than ca. 262,000 years away from common era). + /// + /// # Examples + /// + /// ``` + /// use chrono::{NaiveDate, Weekday}; + /// + /// let date = NaiveDate::MIN; + /// let week = date.week(Weekday::Mon); + /// if let Some(first_day) = week.checked_first_day() { + /// assert!(first_day == date); + /// } else { + /// // error handling code + /// return; + /// }; + /// ``` + #[inline] + #[must_use] + pub const fn checked_first_day(&self) -> Option { + let start = self.start.num_days_from_monday() as i32; + let ref_day = self.date.weekday().num_days_from_monday() as i32; + // Calculate the number of days to subtract from `self.date`. + // Do not construct an intermediate date beyond `self.date`, because that may be out of + // range if `date` is close to `NaiveDate::MAX`. + let days = start - ref_day - if start > ref_day { 7 } else { 0 }; + self.date.add_days(days) + } + + /// Returns a date representing the last day of the week. + /// + /// # Panics + /// + /// Panics if the last day of the week happens to fall just out of range of `NaiveDate` + /// (more than ca. 262,000 years away from common era). + /// + /// # Examples + /// + /// ``` + /// use chrono::{NaiveDate, Weekday}; + /// + /// let date = NaiveDate::from_ymd_opt(2022, 4, 18).unwrap(); + /// let week = date.week(Weekday::Mon); + /// assert!(week.last_day() >= date); + /// ``` + #[inline] + #[must_use] + pub const fn last_day(&self) -> NaiveDate { + expect(self.checked_last_day(), "last weekday out of range for `NaiveDate`") + } + + /// Returns a date representing the last day of the week or + /// `None` if the date is out of `NaiveDate`'s range + /// (more than ca. 262,000 years away from common era). + /// + /// # Examples + /// + /// ``` + /// use chrono::{NaiveDate, Weekday}; + /// + /// let date = NaiveDate::MAX; + /// let week = date.week(Weekday::Mon); + /// if let Some(last_day) = week.checked_last_day() { + /// assert!(last_day == date); + /// } else { + /// // error handling code + /// return; + /// }; + /// ``` + #[inline] + #[must_use] + pub const fn checked_last_day(&self) -> Option { + let end = self.start.pred().num_days_from_monday() as i32; + let ref_day = self.date.weekday().num_days_from_monday() as i32; + // Calculate the number of days to add to `self.date`. + // Do not construct an intermediate date before `self.date` (like with `first_day()`), + // because that may be out of range if `date` is close to `NaiveDate::MIN`. + let days = end - ref_day + if end < ref_day { 7 } else { 0 }; + self.date.add_days(days) + } + + /// Returns a [`RangeInclusive`] representing the whole week bounded by + /// [first_day](NaiveWeek::first_day) and [last_day](NaiveWeek::last_day) functions. + /// + /// # Panics + /// + /// Panics if the either the first or last day of the week happens to fall just out of range of + /// `NaiveDate` (more than ca. 262,000 years away from common era). + /// + /// # Examples + /// + /// ``` + /// use chrono::{NaiveDate, Weekday}; + /// + /// let date = NaiveDate::from_ymd_opt(2022, 4, 18).unwrap(); + /// let week = date.week(Weekday::Mon); + /// let days = week.days(); + /// assert!(days.contains(&date)); + /// ``` + #[inline] + #[must_use] + pub const fn days(&self) -> RangeInclusive { + // `expect` doesn't work because `RangeInclusive` is not `Copy` + match self.checked_days() { + Some(val) => val, + None => panic!("{}", "first or last weekday is out of range for `NaiveDate`"), + } + } + + /// Returns an [`Option>`] representing the whole week bounded by + /// [checked_first_day](NaiveWeek::checked_first_day) and + /// [checked_last_day](NaiveWeek::checked_last_day) functions. + /// + /// Returns `None` if either of the boundaries are out of `NaiveDate`'s range + /// (more than ca. 262,000 years away from common era). + /// + /// + /// # Examples + /// + /// ``` + /// use chrono::{NaiveDate, Weekday}; + /// + /// let date = NaiveDate::MAX; + /// let week = date.week(Weekday::Mon); + /// let _days = match week.checked_days() { + /// Some(d) => d, + /// None => { + /// // error handling code + /// return; + /// } + /// }; + /// ``` + #[inline] + #[must_use] + pub const fn checked_days(&self) -> Option> { + match (self.checked_first_day(), self.checked_last_day()) { + (Some(first), Some(last)) => Some(first..=last), + (_, _) => None, + } + } +} + +/// A duration in calendar days. +/// +/// This is useful because when using `TimeDelta` it is possible that adding `TimeDelta::days(1)` +/// doesn't increment the day value as expected due to it being a fixed number of seconds. This +/// difference applies only when dealing with `DateTime` data types and in other cases +/// `TimeDelta::days(n)` and `Days::new(n)` are equivalent. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +pub struct Days(pub(crate) u64); + +impl Days { + /// Construct a new `Days` from a number of days + pub const fn new(num: u64) -> Self { + Self(num) + } +} + +/// Serialization/Deserialization of `NaiveDateTime` in alternate formats +/// +/// The various modules in here are intended to be used with serde's [`with` annotation] to +/// serialize as something other than the default ISO 8601 format. +/// +/// [`with` annotation]: https://serde.rs/field-attrs.html#with +#[cfg(feature = "serde")] +pub mod serde { + pub use super::datetime::serde::*; +} + +#[cfg(test)] +mod test { + use crate::{NaiveDate, Weekday}; + #[test] + fn test_naiveweek() { + let date = NaiveDate::from_ymd_opt(2022, 5, 18).unwrap(); + let asserts = [ + (Weekday::Mon, "Mon 2022-05-16", "Sun 2022-05-22"), + (Weekday::Tue, "Tue 2022-05-17", "Mon 2022-05-23"), + (Weekday::Wed, "Wed 2022-05-18", "Tue 2022-05-24"), + (Weekday::Thu, "Thu 2022-05-12", "Wed 2022-05-18"), + (Weekday::Fri, "Fri 2022-05-13", "Thu 2022-05-19"), + (Weekday::Sat, "Sat 2022-05-14", "Fri 2022-05-20"), + (Weekday::Sun, "Sun 2022-05-15", "Sat 2022-05-21"), + ]; + for (start, first_day, last_day) in asserts { + let week = date.week(start); + let days = week.days(); + assert_eq!(Ok(week.first_day()), NaiveDate::parse_from_str(first_day, "%a %Y-%m-%d")); + assert_eq!(Ok(week.last_day()), NaiveDate::parse_from_str(last_day, "%a %Y-%m-%d")); + assert!(days.contains(&date)); + } + } + + #[test] + fn test_naiveweek_min_max() { + let date_max = NaiveDate::MAX; + assert!(date_max.week(Weekday::Mon).first_day() <= date_max); + let date_min = NaiveDate::MIN; + assert!(date_min.week(Weekday::Mon).last_day() >= date_min); + } + + #[test] + fn test_naiveweek_checked_no_panic() { + let date_max = NaiveDate::MAX; + if let Some(last) = date_max.week(Weekday::Mon).checked_last_day() { + assert!(last == date_max); + } + let date_min = NaiveDate::MIN; + if let Some(first) = date_min.week(Weekday::Mon).checked_first_day() { + assert!(first == date_min); + } + let _ = date_min.week(Weekday::Mon).checked_days(); + let _ = date_max.week(Weekday::Mon).checked_days(); + } +} diff --git a/third_party/rust/chrono/src/naive/time.rs b/third_party/rust/chrono/src/naive/time.rs deleted file mode 100644 index 1ddc9fbedc9..00000000000 --- a/third_party/rust/chrono/src/naive/time.rs +++ /dev/null @@ -1,1814 +0,0 @@ -// This is a part of Chrono. -// See README.md and LICENSE.txt for details. - -//! ISO 8601 time without timezone. - -#[cfg(any(feature = "alloc", feature = "std", test))] -use core::borrow::Borrow; -use core::ops::{Add, AddAssign, Sub, SubAssign}; -use core::{fmt, hash, str}; -use oldtime::Duration as OldDuration; - -use div::div_mod_floor; -#[cfg(any(feature = "alloc", feature = "std", test))] -use format::DelayedFormat; -use format::{parse, ParseError, ParseResult, Parsed, StrftimeItems}; -use format::{Fixed, Item, Numeric, Pad}; -use Timelike; - -pub const MIN_TIME: NaiveTime = NaiveTime { secs: 0, frac: 0 }; -pub const MAX_TIME: NaiveTime = NaiveTime { secs: 23 * 3600 + 59 * 60 + 59, frac: 999_999_999 }; - -/// ISO 8601 time without timezone. -/// Allows for the nanosecond precision and optional leap second representation. -/// -/// # Leap Second Handling -/// -/// Since 1960s, the manmade atomic clock has been so accurate that -/// it is much more accurate than Earth's own motion. -/// It became desirable to define the civil time in terms of the atomic clock, -/// but that risks the desynchronization of the civil time from Earth. -/// To account for this, the designers of the Coordinated Universal Time (UTC) -/// made that the UTC should be kept within 0.9 seconds of the observed Earth-bound time. -/// When the mean solar day is longer than the ideal (86,400 seconds), -/// the error slowly accumulates and it is necessary to add a **leap second** -/// to slow the UTC down a bit. -/// (We may also remove a second to speed the UTC up a bit, but it never happened.) -/// The leap second, if any, follows 23:59:59 of June 30 or December 31 in the UTC. -/// -/// Fast forward to the 21st century, -/// we have seen 26 leap seconds from January 1972 to December 2015. -/// Yes, 26 seconds. Probably you can read this paragraph within 26 seconds. -/// But those 26 seconds, and possibly more in the future, are never predictable, -/// and whether to add a leap second or not is known only before 6 months. -/// Internet-based clocks (via NTP) do account for known leap seconds, -/// but the system API normally doesn't (and often can't, with no network connection) -/// and there is no reliable way to retrieve leap second information. -/// -/// Chrono does not try to accurately implement leap seconds; it is impossible. -/// Rather, **it allows for leap seconds but behaves as if there are *no other* leap seconds.** -/// Various operations will ignore any possible leap second(s) -/// except when any of the operands were actually leap seconds. -/// -/// If you cannot tolerate this behavior, -/// you must use a separate `TimeZone` for the International Atomic Time (TAI). -/// TAI is like UTC but has no leap seconds, and thus slightly differs from UTC. -/// Chrono does not yet provide such implementation, but it is planned. -/// -/// ## Representing Leap Seconds -/// -/// The leap second is indicated via fractional seconds more than 1 second. -/// This makes possible to treat a leap second as the prior non-leap second -/// if you don't care about sub-second accuracy. -/// You should use the proper formatting to get the raw leap second. -/// -/// All methods accepting fractional seconds will accept such values. -/// -/// ~~~~ -/// use chrono::{NaiveDate, NaiveTime, Utc, TimeZone}; -/// -/// let t = NaiveTime::from_hms_milli(8, 59, 59, 1_000); -/// -/// let dt1 = NaiveDate::from_ymd(2015, 7, 1).and_hms_micro(8, 59, 59, 1_000_000); -/// -/// let dt2 = Utc.ymd(2015, 6, 30).and_hms_nano(23, 59, 59, 1_000_000_000); -/// # let _ = (t, dt1, dt2); -/// ~~~~ -/// -/// Note that the leap second can happen anytime given an appropriate time zone; -/// 2015-07-01 01:23:60 would be a proper leap second if UTC+01:24 had existed. -/// Practically speaking, though, by the time of the first leap second on 1972-06-30, -/// every time zone offset around the world has standardized to the 5-minute alignment. -/// -/// ## Date And Time Arithmetics -/// -/// As a concrete example, let's assume that `03:00:60` and `04:00:60` are leap seconds. -/// In reality, of course, leap seconds are separated by at least 6 months. -/// We will also use some intuitive concise notations for the explanation. -/// -/// `Time + Duration` -/// (short for [`NaiveTime::overflowing_add_signed`](#method.overflowing_add_signed)): -/// -/// - `03:00:00 + 1s = 03:00:01`. -/// - `03:00:59 + 60s = 03:02:00`. -/// - `03:00:59 + 1s = 03:01:00`. -/// - `03:00:60 + 1s = 03:01:00`. -/// Note that the sum is identical to the previous. -/// - `03:00:60 + 60s = 03:01:59`. -/// - `03:00:60 + 61s = 03:02:00`. -/// - `03:00:60.1 + 0.8s = 03:00:60.9`. -/// -/// `Time - Duration` -/// (short for [`NaiveTime::overflowing_sub_signed`](#method.overflowing_sub_signed)): -/// -/// - `03:00:00 - 1s = 02:59:59`. -/// - `03:01:00 - 1s = 03:00:59`. -/// - `03:01:00 - 60s = 03:00:00`. -/// - `03:00:60 - 60s = 03:00:00`. -/// Note that the result is identical to the previous. -/// - `03:00:60.7 - 0.4s = 03:00:60.3`. -/// - `03:00:60.7 - 0.9s = 03:00:59.8`. -/// -/// `Time - Time` -/// (short for [`NaiveTime::signed_duration_since`](#method.signed_duration_since)): -/// -/// - `04:00:00 - 03:00:00 = 3600s`. -/// - `03:01:00 - 03:00:00 = 60s`. -/// - `03:00:60 - 03:00:00 = 60s`. -/// Note that the difference is identical to the previous. -/// - `03:00:60.6 - 03:00:59.4 = 1.2s`. -/// - `03:01:00 - 03:00:59.8 = 0.2s`. -/// - `03:01:00 - 03:00:60.5 = 0.5s`. -/// Note that the difference is larger than the previous, -/// even though the leap second clearly follows the previous whole second. -/// - `04:00:60.9 - 03:00:60.1 = -/// (04:00:60.9 - 04:00:00) + (04:00:00 - 03:01:00) + (03:01:00 - 03:00:60.1) = -/// 60.9s + 3540s + 0.9s = 3601.8s`. -/// -/// In general, -/// -/// - `Time + Duration` unconditionally equals to `Duration + Time`. -/// -/// - `Time - Duration` unconditionally equals to `Time + (-Duration)`. -/// -/// - `Time1 - Time2` unconditionally equals to `-(Time2 - Time1)`. -/// -/// - Associativity does not generally hold, because -/// `(Time + Duration1) - Duration2` no longer equals to `Time + (Duration1 - Duration2)` -/// for two positive durations. -/// -/// - As a special case, `(Time + Duration) - Duration` also does not equal to `Time`. -/// -/// - If you can assume that all durations have the same sign, however, -/// then the associativity holds: -/// `(Time + Duration1) + Duration2` equals to `Time + (Duration1 + Duration2)` -/// for two positive durations. -/// -/// ## Reading And Writing Leap Seconds -/// -/// The "typical" leap seconds on the minute boundary are -/// correctly handled both in the formatting and parsing. -/// The leap second in the human-readable representation -/// will be represented as the second part being 60, as required by ISO 8601. -/// -/// ~~~~ -/// use chrono::{Utc, TimeZone}; -/// -/// let dt = Utc.ymd(2015, 6, 30).and_hms_milli(23, 59, 59, 1_000); -/// assert_eq!(format!("{:?}", dt), "2015-06-30T23:59:60Z"); -/// ~~~~ -/// -/// There are hypothetical leap seconds not on the minute boundary -/// nevertheless supported by Chrono. -/// They are allowed for the sake of completeness and consistency; -/// there were several "exotic" time zone offsets with fractional minutes prior to UTC after all. -/// For such cases the human-readable representation is ambiguous -/// and would be read back to the next non-leap second. -/// -/// ~~~~ -/// use chrono::{DateTime, Utc, TimeZone}; -/// -/// let dt = Utc.ymd(2015, 6, 30).and_hms_milli(23, 56, 4, 1_000); -/// assert_eq!(format!("{:?}", dt), "2015-06-30T23:56:05Z"); -/// -/// let dt = Utc.ymd(2015, 6, 30).and_hms(23, 56, 5); -/// assert_eq!(format!("{:?}", dt), "2015-06-30T23:56:05Z"); -/// assert_eq!(DateTime::parse_from_rfc3339("2015-06-30T23:56:05Z").unwrap(), dt); -/// ~~~~ -/// -/// Since Chrono alone cannot determine any existence of leap seconds, -/// **there is absolutely no guarantee that the leap second read has actually happened**. -#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone)] -pub struct NaiveTime { - secs: u32, - frac: u32, -} - -impl NaiveTime { - /// Makes a new `NaiveTime` from hour, minute and second. - /// - /// No [leap second](#leap-second-handling) is allowed here; - /// use `NaiveTime::from_hms_*` methods with a subsecond parameter instead. - /// - /// Panics on invalid hour, minute and/or second. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveTime, Timelike}; - /// - /// let t = NaiveTime::from_hms(23, 56, 4); - /// assert_eq!(t.hour(), 23); - /// assert_eq!(t.minute(), 56); - /// assert_eq!(t.second(), 4); - /// assert_eq!(t.nanosecond(), 0); - /// ~~~~ - #[inline] - pub fn from_hms(hour: u32, min: u32, sec: u32) -> NaiveTime { - NaiveTime::from_hms_opt(hour, min, sec).expect("invalid time") - } - - /// Makes a new `NaiveTime` from hour, minute and second. - /// - /// No [leap second](#leap-second-handling) is allowed here; - /// use `NaiveTime::from_hms_*_opt` methods with a subsecond parameter instead. - /// - /// Returns `None` on invalid hour, minute and/or second. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::NaiveTime; - /// - /// let from_hms_opt = NaiveTime::from_hms_opt; - /// - /// assert!(from_hms_opt(0, 0, 0).is_some()); - /// assert!(from_hms_opt(23, 59, 59).is_some()); - /// assert!(from_hms_opt(24, 0, 0).is_none()); - /// assert!(from_hms_opt(23, 60, 0).is_none()); - /// assert!(from_hms_opt(23, 59, 60).is_none()); - /// ~~~~ - #[inline] - pub fn from_hms_opt(hour: u32, min: u32, sec: u32) -> Option { - NaiveTime::from_hms_nano_opt(hour, min, sec, 0) - } - - /// Makes a new `NaiveTime` from hour, minute, second and millisecond. - /// - /// The millisecond part can exceed 1,000 - /// in order to represent the [leap second](#leap-second-handling). - /// - /// Panics on invalid hour, minute, second and/or millisecond. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveTime, Timelike}; - /// - /// let t = NaiveTime::from_hms_milli(23, 56, 4, 12); - /// assert_eq!(t.hour(), 23); - /// assert_eq!(t.minute(), 56); - /// assert_eq!(t.second(), 4); - /// assert_eq!(t.nanosecond(), 12_000_000); - /// ~~~~ - #[inline] - pub fn from_hms_milli(hour: u32, min: u32, sec: u32, milli: u32) -> NaiveTime { - NaiveTime::from_hms_milli_opt(hour, min, sec, milli).expect("invalid time") - } - - /// Makes a new `NaiveTime` from hour, minute, second and millisecond. - /// - /// The millisecond part can exceed 1,000 - /// in order to represent the [leap second](#leap-second-handling). - /// - /// Returns `None` on invalid hour, minute, second and/or millisecond. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::NaiveTime; - /// - /// let from_hmsm_opt = NaiveTime::from_hms_milli_opt; - /// - /// assert!(from_hmsm_opt(0, 0, 0, 0).is_some()); - /// assert!(from_hmsm_opt(23, 59, 59, 999).is_some()); - /// assert!(from_hmsm_opt(23, 59, 59, 1_999).is_some()); // a leap second after 23:59:59 - /// assert!(from_hmsm_opt(24, 0, 0, 0).is_none()); - /// assert!(from_hmsm_opt(23, 60, 0, 0).is_none()); - /// assert!(from_hmsm_opt(23, 59, 60, 0).is_none()); - /// assert!(from_hmsm_opt(23, 59, 59, 2_000).is_none()); - /// ~~~~ - #[inline] - pub fn from_hms_milli_opt(hour: u32, min: u32, sec: u32, milli: u32) -> Option { - milli - .checked_mul(1_000_000) - .and_then(|nano| NaiveTime::from_hms_nano_opt(hour, min, sec, nano)) - } - - /// Makes a new `NaiveTime` from hour, minute, second and microsecond. - /// - /// The microsecond part can exceed 1,000,000 - /// in order to represent the [leap second](#leap-second-handling). - /// - /// Panics on invalid hour, minute, second and/or microsecond. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveTime, Timelike}; - /// - /// let t = NaiveTime::from_hms_micro(23, 56, 4, 12_345); - /// assert_eq!(t.hour(), 23); - /// assert_eq!(t.minute(), 56); - /// assert_eq!(t.second(), 4); - /// assert_eq!(t.nanosecond(), 12_345_000); - /// ~~~~ - #[inline] - pub fn from_hms_micro(hour: u32, min: u32, sec: u32, micro: u32) -> NaiveTime { - NaiveTime::from_hms_micro_opt(hour, min, sec, micro).expect("invalid time") - } - - /// Makes a new `NaiveTime` from hour, minute, second and microsecond. - /// - /// The microsecond part can exceed 1,000,000 - /// in order to represent the [leap second](#leap-second-handling). - /// - /// Returns `None` on invalid hour, minute, second and/or microsecond. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::NaiveTime; - /// - /// let from_hmsu_opt = NaiveTime::from_hms_micro_opt; - /// - /// assert!(from_hmsu_opt(0, 0, 0, 0).is_some()); - /// assert!(from_hmsu_opt(23, 59, 59, 999_999).is_some()); - /// assert!(from_hmsu_opt(23, 59, 59, 1_999_999).is_some()); // a leap second after 23:59:59 - /// assert!(from_hmsu_opt(24, 0, 0, 0).is_none()); - /// assert!(from_hmsu_opt(23, 60, 0, 0).is_none()); - /// assert!(from_hmsu_opt(23, 59, 60, 0).is_none()); - /// assert!(from_hmsu_opt(23, 59, 59, 2_000_000).is_none()); - /// ~~~~ - #[inline] - pub fn from_hms_micro_opt(hour: u32, min: u32, sec: u32, micro: u32) -> Option { - micro.checked_mul(1_000).and_then(|nano| NaiveTime::from_hms_nano_opt(hour, min, sec, nano)) - } - - /// Makes a new `NaiveTime` from hour, minute, second and nanosecond. - /// - /// The nanosecond part can exceed 1,000,000,000 - /// in order to represent the [leap second](#leap-second-handling). - /// - /// Panics on invalid hour, minute, second and/or nanosecond. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveTime, Timelike}; - /// - /// let t = NaiveTime::from_hms_nano(23, 56, 4, 12_345_678); - /// assert_eq!(t.hour(), 23); - /// assert_eq!(t.minute(), 56); - /// assert_eq!(t.second(), 4); - /// assert_eq!(t.nanosecond(), 12_345_678); - /// ~~~~ - #[inline] - pub fn from_hms_nano(hour: u32, min: u32, sec: u32, nano: u32) -> NaiveTime { - NaiveTime::from_hms_nano_opt(hour, min, sec, nano).expect("invalid time") - } - - /// Makes a new `NaiveTime` from hour, minute, second and nanosecond. - /// - /// The nanosecond part can exceed 1,000,000,000 - /// in order to represent the [leap second](#leap-second-handling). - /// - /// Returns `None` on invalid hour, minute, second and/or nanosecond. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::NaiveTime; - /// - /// let from_hmsn_opt = NaiveTime::from_hms_nano_opt; - /// - /// assert!(from_hmsn_opt(0, 0, 0, 0).is_some()); - /// assert!(from_hmsn_opt(23, 59, 59, 999_999_999).is_some()); - /// assert!(from_hmsn_opt(23, 59, 59, 1_999_999_999).is_some()); // a leap second after 23:59:59 - /// assert!(from_hmsn_opt(24, 0, 0, 0).is_none()); - /// assert!(from_hmsn_opt(23, 60, 0, 0).is_none()); - /// assert!(from_hmsn_opt(23, 59, 60, 0).is_none()); - /// assert!(from_hmsn_opt(23, 59, 59, 2_000_000_000).is_none()); - /// ~~~~ - #[inline] - pub fn from_hms_nano_opt(hour: u32, min: u32, sec: u32, nano: u32) -> Option { - if hour >= 24 || min >= 60 || sec >= 60 || nano >= 2_000_000_000 { - return None; - } - let secs = hour * 3600 + min * 60 + sec; - Some(NaiveTime { secs: secs, frac: nano }) - } - - /// Makes a new `NaiveTime` from the number of seconds since midnight and nanosecond. - /// - /// The nanosecond part can exceed 1,000,000,000 - /// in order to represent the [leap second](#leap-second-handling). - /// - /// Panics on invalid number of seconds and/or nanosecond. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveTime, Timelike}; - /// - /// let t = NaiveTime::from_num_seconds_from_midnight(86164, 12_345_678); - /// assert_eq!(t.hour(), 23); - /// assert_eq!(t.minute(), 56); - /// assert_eq!(t.second(), 4); - /// assert_eq!(t.nanosecond(), 12_345_678); - /// ~~~~ - #[inline] - pub fn from_num_seconds_from_midnight(secs: u32, nano: u32) -> NaiveTime { - NaiveTime::from_num_seconds_from_midnight_opt(secs, nano).expect("invalid time") - } - - /// Makes a new `NaiveTime` from the number of seconds since midnight and nanosecond. - /// - /// The nanosecond part can exceed 1,000,000,000 - /// in order to represent the [leap second](#leap-second-handling). - /// - /// Returns `None` on invalid number of seconds and/or nanosecond. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::NaiveTime; - /// - /// let from_nsecs_opt = NaiveTime::from_num_seconds_from_midnight_opt; - /// - /// assert!(from_nsecs_opt(0, 0).is_some()); - /// assert!(from_nsecs_opt(86399, 999_999_999).is_some()); - /// assert!(from_nsecs_opt(86399, 1_999_999_999).is_some()); // a leap second after 23:59:59 - /// assert!(from_nsecs_opt(86_400, 0).is_none()); - /// assert!(from_nsecs_opt(86399, 2_000_000_000).is_none()); - /// ~~~~ - #[inline] - pub fn from_num_seconds_from_midnight_opt(secs: u32, nano: u32) -> Option { - if secs >= 86_400 || nano >= 2_000_000_000 { - return None; - } - Some(NaiveTime { secs: secs, frac: nano }) - } - - /// Parses a string with the specified format string and returns a new `NaiveTime`. - /// See the [`format::strftime` module](../format/strftime/index.html) - /// on the supported escape sequences. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::NaiveTime; - /// - /// let parse_from_str = NaiveTime::parse_from_str; - /// - /// assert_eq!(parse_from_str("23:56:04", "%H:%M:%S"), - /// Ok(NaiveTime::from_hms(23, 56, 4))); - /// assert_eq!(parse_from_str("pm012345.6789", "%p%I%M%S%.f"), - /// Ok(NaiveTime::from_hms_micro(13, 23, 45, 678_900))); - /// ~~~~ - /// - /// Date and offset is ignored for the purpose of parsing. - /// - /// ~~~~ - /// # use chrono::NaiveTime; - /// # let parse_from_str = NaiveTime::parse_from_str; - /// assert_eq!(parse_from_str("2014-5-17T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), - /// Ok(NaiveTime::from_hms(12, 34, 56))); - /// ~~~~ - /// - /// [Leap seconds](#leap-second-handling) are correctly handled by - /// treating any time of the form `hh:mm:60` as a leap second. - /// (This equally applies to the formatting, so the round trip is possible.) - /// - /// ~~~~ - /// # use chrono::NaiveTime; - /// # let parse_from_str = NaiveTime::parse_from_str; - /// assert_eq!(parse_from_str("08:59:60.123", "%H:%M:%S%.f"), - /// Ok(NaiveTime::from_hms_milli(8, 59, 59, 1_123))); - /// ~~~~ - /// - /// Missing seconds are assumed to be zero, - /// but out-of-bound times or insufficient fields are errors otherwise. - /// - /// ~~~~ - /// # use chrono::NaiveTime; - /// # let parse_from_str = NaiveTime::parse_from_str; - /// assert_eq!(parse_from_str("7:15", "%H:%M"), - /// Ok(NaiveTime::from_hms(7, 15, 0))); - /// - /// assert!(parse_from_str("04m33s", "%Mm%Ss").is_err()); - /// assert!(parse_from_str("12", "%H").is_err()); - /// assert!(parse_from_str("17:60", "%H:%M").is_err()); - /// assert!(parse_from_str("24:00:00", "%H:%M:%S").is_err()); - /// ~~~~ - /// - /// All parsed fields should be consistent to each other, otherwise it's an error. - /// Here `%H` is for 24-hour clocks, unlike `%I`, - /// and thus can be independently determined without AM/PM. - /// - /// ~~~~ - /// # use chrono::NaiveTime; - /// # let parse_from_str = NaiveTime::parse_from_str; - /// assert!(parse_from_str("13:07 AM", "%H:%M %p").is_err()); - /// ~~~~ - pub fn parse_from_str(s: &str, fmt: &str) -> ParseResult { - let mut parsed = Parsed::new(); - parse(&mut parsed, s, StrftimeItems::new(fmt))?; - parsed.to_naive_time() - } - - /// Adds given `Duration` to the current time, - /// and also returns the number of *seconds* - /// in the integral number of days ignored from the addition. - /// (We cannot return `Duration` because it is subject to overflow or underflow.) - /// - /// # Example - /// - /// ~~~~ - /// # extern crate chrono; fn main() { - /// use chrono::{Duration, NaiveTime}; - /// - /// let from_hms = NaiveTime::from_hms; - /// - /// assert_eq!(from_hms(3, 4, 5).overflowing_add_signed(Duration::hours(11)), - /// (from_hms(14, 4, 5), 0)); - /// assert_eq!(from_hms(3, 4, 5).overflowing_add_signed(Duration::hours(23)), - /// (from_hms(2, 4, 5), 86_400)); - /// assert_eq!(from_hms(3, 4, 5).overflowing_add_signed(Duration::hours(-7)), - /// (from_hms(20, 4, 5), -86_400)); - /// # } - /// ~~~~ - #[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))] - pub fn overflowing_add_signed(&self, mut rhs: OldDuration) -> (NaiveTime, i64) { - let mut secs = self.secs; - let mut frac = self.frac; - - // check if `self` is a leap second and adding `rhs` would escape that leap second. - // if it's the case, update `self` and `rhs` to involve no leap second; - // otherwise the addition immediately finishes. - if frac >= 1_000_000_000 { - let rfrac = 2_000_000_000 - frac; - if rhs >= OldDuration::nanoseconds(i64::from(rfrac)) { - rhs = rhs - OldDuration::nanoseconds(i64::from(rfrac)); - secs += 1; - frac = 0; - } else if rhs < OldDuration::nanoseconds(-i64::from(frac)) { - rhs = rhs + OldDuration::nanoseconds(i64::from(frac)); - frac = 0; - } else { - frac = (i64::from(frac) + rhs.num_nanoseconds().unwrap()) as u32; - debug_assert!(frac < 2_000_000_000); - return (NaiveTime { secs: secs, frac: frac }, 0); - } - } - debug_assert!(secs <= 86_400); - debug_assert!(frac < 1_000_000_000); - - let rhssecs = rhs.num_seconds(); - let rhsfrac = (rhs - OldDuration::seconds(rhssecs)).num_nanoseconds().unwrap(); - debug_assert_eq!(OldDuration::seconds(rhssecs) + OldDuration::nanoseconds(rhsfrac), rhs); - let rhssecsinday = rhssecs % 86_400; - let mut morerhssecs = rhssecs - rhssecsinday; - let rhssecs = rhssecsinday as i32; - let rhsfrac = rhsfrac as i32; - debug_assert!(-86_400 < rhssecs && rhssecs < 86_400); - debug_assert_eq!(morerhssecs % 86_400, 0); - debug_assert!(-1_000_000_000 < rhsfrac && rhsfrac < 1_000_000_000); - - let mut secs = secs as i32 + rhssecs; - let mut frac = frac as i32 + rhsfrac; - debug_assert!(-86_400 < secs && secs < 2 * 86_400); - debug_assert!(-1_000_000_000 < frac && frac < 2_000_000_000); - - if frac < 0 { - frac += 1_000_000_000; - secs -= 1; - } else if frac >= 1_000_000_000 { - frac -= 1_000_000_000; - secs += 1; - } - debug_assert!(-86_400 <= secs && secs < 2 * 86_400); - debug_assert!(0 <= frac && frac < 1_000_000_000); - - if secs < 0 { - secs += 86_400; - morerhssecs -= 86_400; - } else if secs >= 86_400 { - secs -= 86_400; - morerhssecs += 86_400; - } - debug_assert!(0 <= secs && secs < 86_400); - - (NaiveTime { secs: secs as u32, frac: frac as u32 }, morerhssecs) - } - - /// Subtracts given `Duration` from the current time, - /// and also returns the number of *seconds* - /// in the integral number of days ignored from the subtraction. - /// (We cannot return `Duration` because it is subject to overflow or underflow.) - /// - /// # Example - /// - /// ~~~~ - /// # extern crate chrono; fn main() { - /// use chrono::{Duration, NaiveTime}; - /// - /// let from_hms = NaiveTime::from_hms; - /// - /// assert_eq!(from_hms(3, 4, 5).overflowing_sub_signed(Duration::hours(2)), - /// (from_hms(1, 4, 5), 0)); - /// assert_eq!(from_hms(3, 4, 5).overflowing_sub_signed(Duration::hours(17)), - /// (from_hms(10, 4, 5), 86_400)); - /// assert_eq!(from_hms(3, 4, 5).overflowing_sub_signed(Duration::hours(-22)), - /// (from_hms(1, 4, 5), -86_400)); - /// # } - /// ~~~~ - #[inline] - pub fn overflowing_sub_signed(&self, rhs: OldDuration) -> (NaiveTime, i64) { - let (time, rhs) = self.overflowing_add_signed(-rhs); - (time, -rhs) // safe to negate, rhs is within +/- (2^63 / 1000) - } - - /// Subtracts another `NaiveTime` from the current time. - /// Returns a `Duration` within +/- 1 day. - /// This does not overflow or underflow at all. - /// - /// As a part of Chrono's [leap second handling](#leap-second-handling), - /// the subtraction assumes that **there is no leap second ever**, - /// except when any of the `NaiveTime`s themselves represents a leap second - /// in which case the assumption becomes that - /// **there are exactly one (or two) leap second(s) ever**. - /// - /// # Example - /// - /// ~~~~ - /// # extern crate chrono; fn main() { - /// use chrono::{Duration, NaiveTime}; - /// - /// let from_hmsm = NaiveTime::from_hms_milli; - /// let since = NaiveTime::signed_duration_since; - /// - /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 5, 7, 900)), - /// Duration::zero()); - /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 5, 7, 875)), - /// Duration::milliseconds(25)); - /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 5, 6, 925)), - /// Duration::milliseconds(975)); - /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 5, 0, 900)), - /// Duration::seconds(7)); - /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 0, 7, 900)), - /// Duration::seconds(5 * 60)); - /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(0, 5, 7, 900)), - /// Duration::seconds(3 * 3600)); - /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(4, 5, 7, 900)), - /// Duration::seconds(-3600)); - /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(2, 4, 6, 800)), - /// Duration::seconds(3600 + 60 + 1) + Duration::milliseconds(100)); - /// # } - /// ~~~~ - /// - /// Leap seconds are handled, but the subtraction assumes that - /// there were no other leap seconds happened. - /// - /// ~~~~ - /// # extern crate chrono; fn main() { - /// # use chrono::{Duration, NaiveTime}; - /// # let from_hmsm = NaiveTime::from_hms_milli; - /// # let since = NaiveTime::signed_duration_since; - /// assert_eq!(since(from_hmsm(3, 0, 59, 1_000), from_hmsm(3, 0, 59, 0)), - /// Duration::seconds(1)); - /// assert_eq!(since(from_hmsm(3, 0, 59, 1_500), from_hmsm(3, 0, 59, 0)), - /// Duration::milliseconds(1500)); - /// assert_eq!(since(from_hmsm(3, 0, 59, 1_000), from_hmsm(3, 0, 0, 0)), - /// Duration::seconds(60)); - /// assert_eq!(since(from_hmsm(3, 0, 0, 0), from_hmsm(2, 59, 59, 1_000)), - /// Duration::seconds(1)); - /// assert_eq!(since(from_hmsm(3, 0, 59, 1_000), from_hmsm(2, 59, 59, 1_000)), - /// Duration::seconds(61)); - /// # } - /// ~~~~ - pub fn signed_duration_since(self, rhs: NaiveTime) -> OldDuration { - // | | :leap| | | | | | | :leap| | - // | | : | | | | | | | : | | - // ----+----+-----*---+----+----+----+----+----+----+-------*-+----+---- - // | `rhs` | | `self` - // |======================================>| | - // | | `self.secs - rhs.secs` |`self.frac` - // |====>| | |======>| - // `rhs.frac`|========================================>| - // | | | `self - rhs` | | - - use core::cmp::Ordering; - - let secs = i64::from(self.secs) - i64::from(rhs.secs); - let frac = i64::from(self.frac) - i64::from(rhs.frac); - - // `secs` may contain a leap second yet to be counted - let adjust = match self.secs.cmp(&rhs.secs) { - Ordering::Greater => { - if rhs.frac >= 1_000_000_000 { - 1 - } else { - 0 - } - } - Ordering::Equal => 0, - Ordering::Less => { - if self.frac >= 1_000_000_000 { - -1 - } else { - 0 - } - } - }; - - OldDuration::seconds(secs + adjust) + OldDuration::nanoseconds(frac) - } - - /// Formats the time with the specified formatting items. - /// Otherwise it is the same as the ordinary [`format`](#method.format) method. - /// - /// The `Iterator` of items should be `Clone`able, - /// since the resulting `DelayedFormat` value may be formatted multiple times. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::NaiveTime; - /// use chrono::format::strftime::StrftimeItems; - /// - /// let fmt = StrftimeItems::new("%H:%M:%S"); - /// let t = NaiveTime::from_hms(23, 56, 4); - /// assert_eq!(t.format_with_items(fmt.clone()).to_string(), "23:56:04"); - /// assert_eq!(t.format("%H:%M:%S").to_string(), "23:56:04"); - /// ~~~~ - /// - /// The resulting `DelayedFormat` can be formatted directly via the `Display` trait. - /// - /// ~~~~ - /// # use chrono::NaiveTime; - /// # use chrono::format::strftime::StrftimeItems; - /// # let fmt = StrftimeItems::new("%H:%M:%S").clone(); - /// # let t = NaiveTime::from_hms(23, 56, 4); - /// assert_eq!(format!("{}", t.format_with_items(fmt)), "23:56:04"); - /// ~~~~ - #[cfg(any(feature = "alloc", feature = "std", test))] - #[inline] - pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat - where - I: Iterator + Clone, - B: Borrow>, - { - DelayedFormat::new(None, Some(*self), items) - } - - /// Formats the time with the specified format string. - /// See the [`format::strftime` module](../format/strftime/index.html) - /// on the supported escape sequences. - /// - /// This returns a `DelayedFormat`, - /// which gets converted to a string only when actual formatting happens. - /// You may use the `to_string` method to get a `String`, - /// or just feed it into `print!` and other formatting macros. - /// (In this way it avoids the redundant memory allocation.) - /// - /// A wrong format string does *not* issue an error immediately. - /// Rather, converting or formatting the `DelayedFormat` fails. - /// You are recommended to immediately use `DelayedFormat` for this reason. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::NaiveTime; - /// - /// let t = NaiveTime::from_hms_nano(23, 56, 4, 12_345_678); - /// assert_eq!(t.format("%H:%M:%S").to_string(), "23:56:04"); - /// assert_eq!(t.format("%H:%M:%S%.6f").to_string(), "23:56:04.012345"); - /// assert_eq!(t.format("%-I:%M %p").to_string(), "11:56 PM"); - /// ~~~~ - /// - /// The resulting `DelayedFormat` can be formatted directly via the `Display` trait. - /// - /// ~~~~ - /// # use chrono::NaiveTime; - /// # let t = NaiveTime::from_hms_nano(23, 56, 4, 12_345_678); - /// assert_eq!(format!("{}", t.format("%H:%M:%S")), "23:56:04"); - /// assert_eq!(format!("{}", t.format("%H:%M:%S%.6f")), "23:56:04.012345"); - /// assert_eq!(format!("{}", t.format("%-I:%M %p")), "11:56 PM"); - /// ~~~~ - #[cfg(any(feature = "alloc", feature = "std", test))] - #[inline] - pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { - self.format_with_items(StrftimeItems::new(fmt)) - } - - /// Returns a triple of the hour, minute and second numbers. - fn hms(&self) -> (u32, u32, u32) { - let (mins, sec) = div_mod_floor(self.secs, 60); - let (hour, min) = div_mod_floor(mins, 60); - (hour, min, sec) - } -} - -impl Timelike for NaiveTime { - /// Returns the hour number from 0 to 23. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveTime, Timelike}; - /// - /// assert_eq!(NaiveTime::from_hms(0, 0, 0).hour(), 0); - /// assert_eq!(NaiveTime::from_hms_nano(23, 56, 4, 12_345_678).hour(), 23); - /// ~~~~ - #[inline] - fn hour(&self) -> u32 { - self.hms().0 - } - - /// Returns the minute number from 0 to 59. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveTime, Timelike}; - /// - /// assert_eq!(NaiveTime::from_hms(0, 0, 0).minute(), 0); - /// assert_eq!(NaiveTime::from_hms_nano(23, 56, 4, 12_345_678).minute(), 56); - /// ~~~~ - #[inline] - fn minute(&self) -> u32 { - self.hms().1 - } - - /// Returns the second number from 0 to 59. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveTime, Timelike}; - /// - /// assert_eq!(NaiveTime::from_hms(0, 0, 0).second(), 0); - /// assert_eq!(NaiveTime::from_hms_nano(23, 56, 4, 12_345_678).second(), 4); - /// ~~~~ - /// - /// This method never returns 60 even when it is a leap second. - /// ([Why?](#leap-second-handling)) - /// Use the proper [formatting method](#method.format) to get a human-readable representation. - /// - /// ~~~~ - /// # use chrono::{NaiveTime, Timelike}; - /// let leap = NaiveTime::from_hms_milli(23, 59, 59, 1_000); - /// assert_eq!(leap.second(), 59); - /// assert_eq!(leap.format("%H:%M:%S").to_string(), "23:59:60"); - /// ~~~~ - #[inline] - fn second(&self) -> u32 { - self.hms().2 - } - - /// Returns the number of nanoseconds since the whole non-leap second. - /// The range from 1,000,000,000 to 1,999,999,999 represents - /// the [leap second](#leap-second-handling). - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveTime, Timelike}; - /// - /// assert_eq!(NaiveTime::from_hms(0, 0, 0).nanosecond(), 0); - /// assert_eq!(NaiveTime::from_hms_nano(23, 56, 4, 12_345_678).nanosecond(), 12_345_678); - /// ~~~~ - /// - /// Leap seconds may have seemingly out-of-range return values. - /// You can reduce the range with `time.nanosecond() % 1_000_000_000`, or - /// use the proper [formatting method](#method.format) to get a human-readable representation. - /// - /// ~~~~ - /// # use chrono::{NaiveTime, Timelike}; - /// let leap = NaiveTime::from_hms_milli(23, 59, 59, 1_000); - /// assert_eq!(leap.nanosecond(), 1_000_000_000); - /// assert_eq!(leap.format("%H:%M:%S%.9f").to_string(), "23:59:60.000000000"); - /// ~~~~ - #[inline] - fn nanosecond(&self) -> u32 { - self.frac - } - - /// Makes a new `NaiveTime` with the hour number changed. - /// - /// Returns `None` when the resulting `NaiveTime` would be invalid. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveTime, Timelike}; - /// - /// let dt = NaiveTime::from_hms_nano(23, 56, 4, 12_345_678); - /// assert_eq!(dt.with_hour(7), Some(NaiveTime::from_hms_nano(7, 56, 4, 12_345_678))); - /// assert_eq!(dt.with_hour(24), None); - /// ~~~~ - #[inline] - fn with_hour(&self, hour: u32) -> Option { - if hour >= 24 { - return None; - } - let secs = hour * 3600 + self.secs % 3600; - Some(NaiveTime { secs: secs, ..*self }) - } - - /// Makes a new `NaiveTime` with the minute number changed. - /// - /// Returns `None` when the resulting `NaiveTime` would be invalid. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveTime, Timelike}; - /// - /// let dt = NaiveTime::from_hms_nano(23, 56, 4, 12_345_678); - /// assert_eq!(dt.with_minute(45), Some(NaiveTime::from_hms_nano(23, 45, 4, 12_345_678))); - /// assert_eq!(dt.with_minute(60), None); - /// ~~~~ - #[inline] - fn with_minute(&self, min: u32) -> Option { - if min >= 60 { - return None; - } - let secs = self.secs / 3600 * 3600 + min * 60 + self.secs % 60; - Some(NaiveTime { secs: secs, ..*self }) - } - - /// Makes a new `NaiveTime` with the second number changed. - /// - /// Returns `None` when the resulting `NaiveTime` would be invalid. - /// As with the [`second`](#method.second) method, - /// the input range is restricted to 0 through 59. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveTime, Timelike}; - /// - /// let dt = NaiveTime::from_hms_nano(23, 56, 4, 12_345_678); - /// assert_eq!(dt.with_second(17), Some(NaiveTime::from_hms_nano(23, 56, 17, 12_345_678))); - /// assert_eq!(dt.with_second(60), None); - /// ~~~~ - #[inline] - fn with_second(&self, sec: u32) -> Option { - if sec >= 60 { - return None; - } - let secs = self.secs / 60 * 60 + sec; - Some(NaiveTime { secs: secs, ..*self }) - } - - /// Makes a new `NaiveTime` with nanoseconds since the whole non-leap second changed. - /// - /// Returns `None` when the resulting `NaiveTime` would be invalid. - /// As with the [`nanosecond`](#method.nanosecond) method, - /// the input range can exceed 1,000,000,000 for leap seconds. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveTime, Timelike}; - /// - /// let dt = NaiveTime::from_hms_nano(23, 56, 4, 12_345_678); - /// assert_eq!(dt.with_nanosecond(333_333_333), - /// Some(NaiveTime::from_hms_nano(23, 56, 4, 333_333_333))); - /// assert_eq!(dt.with_nanosecond(2_000_000_000), None); - /// ~~~~ - /// - /// Leap seconds can theoretically follow *any* whole second. - /// The following would be a proper leap second at the time zone offset of UTC-00:03:57 - /// (there are several historical examples comparable to this "non-sense" offset), - /// and therefore is allowed. - /// - /// ~~~~ - /// # use chrono::{NaiveTime, Timelike}; - /// # let dt = NaiveTime::from_hms_nano(23, 56, 4, 12_345_678); - /// assert_eq!(dt.with_nanosecond(1_333_333_333), - /// Some(NaiveTime::from_hms_nano(23, 56, 4, 1_333_333_333))); - /// ~~~~ - #[inline] - fn with_nanosecond(&self, nano: u32) -> Option { - if nano >= 2_000_000_000 { - return None; - } - Some(NaiveTime { frac: nano, ..*self }) - } - - /// Returns the number of non-leap seconds past the last midnight. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{NaiveTime, Timelike}; - /// - /// assert_eq!(NaiveTime::from_hms(1, 2, 3).num_seconds_from_midnight(), - /// 3723); - /// assert_eq!(NaiveTime::from_hms_nano(23, 56, 4, 12_345_678).num_seconds_from_midnight(), - /// 86164); - /// assert_eq!(NaiveTime::from_hms_milli(23, 59, 59, 1_000).num_seconds_from_midnight(), - /// 86399); - /// ~~~~ - #[inline] - fn num_seconds_from_midnight(&self) -> u32 { - self.secs // do not repeat the calculation! - } -} - -/// `NaiveTime` can be used as a key to the hash maps (in principle). -/// -/// Practically this also takes account of fractional seconds, so it is not recommended. -/// (For the obvious reason this also distinguishes leap seconds from non-leap seconds.) -impl hash::Hash for NaiveTime { - fn hash(&self, state: &mut H) { - self.secs.hash(state); - self.frac.hash(state); - } -} - -/// An addition of `Duration` to `NaiveTime` wraps around and never overflows or underflows. -/// In particular the addition ignores integral number of days. -/// -/// As a part of Chrono's [leap second handling](#leap-second-handling), -/// the addition assumes that **there is no leap second ever**, -/// except when the `NaiveTime` itself represents a leap second -/// in which case the assumption becomes that **there is exactly a single leap second ever**. -/// -/// # Example -/// -/// ~~~~ -/// # extern crate chrono; fn main() { -/// use chrono::{Duration, NaiveTime}; -/// -/// let from_hmsm = NaiveTime::from_hms_milli; -/// -/// assert_eq!(from_hmsm(3, 5, 7, 0) + Duration::zero(), from_hmsm(3, 5, 7, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) + Duration::seconds(1), from_hmsm(3, 5, 8, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) + Duration::seconds(-1), from_hmsm(3, 5, 6, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) + Duration::seconds(60 + 4), from_hmsm(3, 6, 11, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) + Duration::seconds(7*60*60 - 6*60), from_hmsm(9, 59, 7, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) + Duration::milliseconds(80), from_hmsm(3, 5, 7, 80)); -/// assert_eq!(from_hmsm(3, 5, 7, 950) + Duration::milliseconds(280), from_hmsm(3, 5, 8, 230)); -/// assert_eq!(from_hmsm(3, 5, 7, 950) + Duration::milliseconds(-980), from_hmsm(3, 5, 6, 970)); -/// # } -/// ~~~~ -/// -/// The addition wraps around. -/// -/// ~~~~ -/// # extern crate chrono; fn main() { -/// # use chrono::{Duration, NaiveTime}; -/// # let from_hmsm = NaiveTime::from_hms_milli; -/// assert_eq!(from_hmsm(3, 5, 7, 0) + Duration::seconds(22*60*60), from_hmsm(1, 5, 7, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) + Duration::seconds(-8*60*60), from_hmsm(19, 5, 7, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) + Duration::days(800), from_hmsm(3, 5, 7, 0)); -/// # } -/// ~~~~ -/// -/// Leap seconds are handled, but the addition assumes that it is the only leap second happened. -/// -/// ~~~~ -/// # extern crate chrono; fn main() { -/// # use chrono::{Duration, NaiveTime}; -/// # let from_hmsm = NaiveTime::from_hms_milli; -/// let leap = from_hmsm(3, 5, 59, 1_300); -/// assert_eq!(leap + Duration::zero(), from_hmsm(3, 5, 59, 1_300)); -/// assert_eq!(leap + Duration::milliseconds(-500), from_hmsm(3, 5, 59, 800)); -/// assert_eq!(leap + Duration::milliseconds(500), from_hmsm(3, 5, 59, 1_800)); -/// assert_eq!(leap + Duration::milliseconds(800), from_hmsm(3, 6, 0, 100)); -/// assert_eq!(leap + Duration::seconds(10), from_hmsm(3, 6, 9, 300)); -/// assert_eq!(leap + Duration::seconds(-10), from_hmsm(3, 5, 50, 300)); -/// assert_eq!(leap + Duration::days(1), from_hmsm(3, 5, 59, 300)); -/// # } -/// ~~~~ -impl Add for NaiveTime { - type Output = NaiveTime; - - #[inline] - fn add(self, rhs: OldDuration) -> NaiveTime { - self.overflowing_add_signed(rhs).0 - } -} - -impl AddAssign for NaiveTime { - #[inline] - fn add_assign(&mut self, rhs: OldDuration) { - *self = self.add(rhs); - } -} - -/// A subtraction of `Duration` from `NaiveTime` wraps around and never overflows or underflows. -/// In particular the addition ignores integral number of days. -/// It is the same as the addition with a negated `Duration`. -/// -/// As a part of Chrono's [leap second handling](#leap-second-handling), -/// the addition assumes that **there is no leap second ever**, -/// except when the `NaiveTime` itself represents a leap second -/// in which case the assumption becomes that **there is exactly a single leap second ever**. -/// -/// # Example -/// -/// ~~~~ -/// # extern crate chrono; fn main() { -/// use chrono::{Duration, NaiveTime}; -/// -/// let from_hmsm = NaiveTime::from_hms_milli; -/// -/// assert_eq!(from_hmsm(3, 5, 7, 0) - Duration::zero(), from_hmsm(3, 5, 7, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) - Duration::seconds(1), from_hmsm(3, 5, 6, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) - Duration::seconds(60 + 5), from_hmsm(3, 4, 2, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) - Duration::seconds(2*60*60 + 6*60), from_hmsm(0, 59, 7, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) - Duration::milliseconds(80), from_hmsm(3, 5, 6, 920)); -/// assert_eq!(from_hmsm(3, 5, 7, 950) - Duration::milliseconds(280), from_hmsm(3, 5, 7, 670)); -/// # } -/// ~~~~ -/// -/// The subtraction wraps around. -/// -/// ~~~~ -/// # extern crate chrono; fn main() { -/// # use chrono::{Duration, NaiveTime}; -/// # let from_hmsm = NaiveTime::from_hms_milli; -/// assert_eq!(from_hmsm(3, 5, 7, 0) - Duration::seconds(8*60*60), from_hmsm(19, 5, 7, 0)); -/// assert_eq!(from_hmsm(3, 5, 7, 0) - Duration::days(800), from_hmsm(3, 5, 7, 0)); -/// # } -/// ~~~~ -/// -/// Leap seconds are handled, but the subtraction assumes that it is the only leap second happened. -/// -/// ~~~~ -/// # extern crate chrono; fn main() { -/// # use chrono::{Duration, NaiveTime}; -/// # let from_hmsm = NaiveTime::from_hms_milli; -/// let leap = from_hmsm(3, 5, 59, 1_300); -/// assert_eq!(leap - Duration::zero(), from_hmsm(3, 5, 59, 1_300)); -/// assert_eq!(leap - Duration::milliseconds(200), from_hmsm(3, 5, 59, 1_100)); -/// assert_eq!(leap - Duration::milliseconds(500), from_hmsm(3, 5, 59, 800)); -/// assert_eq!(leap - Duration::seconds(60), from_hmsm(3, 5, 0, 300)); -/// assert_eq!(leap - Duration::days(1), from_hmsm(3, 6, 0, 300)); -/// # } -/// ~~~~ -impl Sub for NaiveTime { - type Output = NaiveTime; - - #[inline] - fn sub(self, rhs: OldDuration) -> NaiveTime { - self.overflowing_sub_signed(rhs).0 - } -} - -impl SubAssign for NaiveTime { - #[inline] - fn sub_assign(&mut self, rhs: OldDuration) { - *self = self.sub(rhs); - } -} - -/// Subtracts another `NaiveTime` from the current time. -/// Returns a `Duration` within +/- 1 day. -/// This does not overflow or underflow at all. -/// -/// As a part of Chrono's [leap second handling](#leap-second-handling), -/// the subtraction assumes that **there is no leap second ever**, -/// except when any of the `NaiveTime`s themselves represents a leap second -/// in which case the assumption becomes that -/// **there are exactly one (or two) leap second(s) ever**. -/// -/// The implementation is a wrapper around -/// [`NaiveTime::signed_duration_since`](#method.signed_duration_since). -/// -/// # Example -/// -/// ~~~~ -/// # extern crate chrono; fn main() { -/// use chrono::{Duration, NaiveTime}; -/// -/// let from_hmsm = NaiveTime::from_hms_milli; -/// -/// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(3, 5, 7, 900), Duration::zero()); -/// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(3, 5, 7, 875), Duration::milliseconds(25)); -/// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(3, 5, 6, 925), Duration::milliseconds(975)); -/// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(3, 5, 0, 900), Duration::seconds(7)); -/// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(3, 0, 7, 900), Duration::seconds(5 * 60)); -/// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(0, 5, 7, 900), Duration::seconds(3 * 3600)); -/// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(4, 5, 7, 900), Duration::seconds(-3600)); -/// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(2, 4, 6, 800), -/// Duration::seconds(3600 + 60 + 1) + Duration::milliseconds(100)); -/// # } -/// ~~~~ -/// -/// Leap seconds are handled, but the subtraction assumes that -/// there were no other leap seconds happened. -/// -/// ~~~~ -/// # extern crate chrono; fn main() { -/// # use chrono::{Duration, NaiveTime}; -/// # let from_hmsm = NaiveTime::from_hms_milli; -/// assert_eq!(from_hmsm(3, 0, 59, 1_000) - from_hmsm(3, 0, 59, 0), Duration::seconds(1)); -/// assert_eq!(from_hmsm(3, 0, 59, 1_500) - from_hmsm(3, 0, 59, 0), -/// Duration::milliseconds(1500)); -/// assert_eq!(from_hmsm(3, 0, 59, 1_000) - from_hmsm(3, 0, 0, 0), Duration::seconds(60)); -/// assert_eq!(from_hmsm(3, 0, 0, 0) - from_hmsm(2, 59, 59, 1_000), Duration::seconds(1)); -/// assert_eq!(from_hmsm(3, 0, 59, 1_000) - from_hmsm(2, 59, 59, 1_000), -/// Duration::seconds(61)); -/// # } -/// ~~~~ -impl Sub for NaiveTime { - type Output = OldDuration; - - #[inline] - fn sub(self, rhs: NaiveTime) -> OldDuration { - self.signed_duration_since(rhs) - } -} - -/// The `Debug` output of the naive time `t` is the same as -/// [`t.format("%H:%M:%S%.f")`](../format/strftime/index.html). -/// -/// The string printed can be readily parsed via the `parse` method on `str`. -/// -/// It should be noted that, for leap seconds not on the minute boundary, -/// it may print a representation not distinguishable from non-leap seconds. -/// This doesn't matter in practice, since such leap seconds never happened. -/// (By the time of the first leap second on 1972-06-30, -/// every time zone offset around the world has standardized to the 5-minute alignment.) -/// -/// # Example -/// -/// ~~~~ -/// use chrono::NaiveTime; -/// -/// assert_eq!(format!("{:?}", NaiveTime::from_hms(23, 56, 4)), "23:56:04"); -/// assert_eq!(format!("{:?}", NaiveTime::from_hms_milli(23, 56, 4, 12)), "23:56:04.012"); -/// assert_eq!(format!("{:?}", NaiveTime::from_hms_micro(23, 56, 4, 1234)), "23:56:04.001234"); -/// assert_eq!(format!("{:?}", NaiveTime::from_hms_nano(23, 56, 4, 123456)), "23:56:04.000123456"); -/// ~~~~ -/// -/// Leap seconds may also be used. -/// -/// ~~~~ -/// # use chrono::NaiveTime; -/// assert_eq!(format!("{:?}", NaiveTime::from_hms_milli(6, 59, 59, 1_500)), "06:59:60.500"); -/// ~~~~ -impl fmt::Debug for NaiveTime { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let (hour, min, sec) = self.hms(); - let (sec, nano) = if self.frac >= 1_000_000_000 { - (sec + 1, self.frac - 1_000_000_000) - } else { - (sec, self.frac) - }; - - write!(f, "{:02}:{:02}:{:02}", hour, min, sec)?; - if nano == 0 { - Ok(()) - } else if nano % 1_000_000 == 0 { - write!(f, ".{:03}", nano / 1_000_000) - } else if nano % 1_000 == 0 { - write!(f, ".{:06}", nano / 1_000) - } else { - write!(f, ".{:09}", nano) - } - } -} - -/// The `Display` output of the naive time `t` is the same as -/// [`t.format("%H:%M:%S%.f")`](../format/strftime/index.html). -/// -/// The string printed can be readily parsed via the `parse` method on `str`. -/// -/// It should be noted that, for leap seconds not on the minute boundary, -/// it may print a representation not distinguishable from non-leap seconds. -/// This doesn't matter in practice, since such leap seconds never happened. -/// (By the time of the first leap second on 1972-06-30, -/// every time zone offset around the world has standardized to the 5-minute alignment.) -/// -/// # Example -/// -/// ~~~~ -/// use chrono::NaiveTime; -/// -/// assert_eq!(format!("{}", NaiveTime::from_hms(23, 56, 4)), "23:56:04"); -/// assert_eq!(format!("{}", NaiveTime::from_hms_milli(23, 56, 4, 12)), "23:56:04.012"); -/// assert_eq!(format!("{}", NaiveTime::from_hms_micro(23, 56, 4, 1234)), "23:56:04.001234"); -/// assert_eq!(format!("{}", NaiveTime::from_hms_nano(23, 56, 4, 123456)), "23:56:04.000123456"); -/// ~~~~ -/// -/// Leap seconds may also be used. -/// -/// ~~~~ -/// # use chrono::NaiveTime; -/// assert_eq!(format!("{}", NaiveTime::from_hms_milli(6, 59, 59, 1_500)), "06:59:60.500"); -/// ~~~~ -impl fmt::Display for NaiveTime { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Debug::fmt(self, f) - } -} - -/// Parsing a `str` into a `NaiveTime` uses the same format, -/// [`%H:%M:%S%.f`](../format/strftime/index.html), as in `Debug` and `Display`. -/// -/// # Example -/// -/// ~~~~ -/// use chrono::NaiveTime; -/// -/// let t = NaiveTime::from_hms(23, 56, 4); -/// assert_eq!("23:56:04".parse::(), Ok(t)); -/// -/// let t = NaiveTime::from_hms_nano(23, 56, 4, 12_345_678); -/// assert_eq!("23:56:4.012345678".parse::(), Ok(t)); -/// -/// let t = NaiveTime::from_hms_nano(23, 59, 59, 1_234_567_890); // leap second -/// assert_eq!("23:59:60.23456789".parse::(), Ok(t)); -/// -/// assert!("foo".parse::().is_err()); -/// ~~~~ -impl str::FromStr for NaiveTime { - type Err = ParseError; - - fn from_str(s: &str) -> ParseResult { - const ITEMS: &'static [Item<'static>] = &[ - Item::Numeric(Numeric::Hour, Pad::Zero), - Item::Space(""), - Item::Literal(":"), - Item::Numeric(Numeric::Minute, Pad::Zero), - Item::Space(""), - Item::Literal(":"), - Item::Numeric(Numeric::Second, Pad::Zero), - Item::Fixed(Fixed::Nanosecond), - Item::Space(""), - ]; - - let mut parsed = Parsed::new(); - parse(&mut parsed, s, ITEMS.iter())?; - parsed.to_naive_time() - } -} - -#[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))] -fn test_encodable_json(to_string: F) -where - F: Fn(&NaiveTime) -> Result, - E: ::std::fmt::Debug, -{ - assert_eq!(to_string(&NaiveTime::from_hms(0, 0, 0)).ok(), Some(r#""00:00:00""#.into())); - assert_eq!( - to_string(&NaiveTime::from_hms_milli(0, 0, 0, 950)).ok(), - Some(r#""00:00:00.950""#.into()) - ); - assert_eq!( - to_string(&NaiveTime::from_hms_milli(0, 0, 59, 1_000)).ok(), - Some(r#""00:00:60""#.into()) - ); - assert_eq!(to_string(&NaiveTime::from_hms(0, 1, 2)).ok(), Some(r#""00:01:02""#.into())); - assert_eq!( - to_string(&NaiveTime::from_hms_nano(3, 5, 7, 98765432)).ok(), - Some(r#""03:05:07.098765432""#.into()) - ); - assert_eq!(to_string(&NaiveTime::from_hms(7, 8, 9)).ok(), Some(r#""07:08:09""#.into())); - assert_eq!( - to_string(&NaiveTime::from_hms_micro(12, 34, 56, 789)).ok(), - Some(r#""12:34:56.000789""#.into()) - ); - assert_eq!( - to_string(&NaiveTime::from_hms_nano(23, 59, 59, 1_999_999_999)).ok(), - Some(r#""23:59:60.999999999""#.into()) - ); -} - -#[cfg(all(test, any(feature = "rustc-serialize", feature = "serde")))] -fn test_decodable_json(from_str: F) -where - F: Fn(&str) -> Result, - E: ::std::fmt::Debug, -{ - assert_eq!(from_str(r#""00:00:00""#).ok(), Some(NaiveTime::from_hms(0, 0, 0))); - assert_eq!(from_str(r#""0:0:0""#).ok(), Some(NaiveTime::from_hms(0, 0, 0))); - assert_eq!(from_str(r#""00:00:00.950""#).ok(), Some(NaiveTime::from_hms_milli(0, 0, 0, 950))); - assert_eq!(from_str(r#""0:0:0.95""#).ok(), Some(NaiveTime::from_hms_milli(0, 0, 0, 950))); - assert_eq!(from_str(r#""00:00:60""#).ok(), Some(NaiveTime::from_hms_milli(0, 0, 59, 1_000))); - assert_eq!(from_str(r#""00:01:02""#).ok(), Some(NaiveTime::from_hms(0, 1, 2))); - assert_eq!( - from_str(r#""03:05:07.098765432""#).ok(), - Some(NaiveTime::from_hms_nano(3, 5, 7, 98765432)) - ); - assert_eq!(from_str(r#""07:08:09""#).ok(), Some(NaiveTime::from_hms(7, 8, 9))); - assert_eq!( - from_str(r#""12:34:56.000789""#).ok(), - Some(NaiveTime::from_hms_micro(12, 34, 56, 789)) - ); - assert_eq!( - from_str(r#""23:59:60.999999999""#).ok(), - Some(NaiveTime::from_hms_nano(23, 59, 59, 1_999_999_999)) - ); - assert_eq!( - from_str(r#""23:59:60.9999999999997""#).ok(), // excess digits are ignored - Some(NaiveTime::from_hms_nano(23, 59, 59, 1_999_999_999)) - ); - - // bad formats - assert!(from_str(r#""""#).is_err()); - assert!(from_str(r#""000000""#).is_err()); - assert!(from_str(r#""00:00:61""#).is_err()); - assert!(from_str(r#""00:60:00""#).is_err()); - assert!(from_str(r#""24:00:00""#).is_err()); - assert!(from_str(r#""23:59:59,1""#).is_err()); - assert!(from_str(r#""012:34:56""#).is_err()); - assert!(from_str(r#""hh:mm:ss""#).is_err()); - assert!(from_str(r#"0"#).is_err()); - assert!(from_str(r#"86399"#).is_err()); - assert!(from_str(r#"{}"#).is_err()); - // pre-0.3.0 rustc-serialize format is now invalid - assert!(from_str(r#"{"secs":0,"frac":0}"#).is_err()); - assert!(from_str(r#"null"#).is_err()); -} - -#[cfg(feature = "rustc-serialize")] -mod rustc_serialize { - use super::NaiveTime; - use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; - - impl Encodable for NaiveTime { - fn encode(&self, s: &mut S) -> Result<(), S::Error> { - format!("{:?}", self).encode(s) - } - } - - impl Decodable for NaiveTime { - fn decode(d: &mut D) -> Result { - d.read_str()?.parse().map_err(|_| d.error("invalid time")) - } - } - - #[cfg(test)] - use rustc_serialize::json; - - #[test] - fn test_encodable() { - super::test_encodable_json(json::encode); - } - - #[test] - fn test_decodable() { - super::test_decodable_json(json::decode); - } -} - -#[cfg(feature = "serde")] -mod serde { - use super::NaiveTime; - use core::fmt; - use serdelib::{de, ser}; - - // TODO not very optimized for space (binary formats would want something better) - // TODO round-trip for general leap seconds (not just those with second = 60) - - impl ser::Serialize for NaiveTime { - fn serialize(&self, serializer: S) -> Result - where - S: ser::Serializer, - { - serializer.collect_str(&self) - } - } - - struct NaiveTimeVisitor; - - impl<'de> de::Visitor<'de> for NaiveTimeVisitor { - type Value = NaiveTime; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - write!(formatter, "a formatted time string") - } - - fn visit_str(self, value: &str) -> Result - where - E: de::Error, - { - value.parse().map_err(E::custom) - } - } - - impl<'de> de::Deserialize<'de> for NaiveTime { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - deserializer.deserialize_str(NaiveTimeVisitor) - } - } - - #[cfg(test)] - extern crate bincode; - #[cfg(test)] - extern crate serde_json; - - #[test] - fn test_serde_serialize() { - super::test_encodable_json(self::serde_json::to_string); - } - - #[test] - fn test_serde_deserialize() { - super::test_decodable_json(|input| self::serde_json::from_str(&input)); - } - - #[test] - fn test_serde_bincode() { - // Bincode is relevant to test separately from JSON because - // it is not self-describing. - use self::bincode::{deserialize, serialize, Infinite}; - - let t = NaiveTime::from_hms_nano(3, 5, 7, 98765432); - let encoded = serialize(&t, Infinite).unwrap(); - let decoded: NaiveTime = deserialize(&encoded).unwrap(); - assert_eq!(t, decoded); - } -} - -#[cfg(test)] -mod tests { - use super::NaiveTime; - use oldtime::Duration; - use std::u32; - use Timelike; - - #[test] - fn test_time_from_hms_milli() { - assert_eq!( - NaiveTime::from_hms_milli_opt(3, 5, 7, 0), - Some(NaiveTime::from_hms_nano(3, 5, 7, 0)) - ); - assert_eq!( - NaiveTime::from_hms_milli_opt(3, 5, 7, 777), - Some(NaiveTime::from_hms_nano(3, 5, 7, 777_000_000)) - ); - assert_eq!( - NaiveTime::from_hms_milli_opt(3, 5, 7, 1_999), - Some(NaiveTime::from_hms_nano(3, 5, 7, 1_999_000_000)) - ); - assert_eq!(NaiveTime::from_hms_milli_opt(3, 5, 7, 2_000), None); - assert_eq!(NaiveTime::from_hms_milli_opt(3, 5, 7, 5_000), None); // overflow check - assert_eq!(NaiveTime::from_hms_milli_opt(3, 5, 7, u32::MAX), None); - } - - #[test] - fn test_time_from_hms_micro() { - assert_eq!( - NaiveTime::from_hms_micro_opt(3, 5, 7, 0), - Some(NaiveTime::from_hms_nano(3, 5, 7, 0)) - ); - assert_eq!( - NaiveTime::from_hms_micro_opt(3, 5, 7, 333), - Some(NaiveTime::from_hms_nano(3, 5, 7, 333_000)) - ); - assert_eq!( - NaiveTime::from_hms_micro_opt(3, 5, 7, 777_777), - Some(NaiveTime::from_hms_nano(3, 5, 7, 777_777_000)) - ); - assert_eq!( - NaiveTime::from_hms_micro_opt(3, 5, 7, 1_999_999), - Some(NaiveTime::from_hms_nano(3, 5, 7, 1_999_999_000)) - ); - assert_eq!(NaiveTime::from_hms_micro_opt(3, 5, 7, 2_000_000), None); - assert_eq!(NaiveTime::from_hms_micro_opt(3, 5, 7, 5_000_000), None); // overflow check - assert_eq!(NaiveTime::from_hms_micro_opt(3, 5, 7, u32::MAX), None); - } - - #[test] - fn test_time_hms() { - assert_eq!(NaiveTime::from_hms(3, 5, 7).hour(), 3); - assert_eq!(NaiveTime::from_hms(3, 5, 7).with_hour(0), Some(NaiveTime::from_hms(0, 5, 7))); - assert_eq!(NaiveTime::from_hms(3, 5, 7).with_hour(23), Some(NaiveTime::from_hms(23, 5, 7))); - assert_eq!(NaiveTime::from_hms(3, 5, 7).with_hour(24), None); - assert_eq!(NaiveTime::from_hms(3, 5, 7).with_hour(u32::MAX), None); - - assert_eq!(NaiveTime::from_hms(3, 5, 7).minute(), 5); - assert_eq!(NaiveTime::from_hms(3, 5, 7).with_minute(0), Some(NaiveTime::from_hms(3, 0, 7))); - assert_eq!( - NaiveTime::from_hms(3, 5, 7).with_minute(59), - Some(NaiveTime::from_hms(3, 59, 7)) - ); - assert_eq!(NaiveTime::from_hms(3, 5, 7).with_minute(60), None); - assert_eq!(NaiveTime::from_hms(3, 5, 7).with_minute(u32::MAX), None); - - assert_eq!(NaiveTime::from_hms(3, 5, 7).second(), 7); - assert_eq!(NaiveTime::from_hms(3, 5, 7).with_second(0), Some(NaiveTime::from_hms(3, 5, 0))); - assert_eq!( - NaiveTime::from_hms(3, 5, 7).with_second(59), - Some(NaiveTime::from_hms(3, 5, 59)) - ); - assert_eq!(NaiveTime::from_hms(3, 5, 7).with_second(60), None); - assert_eq!(NaiveTime::from_hms(3, 5, 7).with_second(u32::MAX), None); - } - - #[test] - fn test_time_add() { - macro_rules! check { - ($lhs:expr, $rhs:expr, $sum:expr) => {{ - assert_eq!($lhs + $rhs, $sum); - //assert_eq!($rhs + $lhs, $sum); - }}; - } - - let hmsm = |h, m, s, mi| NaiveTime::from_hms_milli(h, m, s, mi); - - check!(hmsm(3, 5, 7, 900), Duration::zero(), hmsm(3, 5, 7, 900)); - check!(hmsm(3, 5, 7, 900), Duration::milliseconds(100), hmsm(3, 5, 8, 0)); - check!(hmsm(3, 5, 7, 1_300), Duration::milliseconds(-1800), hmsm(3, 5, 6, 500)); - check!(hmsm(3, 5, 7, 1_300), Duration::milliseconds(-800), hmsm(3, 5, 7, 500)); - check!(hmsm(3, 5, 7, 1_300), Duration::milliseconds(-100), hmsm(3, 5, 7, 1_200)); - check!(hmsm(3, 5, 7, 1_300), Duration::milliseconds(100), hmsm(3, 5, 7, 1_400)); - check!(hmsm(3, 5, 7, 1_300), Duration::milliseconds(800), hmsm(3, 5, 8, 100)); - check!(hmsm(3, 5, 7, 1_300), Duration::milliseconds(1800), hmsm(3, 5, 9, 100)); - check!(hmsm(3, 5, 7, 900), Duration::seconds(86399), hmsm(3, 5, 6, 900)); // overwrap - check!(hmsm(3, 5, 7, 900), Duration::seconds(-86399), hmsm(3, 5, 8, 900)); - check!(hmsm(3, 5, 7, 900), Duration::days(12345), hmsm(3, 5, 7, 900)); - check!(hmsm(3, 5, 7, 1_300), Duration::days(1), hmsm(3, 5, 7, 300)); - check!(hmsm(3, 5, 7, 1_300), Duration::days(-1), hmsm(3, 5, 8, 300)); - - // regression tests for #37 - check!(hmsm(0, 0, 0, 0), Duration::milliseconds(-990), hmsm(23, 59, 59, 10)); - check!(hmsm(0, 0, 0, 0), Duration::milliseconds(-9990), hmsm(23, 59, 50, 10)); - } - - #[test] - fn test_time_overflowing_add() { - let hmsm = NaiveTime::from_hms_milli; - - assert_eq!( - hmsm(3, 4, 5, 678).overflowing_add_signed(Duration::hours(11)), - (hmsm(14, 4, 5, 678), 0) - ); - assert_eq!( - hmsm(3, 4, 5, 678).overflowing_add_signed(Duration::hours(23)), - (hmsm(2, 4, 5, 678), 86_400) - ); - assert_eq!( - hmsm(3, 4, 5, 678).overflowing_add_signed(Duration::hours(-7)), - (hmsm(20, 4, 5, 678), -86_400) - ); - - // overflowing_add_signed with leap seconds may be counter-intuitive - assert_eq!( - hmsm(3, 4, 5, 1_678).overflowing_add_signed(Duration::days(1)), - (hmsm(3, 4, 5, 678), 86_400) - ); - assert_eq!( - hmsm(3, 4, 5, 1_678).overflowing_add_signed(Duration::days(-1)), - (hmsm(3, 4, 6, 678), -86_400) - ); - } - - #[test] - fn test_time_addassignment() { - let hms = NaiveTime::from_hms; - let mut time = hms(12, 12, 12); - time += Duration::hours(10); - assert_eq!(time, hms(22, 12, 12)); - time += Duration::hours(10); - assert_eq!(time, hms(8, 12, 12)); - } - - #[test] - fn test_time_subassignment() { - let hms = NaiveTime::from_hms; - let mut time = hms(12, 12, 12); - time -= Duration::hours(10); - assert_eq!(time, hms(2, 12, 12)); - time -= Duration::hours(10); - assert_eq!(time, hms(16, 12, 12)); - } - - #[test] - fn test_time_sub() { - macro_rules! check { - ($lhs:expr, $rhs:expr, $diff:expr) => {{ - // `time1 - time2 = duration` is equivalent to `time2 - time1 = -duration` - assert_eq!($lhs.signed_duration_since($rhs), $diff); - assert_eq!($rhs.signed_duration_since($lhs), -$diff); - }}; - } - - let hmsm = |h, m, s, mi| NaiveTime::from_hms_milli(h, m, s, mi); - - check!(hmsm(3, 5, 7, 900), hmsm(3, 5, 7, 900), Duration::zero()); - check!(hmsm(3, 5, 7, 900), hmsm(3, 5, 7, 600), Duration::milliseconds(300)); - check!(hmsm(3, 5, 7, 200), hmsm(2, 4, 6, 200), Duration::seconds(3600 + 60 + 1)); - check!( - hmsm(3, 5, 7, 200), - hmsm(2, 4, 6, 300), - Duration::seconds(3600 + 60) + Duration::milliseconds(900) - ); - - // treats the leap second as if it coincides with the prior non-leap second, - // as required by `time1 - time2 = duration` and `time2 - time1 = -duration` equivalence. - check!(hmsm(3, 5, 7, 200), hmsm(3, 5, 6, 1_800), Duration::milliseconds(400)); - check!(hmsm(3, 5, 7, 1_200), hmsm(3, 5, 6, 1_800), Duration::milliseconds(1400)); - check!(hmsm(3, 5, 7, 1_200), hmsm(3, 5, 6, 800), Duration::milliseconds(1400)); - - // additional equality: `time1 + duration = time2` is equivalent to - // `time2 - time1 = duration` IF AND ONLY IF `time2` represents a non-leap second. - assert_eq!(hmsm(3, 5, 6, 800) + Duration::milliseconds(400), hmsm(3, 5, 7, 200)); - assert_eq!(hmsm(3, 5, 6, 1_800) + Duration::milliseconds(400), hmsm(3, 5, 7, 200)); - } - - #[test] - fn test_time_fmt() { - assert_eq!(format!("{}", NaiveTime::from_hms_milli(23, 59, 59, 999)), "23:59:59.999"); - assert_eq!(format!("{}", NaiveTime::from_hms_milli(23, 59, 59, 1_000)), "23:59:60"); - assert_eq!(format!("{}", NaiveTime::from_hms_milli(23, 59, 59, 1_001)), "23:59:60.001"); - assert_eq!(format!("{}", NaiveTime::from_hms_micro(0, 0, 0, 43210)), "00:00:00.043210"); - assert_eq!(format!("{}", NaiveTime::from_hms_nano(0, 0, 0, 6543210)), "00:00:00.006543210"); - - // the format specifier should have no effect on `NaiveTime` - assert_eq!(format!("{:30}", NaiveTime::from_hms_milli(3, 5, 7, 9)), "03:05:07.009"); - } - - #[test] - fn test_date_from_str() { - // valid cases - let valid = [ - "0:0:0", - "0:0:0.0000000", - "0:0:0.0000003", - " 4 : 3 : 2.1 ", - " 09:08:07 ", - " 9:8:07 ", - "23:59:60.373929310237", - ]; - for &s in &valid { - let d = match s.parse::() { - Ok(d) => d, - Err(e) => panic!("parsing `{}` has failed: {}", s, e), - }; - let s_ = format!("{:?}", d); - // `s` and `s_` may differ, but `s.parse()` and `s_.parse()` must be same - let d_ = match s_.parse::() { - Ok(d) => d, - Err(e) => { - panic!("`{}` is parsed into `{:?}`, but reparsing that has failed: {}", s, d, e) - } - }; - assert!( - d == d_, - "`{}` is parsed into `{:?}`, but reparsed result \ - `{:?}` does not match", - s, - d, - d_ - ); - } - - // some invalid cases - // since `ParseErrorKind` is private, all we can do is to check if there was an error - assert!("".parse::().is_err()); - assert!("x".parse::().is_err()); - assert!("15".parse::().is_err()); - assert!("15:8".parse::().is_err()); - assert!("15:8:x".parse::().is_err()); - assert!("15:8:9x".parse::().is_err()); - assert!("23:59:61".parse::().is_err()); - assert!("12:34:56.x".parse::().is_err()); - assert!("12:34:56. 0".parse::().is_err()); - } - - #[test] - fn test_time_parse_from_str() { - let hms = |h, m, s| NaiveTime::from_hms(h, m, s); - assert_eq!( - NaiveTime::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), - Ok(hms(12, 34, 56)) - ); // ignore date and offset - assert_eq!(NaiveTime::parse_from_str("PM 12:59", "%P %H:%M"), Ok(hms(12, 59, 0))); - assert!(NaiveTime::parse_from_str("12:3456", "%H:%M:%S").is_err()); - } - - #[test] - fn test_time_format() { - let t = NaiveTime::from_hms_nano(3, 5, 7, 98765432); - assert_eq!(t.format("%H,%k,%I,%l,%P,%p").to_string(), "03, 3,03, 3,am,AM"); - assert_eq!(t.format("%M").to_string(), "05"); - assert_eq!(t.format("%S,%f,%.f").to_string(), "07,098765432,.098765432"); - assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".098,.098765,.098765432"); - assert_eq!(t.format("%R").to_string(), "03:05"); - assert_eq!(t.format("%T,%X").to_string(), "03:05:07,03:05:07"); - assert_eq!(t.format("%r").to_string(), "03:05:07 AM"); - assert_eq!(t.format("%t%n%%%n%t").to_string(), "\t\n%\n\t"); - - let t = NaiveTime::from_hms_micro(3, 5, 7, 432100); - assert_eq!(t.format("%S,%f,%.f").to_string(), "07,432100000,.432100"); - assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".432,.432100,.432100000"); - - let t = NaiveTime::from_hms_milli(3, 5, 7, 210); - assert_eq!(t.format("%S,%f,%.f").to_string(), "07,210000000,.210"); - assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".210,.210000,.210000000"); - - let t = NaiveTime::from_hms(3, 5, 7); - assert_eq!(t.format("%S,%f,%.f").to_string(), "07,000000000,"); - assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".000,.000000,.000000000"); - - // corner cases - assert_eq!(NaiveTime::from_hms(13, 57, 9).format("%r").to_string(), "01:57:09 PM"); - assert_eq!( - NaiveTime::from_hms_milli(23, 59, 59, 1_000).format("%X").to_string(), - "23:59:60" - ); - } -} diff --git a/third_party/rust/chrono/src/naive/time/mod.rs b/third_party/rust/chrono/src/naive/time/mod.rs new file mode 100644 index 00000000000..a0b92836f1b --- /dev/null +++ b/third_party/rust/chrono/src/naive/time/mod.rs @@ -0,0 +1,1643 @@ +// This is a part of Chrono. +// See README.md and LICENSE.txt for details. + +//! ISO 8601 time without timezone. + +#[cfg(feature = "alloc")] +use core::borrow::Borrow; +use core::ops::{Add, AddAssign, Sub, SubAssign}; +use core::time::Duration; +use core::{fmt, str}; + +#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] +use rkyv::{Archive, Deserialize, Serialize}; + +#[cfg(feature = "alloc")] +use crate::format::DelayedFormat; +use crate::format::{ + Fixed, Item, Numeric, Pad, ParseError, ParseResult, Parsed, StrftimeItems, parse, + parse_and_remainder, write_hundreds, +}; +use crate::{FixedOffset, TimeDelta, Timelike}; +use crate::{expect, try_opt}; + +#[cfg(feature = "serde")] +mod serde; + +#[cfg(test)] +mod tests; + +/// ISO 8601 time without timezone. +/// Allows for the nanosecond precision and optional leap second representation. +/// +/// # Leap Second Handling +/// +/// Since 1960s, the manmade atomic clock has been so accurate that +/// it is much more accurate than Earth's own motion. +/// It became desirable to define the civil time in terms of the atomic clock, +/// but that risks the desynchronization of the civil time from Earth. +/// To account for this, the designers of the Coordinated Universal Time (UTC) +/// made that the UTC should be kept within 0.9 seconds of the observed Earth-bound time. +/// When the mean solar day is longer than the ideal (86,400 seconds), +/// the error slowly accumulates and it is necessary to add a **leap second** +/// to slow the UTC down a bit. +/// (We may also remove a second to speed the UTC up a bit, but it never happened.) +/// The leap second, if any, follows 23:59:59 of June 30 or December 31 in the UTC. +/// +/// Fast forward to the 21st century, +/// we have seen 26 leap seconds from January 1972 to December 2015. +/// Yes, 26 seconds. Probably you can read this paragraph within 26 seconds. +/// But those 26 seconds, and possibly more in the future, are never predictable, +/// and whether to add a leap second or not is known only before 6 months. +/// Internet-based clocks (via NTP) do account for known leap seconds, +/// but the system API normally doesn't (and often can't, with no network connection) +/// and there is no reliable way to retrieve leap second information. +/// +/// Chrono does not try to accurately implement leap seconds; it is impossible. +/// Rather, **it allows for leap seconds but behaves as if there are *no other* leap seconds.** +/// Various operations will ignore any possible leap second(s) +/// except when any of the operands were actually leap seconds. +/// +/// If you cannot tolerate this behavior, +/// you must use a separate `TimeZone` for the International Atomic Time (TAI). +/// TAI is like UTC but has no leap seconds, and thus slightly differs from UTC. +/// Chrono does not yet provide such implementation, but it is planned. +/// +/// ## Representing Leap Seconds +/// +/// The leap second is indicated via fractional seconds more than 1 second. +/// This makes possible to treat a leap second as the prior non-leap second +/// if you don't care about sub-second accuracy. +/// You should use the proper formatting to get the raw leap second. +/// +/// All methods accepting fractional seconds will accept such values. +/// +/// ``` +/// use chrono::{NaiveDate, NaiveTime}; +/// +/// let t = NaiveTime::from_hms_milli_opt(8, 59, 59, 1_000).unwrap(); +/// +/// let dt1 = NaiveDate::from_ymd_opt(2015, 7, 1) +/// .unwrap() +/// .and_hms_micro_opt(8, 59, 59, 1_000_000) +/// .unwrap(); +/// +/// let dt2 = NaiveDate::from_ymd_opt(2015, 6, 30) +/// .unwrap() +/// .and_hms_nano_opt(23, 59, 59, 1_000_000_000) +/// .unwrap() +/// .and_utc(); +/// # let _ = (t, dt1, dt2); +/// ``` +/// +/// Note that the leap second can happen anytime given an appropriate time zone; +/// 2015-07-01 01:23:60 would be a proper leap second if UTC+01:24 had existed. +/// Practically speaking, though, by the time of the first leap second on 1972-06-30, +/// every time zone offset around the world has standardized to the 5-minute alignment. +/// +/// ## Date And Time Arithmetics +/// +/// As a concrete example, let's assume that `03:00:60` and `04:00:60` are leap seconds. +/// In reality, of course, leap seconds are separated by at least 6 months. +/// We will also use some intuitive concise notations for the explanation. +/// +/// `Time + TimeDelta` +/// (short for [`NaiveTime::overflowing_add_signed`](#method.overflowing_add_signed)): +/// +/// - `03:00:00 + 1s = 03:00:01`. +/// - `03:00:59 + 60s = 03:01:59`. +/// - `03:00:59 + 61s = 03:02:00`. +/// - `03:00:59 + 1s = 03:01:00`. +/// - `03:00:60 + 1s = 03:01:00`. +/// Note that the sum is identical to the previous. +/// - `03:00:60 + 60s = 03:01:59`. +/// - `03:00:60 + 61s = 03:02:00`. +/// - `03:00:60.1 + 0.8s = 03:00:60.9`. +/// +/// `Time - TimeDelta` +/// (short for [`NaiveTime::overflowing_sub_signed`](#method.overflowing_sub_signed)): +/// +/// - `03:00:00 - 1s = 02:59:59`. +/// - `03:01:00 - 1s = 03:00:59`. +/// - `03:01:00 - 60s = 03:00:00`. +/// - `03:00:60 - 60s = 03:00:00`. +/// Note that the result is identical to the previous. +/// - `03:00:60.7 - 0.4s = 03:00:60.3`. +/// - `03:00:60.7 - 0.9s = 03:00:59.8`. +/// +/// `Time - Time` +/// (short for [`NaiveTime::signed_duration_since`](#method.signed_duration_since)): +/// +/// - `04:00:00 - 03:00:00 = 3600s`. +/// - `03:01:00 - 03:00:00 = 60s`. +/// - `03:00:60 - 03:00:00 = 60s`. +/// Note that the difference is identical to the previous. +/// - `03:00:60.6 - 03:00:59.4 = 1.2s`. +/// - `03:01:00 - 03:00:59.8 = 0.2s`. +/// - `03:01:00 - 03:00:60.5 = 0.5s`. +/// Note that the difference is larger than the previous, +/// even though the leap second clearly follows the previous whole second. +/// - `04:00:60.9 - 03:00:60.1 = +/// (04:00:60.9 - 04:00:00) + (04:00:00 - 03:01:00) + (03:01:00 - 03:00:60.1) = +/// 60.9s + 3540s + 0.9s = 3601.8s`. +/// +/// In general, +/// +/// - `Time + TimeDelta` unconditionally equals to `TimeDelta + Time`. +/// +/// - `Time - TimeDelta` unconditionally equals to `Time + (-TimeDelta)`. +/// +/// - `Time1 - Time2` unconditionally equals to `-(Time2 - Time1)`. +/// +/// - Associativity does not generally hold, because +/// `(Time + TimeDelta1) - TimeDelta2` no longer equals to `Time + (TimeDelta1 - TimeDelta2)` +/// for two positive durations. +/// +/// - As a special case, `(Time + TimeDelta) - TimeDelta` also does not equal to `Time`. +/// +/// - If you can assume that all durations have the same sign, however, +/// then the associativity holds: +/// `(Time + TimeDelta1) + TimeDelta2` equals to `Time + (TimeDelta1 + TimeDelta2)` +/// for two positive durations. +/// +/// ## Reading And Writing Leap Seconds +/// +/// The "typical" leap seconds on the minute boundary are +/// correctly handled both in the formatting and parsing. +/// The leap second in the human-readable representation +/// will be represented as the second part being 60, as required by ISO 8601. +/// +/// ``` +/// use chrono::NaiveDate; +/// +/// let dt = NaiveDate::from_ymd_opt(2015, 6, 30) +/// .unwrap() +/// .and_hms_milli_opt(23, 59, 59, 1_000) +/// .unwrap() +/// .and_utc(); +/// assert_eq!(format!("{:?}", dt), "2015-06-30T23:59:60Z"); +/// ``` +/// +/// There are hypothetical leap seconds not on the minute boundary nevertheless supported by Chrono. +/// They are allowed for the sake of completeness and consistency; there were several "exotic" time +/// zone offsets with fractional minutes prior to UTC after all. +/// For such cases the human-readable representation is ambiguous and would be read back to the next +/// non-leap second. +/// +/// A `NaiveTime` with a leap second that is not on a minute boundary can only be created from a +/// [`DateTime`](crate::DateTime) with fractional minutes as offset, or using +/// [`Timelike::with_nanosecond()`]. +/// +/// ``` +/// use chrono::{FixedOffset, NaiveDate, TimeZone}; +/// +/// let paramaribo_pre1945 = FixedOffset::east_opt(-13236).unwrap(); // -03:40:36 +/// let leap_sec_2015 = +/// NaiveDate::from_ymd_opt(2015, 6, 30).unwrap().and_hms_milli_opt(23, 59, 59, 1_000).unwrap(); +/// let dt1 = paramaribo_pre1945.from_utc_datetime(&leap_sec_2015); +/// assert_eq!(format!("{:?}", dt1), "2015-06-30T20:19:24-03:40:36"); +/// assert_eq!(format!("{:?}", dt1.time()), "20:19:24"); +/// +/// let next_sec = NaiveDate::from_ymd_opt(2015, 7, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); +/// let dt2 = paramaribo_pre1945.from_utc_datetime(&next_sec); +/// assert_eq!(format!("{:?}", dt2), "2015-06-30T20:19:24-03:40:36"); +/// assert_eq!(format!("{:?}", dt2.time()), "20:19:24"); +/// +/// assert!(dt1.time() != dt2.time()); +/// assert!(dt1.time().to_string() == dt2.time().to_string()); +/// ``` +/// +/// Since Chrono alone cannot determine any existence of leap seconds, +/// **there is absolutely no guarantee that the leap second read has actually happened**. +#[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)] +#[cfg_attr( + any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), + derive(Archive, Deserialize, Serialize), + archive(compare(PartialEq, PartialOrd)), + archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) +)] +#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] +pub struct NaiveTime { + secs: u32, + frac: u32, +} + +#[cfg(feature = "arbitrary")] +impl arbitrary::Arbitrary<'_> for NaiveTime { + fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result { + let mins = u.int_in_range(0..=1439)?; + let mut secs = u.int_in_range(0..=60)?; + let mut nano = u.int_in_range(0..=999_999_999)?; + if secs == 60 { + secs = 59; + nano += 1_000_000_000; + } + let time = NaiveTime::from_num_seconds_from_midnight_opt(mins * 60 + secs, nano) + .expect("Could not generate a valid chrono::NaiveTime. It looks like implementation of Arbitrary for NaiveTime is erroneous."); + Ok(time) + } +} + +impl NaiveTime { + /// Makes a new `NaiveTime` from hour, minute and second. + /// + /// No [leap second](#leap-second-handling) is allowed here; + /// use `NaiveTime::from_hms_*` methods with a subsecond parameter instead. + /// + /// # Panics + /// + /// Panics on invalid hour, minute and/or second. + #[deprecated(since = "0.4.23", note = "use `from_hms_opt()` instead")] + #[inline] + #[must_use] + pub const fn from_hms(hour: u32, min: u32, sec: u32) -> NaiveTime { + expect(NaiveTime::from_hms_opt(hour, min, sec), "invalid time") + } + + /// Makes a new `NaiveTime` from hour, minute and second. + /// + /// The millisecond part is allowed to exceed 1,000,000,000 in order to represent a + /// [leap second](#leap-second-handling), but only when `sec == 59`. + /// + /// # Errors + /// + /// Returns `None` on invalid hour, minute and/or second. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveTime; + /// + /// let from_hms_opt = NaiveTime::from_hms_opt; + /// + /// assert!(from_hms_opt(0, 0, 0).is_some()); + /// assert!(from_hms_opt(23, 59, 59).is_some()); + /// assert!(from_hms_opt(24, 0, 0).is_none()); + /// assert!(from_hms_opt(23, 60, 0).is_none()); + /// assert!(from_hms_opt(23, 59, 60).is_none()); + /// ``` + #[inline] + #[must_use] + pub const fn from_hms_opt(hour: u32, min: u32, sec: u32) -> Option { + NaiveTime::from_hms_nano_opt(hour, min, sec, 0) + } + + /// Makes a new `NaiveTime` from hour, minute, second and millisecond. + /// + /// The millisecond part can exceed 1,000 + /// in order to represent the [leap second](#leap-second-handling). + /// + /// # Panics + /// + /// Panics on invalid hour, minute, second and/or millisecond. + #[deprecated(since = "0.4.23", note = "use `from_hms_milli_opt()` instead")] + #[inline] + #[must_use] + pub const fn from_hms_milli(hour: u32, min: u32, sec: u32, milli: u32) -> NaiveTime { + expect(NaiveTime::from_hms_milli_opt(hour, min, sec, milli), "invalid time") + } + + /// Makes a new `NaiveTime` from hour, minute, second and millisecond. + /// + /// The millisecond part is allowed to exceed 1,000,000,000 in order to represent a + /// [leap second](#leap-second-handling), but only when `sec == 59`. + /// + /// # Errors + /// + /// Returns `None` on invalid hour, minute, second and/or millisecond. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveTime; + /// + /// let from_hmsm_opt = NaiveTime::from_hms_milli_opt; + /// + /// assert!(from_hmsm_opt(0, 0, 0, 0).is_some()); + /// assert!(from_hmsm_opt(23, 59, 59, 999).is_some()); + /// assert!(from_hmsm_opt(23, 59, 59, 1_999).is_some()); // a leap second after 23:59:59 + /// assert!(from_hmsm_opt(24, 0, 0, 0).is_none()); + /// assert!(from_hmsm_opt(23, 60, 0, 0).is_none()); + /// assert!(from_hmsm_opt(23, 59, 60, 0).is_none()); + /// assert!(from_hmsm_opt(23, 59, 59, 2_000).is_none()); + /// ``` + #[inline] + #[must_use] + pub const fn from_hms_milli_opt( + hour: u32, + min: u32, + sec: u32, + milli: u32, + ) -> Option { + let nano = try_opt!(milli.checked_mul(1_000_000)); + NaiveTime::from_hms_nano_opt(hour, min, sec, nano) + } + + /// Makes a new `NaiveTime` from hour, minute, second and microsecond. + /// + /// The microsecond part is allowed to exceed 1,000,000,000 in order to represent a + /// [leap second](#leap-second-handling), but only when `sec == 59`. + /// + /// # Panics + /// + /// Panics on invalid hour, minute, second and/or microsecond. + #[deprecated(since = "0.4.23", note = "use `from_hms_micro_opt()` instead")] + #[inline] + #[must_use] + pub const fn from_hms_micro(hour: u32, min: u32, sec: u32, micro: u32) -> NaiveTime { + expect(NaiveTime::from_hms_micro_opt(hour, min, sec, micro), "invalid time") + } + + /// Makes a new `NaiveTime` from hour, minute, second and microsecond. + /// + /// The microsecond part is allowed to exceed 1,000,000,000 in order to represent a + /// [leap second](#leap-second-handling), but only when `sec == 59`. + /// + /// # Errors + /// + /// Returns `None` on invalid hour, minute, second and/or microsecond. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveTime; + /// + /// let from_hmsu_opt = NaiveTime::from_hms_micro_opt; + /// + /// assert!(from_hmsu_opt(0, 0, 0, 0).is_some()); + /// assert!(from_hmsu_opt(23, 59, 59, 999_999).is_some()); + /// assert!(from_hmsu_opt(23, 59, 59, 1_999_999).is_some()); // a leap second after 23:59:59 + /// assert!(from_hmsu_opt(24, 0, 0, 0).is_none()); + /// assert!(from_hmsu_opt(23, 60, 0, 0).is_none()); + /// assert!(from_hmsu_opt(23, 59, 60, 0).is_none()); + /// assert!(from_hmsu_opt(23, 59, 59, 2_000_000).is_none()); + /// ``` + #[inline] + #[must_use] + pub const fn from_hms_micro_opt( + hour: u32, + min: u32, + sec: u32, + micro: u32, + ) -> Option { + let nano = try_opt!(micro.checked_mul(1_000)); + NaiveTime::from_hms_nano_opt(hour, min, sec, nano) + } + + /// Makes a new `NaiveTime` from hour, minute, second and nanosecond. + /// + /// The nanosecond part is allowed to exceed 1,000,000,000 in order to represent a + /// [leap second](#leap-second-handling), but only when `sec == 59`. + /// + /// # Panics + /// + /// Panics on invalid hour, minute, second and/or nanosecond. + #[deprecated(since = "0.4.23", note = "use `from_hms_nano_opt()` instead")] + #[inline] + #[must_use] + pub const fn from_hms_nano(hour: u32, min: u32, sec: u32, nano: u32) -> NaiveTime { + expect(NaiveTime::from_hms_nano_opt(hour, min, sec, nano), "invalid time") + } + + /// Makes a new `NaiveTime` from hour, minute, second and nanosecond. + /// + /// The nanosecond part is allowed to exceed 1,000,000,000 in order to represent a + /// [leap second](#leap-second-handling), but only when `sec == 59`. + /// + /// # Errors + /// + /// Returns `None` on invalid hour, minute, second and/or nanosecond. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveTime; + /// + /// let from_hmsn_opt = NaiveTime::from_hms_nano_opt; + /// + /// assert!(from_hmsn_opt(0, 0, 0, 0).is_some()); + /// assert!(from_hmsn_opt(23, 59, 59, 999_999_999).is_some()); + /// assert!(from_hmsn_opt(23, 59, 59, 1_999_999_999).is_some()); // a leap second after 23:59:59 + /// assert!(from_hmsn_opt(24, 0, 0, 0).is_none()); + /// assert!(from_hmsn_opt(23, 60, 0, 0).is_none()); + /// assert!(from_hmsn_opt(23, 59, 60, 0).is_none()); + /// assert!(from_hmsn_opt(23, 59, 59, 2_000_000_000).is_none()); + /// ``` + #[inline] + #[must_use] + pub const fn from_hms_nano_opt(hour: u32, min: u32, sec: u32, nano: u32) -> Option { + if (hour >= 24 || min >= 60 || sec >= 60) + || (nano >= 1_000_000_000 && sec != 59) + || nano >= 2_000_000_000 + { + return None; + } + let secs = hour * 3600 + min * 60 + sec; + Some(NaiveTime { secs, frac: nano }) + } + + /// Makes a new `NaiveTime` from the number of seconds since midnight and nanosecond. + /// + /// The nanosecond part is allowed to exceed 1,000,000,000 in order to represent a + /// [leap second](#leap-second-handling), but only when `secs % 60 == 59`. + /// + /// # Panics + /// + /// Panics on invalid number of seconds and/or nanosecond. + #[deprecated(since = "0.4.23", note = "use `from_num_seconds_from_midnight_opt()` instead")] + #[inline] + #[must_use] + pub const fn from_num_seconds_from_midnight(secs: u32, nano: u32) -> NaiveTime { + expect(NaiveTime::from_num_seconds_from_midnight_opt(secs, nano), "invalid time") + } + + /// Makes a new `NaiveTime` from the number of seconds since midnight and nanosecond. + /// + /// The nanosecond part is allowed to exceed 1,000,000,000 in order to represent a + /// [leap second](#leap-second-handling), but only when `secs % 60 == 59`. + /// + /// # Errors + /// + /// Returns `None` on invalid number of seconds and/or nanosecond. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveTime; + /// + /// let from_nsecs_opt = NaiveTime::from_num_seconds_from_midnight_opt; + /// + /// assert!(from_nsecs_opt(0, 0).is_some()); + /// assert!(from_nsecs_opt(86399, 999_999_999).is_some()); + /// assert!(from_nsecs_opt(86399, 1_999_999_999).is_some()); // a leap second after 23:59:59 + /// assert!(from_nsecs_opt(86_400, 0).is_none()); + /// assert!(from_nsecs_opt(86399, 2_000_000_000).is_none()); + /// ``` + #[inline] + #[must_use] + pub const fn from_num_seconds_from_midnight_opt(secs: u32, nano: u32) -> Option { + if secs >= 86_400 || nano >= 2_000_000_000 || (nano >= 1_000_000_000 && secs % 60 != 59) { + return None; + } + Some(NaiveTime { secs, frac: nano }) + } + + /// Parses a string with the specified format string and returns a new `NaiveTime`. + /// See the [`format::strftime` module](crate::format::strftime) + /// on the supported escape sequences. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveTime; + /// + /// let parse_from_str = NaiveTime::parse_from_str; + /// + /// assert_eq!( + /// parse_from_str("23:56:04", "%H:%M:%S"), + /// Ok(NaiveTime::from_hms_opt(23, 56, 4).unwrap()) + /// ); + /// assert_eq!( + /// parse_from_str("pm012345.6789", "%p%I%M%S%.f"), + /// Ok(NaiveTime::from_hms_micro_opt(13, 23, 45, 678_900).unwrap()) + /// ); + /// ``` + /// + /// Date and offset is ignored for the purpose of parsing. + /// + /// ``` + /// # use chrono::NaiveTime; + /// # let parse_from_str = NaiveTime::parse_from_str; + /// assert_eq!( + /// parse_from_str("2014-5-17T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), + /// Ok(NaiveTime::from_hms_opt(12, 34, 56).unwrap()) + /// ); + /// ``` + /// + /// [Leap seconds](#leap-second-handling) are correctly handled by + /// treating any time of the form `hh:mm:60` as a leap second. + /// (This equally applies to the formatting, so the round trip is possible.) + /// + /// ``` + /// # use chrono::NaiveTime; + /// # let parse_from_str = NaiveTime::parse_from_str; + /// assert_eq!( + /// parse_from_str("08:59:60.123", "%H:%M:%S%.f"), + /// Ok(NaiveTime::from_hms_milli_opt(8, 59, 59, 1_123).unwrap()) + /// ); + /// ``` + /// + /// Missing seconds are assumed to be zero, + /// but out-of-bound times or insufficient fields are errors otherwise. + /// + /// ``` + /// # use chrono::NaiveTime; + /// # let parse_from_str = NaiveTime::parse_from_str; + /// assert_eq!(parse_from_str("7:15", "%H:%M"), Ok(NaiveTime::from_hms_opt(7, 15, 0).unwrap())); + /// + /// assert!(parse_from_str("04m33s", "%Mm%Ss").is_err()); + /// assert!(parse_from_str("12", "%H").is_err()); + /// assert!(parse_from_str("17:60", "%H:%M").is_err()); + /// assert!(parse_from_str("24:00:00", "%H:%M:%S").is_err()); + /// ``` + /// + /// All parsed fields should be consistent to each other, otherwise it's an error. + /// Here `%H` is for 24-hour clocks, unlike `%I`, + /// and thus can be independently determined without AM/PM. + /// + /// ``` + /// # use chrono::NaiveTime; + /// # let parse_from_str = NaiveTime::parse_from_str; + /// assert!(parse_from_str("13:07 AM", "%H:%M %p").is_err()); + /// ``` + pub fn parse_from_str(s: &str, fmt: &str) -> ParseResult { + let mut parsed = Parsed::new(); + parse(&mut parsed, s, StrftimeItems::new(fmt))?; + parsed.to_naive_time() + } + + /// Parses a string from a user-specified format into a new `NaiveTime` value, and a slice with + /// the remaining portion of the string. + /// See the [`format::strftime` module](crate::format::strftime) + /// on the supported escape sequences. + /// + /// Similar to [`parse_from_str`](#method.parse_from_str). + /// + /// # Example + /// + /// ```rust + /// # use chrono::{NaiveTime}; + /// let (time, remainder) = + /// NaiveTime::parse_and_remainder("3h4m33s trailing text", "%-Hh%-Mm%-Ss").unwrap(); + /// assert_eq!(time, NaiveTime::from_hms_opt(3, 4, 33).unwrap()); + /// assert_eq!(remainder, " trailing text"); + /// ``` + pub fn parse_and_remainder<'a>(s: &'a str, fmt: &str) -> ParseResult<(NaiveTime, &'a str)> { + let mut parsed = Parsed::new(); + let remainder = parse_and_remainder(&mut parsed, s, StrftimeItems::new(fmt))?; + parsed.to_naive_time().map(|t| (t, remainder)) + } + + /// Adds given `TimeDelta` to the current time, and also returns the number of *seconds* + /// in the integral number of days ignored from the addition. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveTime, TimeDelta}; + /// + /// let from_hms = |h, m, s| NaiveTime::from_hms_opt(h, m, s).unwrap(); + /// + /// assert_eq!( + /// from_hms(3, 4, 5).overflowing_add_signed(TimeDelta::try_hours(11).unwrap()), + /// (from_hms(14, 4, 5), 0) + /// ); + /// assert_eq!( + /// from_hms(3, 4, 5).overflowing_add_signed(TimeDelta::try_hours(23).unwrap()), + /// (from_hms(2, 4, 5), 86_400) + /// ); + /// assert_eq!( + /// from_hms(3, 4, 5).overflowing_add_signed(TimeDelta::try_hours(-7).unwrap()), + /// (from_hms(20, 4, 5), -86_400) + /// ); + /// ``` + #[must_use] + pub const fn overflowing_add_signed(&self, rhs: TimeDelta) -> (NaiveTime, i64) { + let mut secs = self.secs as i64; + let mut frac = self.frac as i32; + let secs_to_add = rhs.num_seconds(); + let frac_to_add = rhs.subsec_nanos(); + + // Check if `self` is a leap second and adding `rhs` would escape that leap second. + // If that is the case, update `frac` and `secs` to involve no leap second. + // If it stays within the leap second or the second before, and only adds a fractional + // second, just do that and return (this way the rest of the code can ignore leap seconds). + if frac >= 1_000_000_000 { + // check below is adjusted to not overflow an i32: `frac + frac_to_add >= 2_000_000_000` + if secs_to_add > 0 || (frac_to_add > 0 && frac >= 2_000_000_000 - frac_to_add) { + frac -= 1_000_000_000; + } else if secs_to_add < 0 { + frac -= 1_000_000_000; + secs += 1; + } else { + return (NaiveTime { secs: self.secs, frac: (frac + frac_to_add) as u32 }, 0); + } + } + + let mut secs = secs + secs_to_add; + frac += frac_to_add; + + if frac < 0 { + frac += 1_000_000_000; + secs -= 1; + } else if frac >= 1_000_000_000 { + frac -= 1_000_000_000; + secs += 1; + } + + let secs_in_day = secs.rem_euclid(86_400); + let remaining = secs - secs_in_day; + (NaiveTime { secs: secs_in_day as u32, frac: frac as u32 }, remaining) + } + + /// Subtracts given `TimeDelta` from the current time, and also returns the number of *seconds* + /// in the integral number of days ignored from the subtraction. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveTime, TimeDelta}; + /// + /// let from_hms = |h, m, s| NaiveTime::from_hms_opt(h, m, s).unwrap(); + /// + /// assert_eq!( + /// from_hms(3, 4, 5).overflowing_sub_signed(TimeDelta::try_hours(2).unwrap()), + /// (from_hms(1, 4, 5), 0) + /// ); + /// assert_eq!( + /// from_hms(3, 4, 5).overflowing_sub_signed(TimeDelta::try_hours(17).unwrap()), + /// (from_hms(10, 4, 5), 86_400) + /// ); + /// assert_eq!( + /// from_hms(3, 4, 5).overflowing_sub_signed(TimeDelta::try_hours(-22).unwrap()), + /// (from_hms(1, 4, 5), -86_400) + /// ); + /// ``` + #[inline] + #[must_use] + pub const fn overflowing_sub_signed(&self, rhs: TimeDelta) -> (NaiveTime, i64) { + let (time, rhs) = self.overflowing_add_signed(rhs.neg()); + (time, -rhs) // safe to negate, rhs is within +/- (2^63 / 1000) + } + + /// Subtracts another `NaiveTime` from the current time. + /// Returns a `TimeDelta` within +/- 1 day. + /// This does not overflow or underflow at all. + /// + /// As a part of Chrono's [leap second handling](#leap-second-handling), + /// the subtraction assumes that **there is no leap second ever**, + /// except when any of the `NaiveTime`s themselves represents a leap second + /// in which case the assumption becomes that + /// **there are exactly one (or two) leap second(s) ever**. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveTime, TimeDelta}; + /// + /// let from_hmsm = |h, m, s, milli| NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap(); + /// let since = NaiveTime::signed_duration_since; + /// + /// assert_eq!(since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 5, 7, 900)), TimeDelta::zero()); + /// assert_eq!( + /// since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 5, 7, 875)), + /// TimeDelta::try_milliseconds(25).unwrap() + /// ); + /// assert_eq!( + /// since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 5, 6, 925)), + /// TimeDelta::try_milliseconds(975).unwrap() + /// ); + /// assert_eq!( + /// since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 5, 0, 900)), + /// TimeDelta::try_seconds(7).unwrap() + /// ); + /// assert_eq!( + /// since(from_hmsm(3, 5, 7, 900), from_hmsm(3, 0, 7, 900)), + /// TimeDelta::try_seconds(5 * 60).unwrap() + /// ); + /// assert_eq!( + /// since(from_hmsm(3, 5, 7, 900), from_hmsm(0, 5, 7, 900)), + /// TimeDelta::try_seconds(3 * 3600).unwrap() + /// ); + /// assert_eq!( + /// since(from_hmsm(3, 5, 7, 900), from_hmsm(4, 5, 7, 900)), + /// TimeDelta::try_seconds(-3600).unwrap() + /// ); + /// assert_eq!( + /// since(from_hmsm(3, 5, 7, 900), from_hmsm(2, 4, 6, 800)), + /// TimeDelta::try_seconds(3600 + 60 + 1).unwrap() + TimeDelta::try_milliseconds(100).unwrap() + /// ); + /// ``` + /// + /// Leap seconds are handled, but the subtraction assumes that + /// there were no other leap seconds happened. + /// + /// ``` + /// # use chrono::{TimeDelta, NaiveTime}; + /// # let from_hmsm = |h, m, s, milli| { NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap() }; + /// # let since = NaiveTime::signed_duration_since; + /// assert_eq!(since(from_hmsm(3, 0, 59, 1_000), from_hmsm(3, 0, 59, 0)), + /// TimeDelta::try_seconds(1).unwrap()); + /// assert_eq!(since(from_hmsm(3, 0, 59, 1_500), from_hmsm(3, 0, 59, 0)), + /// TimeDelta::try_milliseconds(1500).unwrap()); + /// assert_eq!(since(from_hmsm(3, 0, 59, 1_000), from_hmsm(3, 0, 0, 0)), + /// TimeDelta::try_seconds(60).unwrap()); + /// assert_eq!(since(from_hmsm(3, 0, 0, 0), from_hmsm(2, 59, 59, 1_000)), + /// TimeDelta::try_seconds(1).unwrap()); + /// assert_eq!(since(from_hmsm(3, 0, 59, 1_000), from_hmsm(2, 59, 59, 1_000)), + /// TimeDelta::try_seconds(61).unwrap()); + /// ``` + #[must_use] + pub const fn signed_duration_since(self, rhs: NaiveTime) -> TimeDelta { + // | | :leap| | | | | | | :leap| | + // | | : | | | | | | | : | | + // ----+----+-----*---+----+----+----+----+----+----+-------*-+----+---- + // | `rhs` | | `self` + // |======================================>| | + // | | `self.secs - rhs.secs` |`self.frac` + // |====>| | |======>| + // `rhs.frac`|========================================>| + // | | | `self - rhs` | | + + let mut secs = self.secs as i64 - rhs.secs as i64; + let frac = self.frac as i64 - rhs.frac as i64; + + // `secs` may contain a leap second yet to be counted + if self.secs > rhs.secs && rhs.frac >= 1_000_000_000 { + secs += 1; + } else if self.secs < rhs.secs && self.frac >= 1_000_000_000 { + secs -= 1; + } + + let secs_from_frac = frac.div_euclid(1_000_000_000); + let frac = frac.rem_euclid(1_000_000_000) as u32; + + expect(TimeDelta::new(secs + secs_from_frac, frac), "must be in range") + } + + /// Adds given `FixedOffset` to the current time, and returns the number of days that should be + /// added to a date as a result of the offset (either `-1`, `0`, or `1` because the offset is + /// always less than 24h). + /// + /// This method is similar to [`overflowing_add_signed`](#method.overflowing_add_signed), but + /// preserves leap seconds. + pub(super) const fn overflowing_add_offset(&self, offset: FixedOffset) -> (NaiveTime, i32) { + let secs = self.secs as i32 + offset.local_minus_utc(); + let days = secs.div_euclid(86_400); + let secs = secs.rem_euclid(86_400); + (NaiveTime { secs: secs as u32, frac: self.frac }, days) + } + + /// Subtracts given `FixedOffset` from the current time, and returns the number of days that + /// should be added to a date as a result of the offset (either `-1`, `0`, or `1` because the + /// offset is always less than 24h). + /// + /// This method is similar to [`overflowing_sub_signed`](#method.overflowing_sub_signed), but + /// preserves leap seconds. + pub(super) const fn overflowing_sub_offset(&self, offset: FixedOffset) -> (NaiveTime, i32) { + let secs = self.secs as i32 - offset.local_minus_utc(); + let days = secs.div_euclid(86_400); + let secs = secs.rem_euclid(86_400); + (NaiveTime { secs: secs as u32, frac: self.frac }, days) + } + + /// Formats the time with the specified formatting items. + /// Otherwise it is the same as the ordinary [`format`](#method.format) method. + /// + /// The `Iterator` of items should be `Clone`able, + /// since the resulting `DelayedFormat` value may be formatted multiple times. + /// + /// # Example + /// + /// ``` + /// use chrono::format::strftime::StrftimeItems; + /// use chrono::NaiveTime; + /// + /// let fmt = StrftimeItems::new("%H:%M:%S"); + /// let t = NaiveTime::from_hms_opt(23, 56, 4).unwrap(); + /// assert_eq!(t.format_with_items(fmt.clone()).to_string(), "23:56:04"); + /// assert_eq!(t.format("%H:%M:%S").to_string(), "23:56:04"); + /// ``` + /// + /// The resulting `DelayedFormat` can be formatted directly via the `Display` trait. + /// + /// ``` + /// # use chrono::NaiveTime; + /// # use chrono::format::strftime::StrftimeItems; + /// # let fmt = StrftimeItems::new("%H:%M:%S").clone(); + /// # let t = NaiveTime::from_hms_opt(23, 56, 4).unwrap(); + /// assert_eq!(format!("{}", t.format_with_items(fmt)), "23:56:04"); + /// ``` + #[cfg(feature = "alloc")] + #[inline] + #[must_use] + pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat + where + I: Iterator + Clone, + B: Borrow>, + { + DelayedFormat::new(None, Some(*self), items) + } + + /// Formats the time with the specified format string. + /// See the [`format::strftime` module](crate::format::strftime) + /// on the supported escape sequences. + /// + /// This returns a `DelayedFormat`, + /// which gets converted to a string only when actual formatting happens. + /// You may use the `to_string` method to get a `String`, + /// or just feed it into `print!` and other formatting macros. + /// (In this way it avoids the redundant memory allocation.) + /// + /// A wrong format string does *not* issue an error immediately. + /// Rather, converting or formatting the `DelayedFormat` fails. + /// You are recommended to immediately use `DelayedFormat` for this reason. + /// + /// # Example + /// + /// ``` + /// use chrono::NaiveTime; + /// + /// let t = NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap(); + /// assert_eq!(t.format("%H:%M:%S").to_string(), "23:56:04"); + /// assert_eq!(t.format("%H:%M:%S%.6f").to_string(), "23:56:04.012345"); + /// assert_eq!(t.format("%-I:%M %p").to_string(), "11:56 PM"); + /// ``` + /// + /// The resulting `DelayedFormat` can be formatted directly via the `Display` trait. + /// + /// ``` + /// # use chrono::NaiveTime; + /// # let t = NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap(); + /// assert_eq!(format!("{}", t.format("%H:%M:%S")), "23:56:04"); + /// assert_eq!(format!("{}", t.format("%H:%M:%S%.6f")), "23:56:04.012345"); + /// assert_eq!(format!("{}", t.format("%-I:%M %p")), "11:56 PM"); + /// ``` + #[cfg(feature = "alloc")] + #[inline] + #[must_use] + pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { + self.format_with_items(StrftimeItems::new(fmt)) + } + + /// Returns a triple of the hour, minute and second numbers. + pub(crate) fn hms(&self) -> (u32, u32, u32) { + let sec = self.secs % 60; + let mins = self.secs / 60; + let min = mins % 60; + let hour = mins / 60; + (hour, min, sec) + } + + /// Returns the number of non-leap seconds past the last midnight. + // This duplicates `Timelike::num_seconds_from_midnight()`, because trait methods can't be const + // yet. + #[inline] + pub(crate) const fn num_seconds_from_midnight(&self) -> u32 { + self.secs + } + + /// Returns the number of nanoseconds since the whole non-leap second. + // This duplicates `Timelike::nanosecond()`, because trait methods can't be const yet. + #[inline] + pub(crate) const fn nanosecond(&self) -> u32 { + self.frac + } + + /// The earliest possible `NaiveTime` + pub const MIN: Self = Self { secs: 0, frac: 0 }; + pub(super) const MAX: Self = Self { secs: 23 * 3600 + 59 * 60 + 59, frac: 999_999_999 }; +} + +impl Timelike for NaiveTime { + /// Returns the hour number from 0 to 23. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveTime, Timelike}; + /// + /// assert_eq!(NaiveTime::from_hms_opt(0, 0, 0).unwrap().hour(), 0); + /// assert_eq!(NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap().hour(), 23); + /// ``` + #[inline] + fn hour(&self) -> u32 { + self.hms().0 + } + + /// Returns the minute number from 0 to 59. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveTime, Timelike}; + /// + /// assert_eq!(NaiveTime::from_hms_opt(0, 0, 0).unwrap().minute(), 0); + /// assert_eq!(NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap().minute(), 56); + /// ``` + #[inline] + fn minute(&self) -> u32 { + self.hms().1 + } + + /// Returns the second number from 0 to 59. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveTime, Timelike}; + /// + /// assert_eq!(NaiveTime::from_hms_opt(0, 0, 0).unwrap().second(), 0); + /// assert_eq!(NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap().second(), 4); + /// ``` + /// + /// This method never returns 60 even when it is a leap second. + /// ([Why?](#leap-second-handling)) + /// Use the proper [formatting method](#method.format) to get a human-readable representation. + /// + /// ``` + /// # #[cfg(feature = "alloc")] { + /// # use chrono::{NaiveTime, Timelike}; + /// let leap = NaiveTime::from_hms_milli_opt(23, 59, 59, 1_000).unwrap(); + /// assert_eq!(leap.second(), 59); + /// assert_eq!(leap.format("%H:%M:%S").to_string(), "23:59:60"); + /// # } + /// ``` + #[inline] + fn second(&self) -> u32 { + self.hms().2 + } + + /// Returns the number of nanoseconds since the whole non-leap second. + /// The range from 1,000,000,000 to 1,999,999,999 represents + /// the [leap second](#leap-second-handling). + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveTime, Timelike}; + /// + /// assert_eq!(NaiveTime::from_hms_opt(0, 0, 0).unwrap().nanosecond(), 0); + /// assert_eq!( + /// NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap().nanosecond(), + /// 12_345_678 + /// ); + /// ``` + /// + /// Leap seconds may have seemingly out-of-range return values. + /// You can reduce the range with `time.nanosecond() % 1_000_000_000`, or + /// use the proper [formatting method](#method.format) to get a human-readable representation. + /// + /// ``` + /// # #[cfg(feature = "alloc")] { + /// # use chrono::{NaiveTime, Timelike}; + /// let leap = NaiveTime::from_hms_milli_opt(23, 59, 59, 1_000).unwrap(); + /// assert_eq!(leap.nanosecond(), 1_000_000_000); + /// assert_eq!(leap.format("%H:%M:%S%.9f").to_string(), "23:59:60.000000000"); + /// # } + /// ``` + #[inline] + fn nanosecond(&self) -> u32 { + self.frac + } + + /// Makes a new `NaiveTime` with the hour number changed. + /// + /// # Errors + /// + /// Returns `None` if the value for `hour` is invalid. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveTime, Timelike}; + /// + /// let dt = NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap(); + /// assert_eq!(dt.with_hour(7), Some(NaiveTime::from_hms_nano_opt(7, 56, 4, 12_345_678).unwrap())); + /// assert_eq!(dt.with_hour(24), None); + /// ``` + #[inline] + fn with_hour(&self, hour: u32) -> Option { + if hour >= 24 { + return None; + } + let secs = hour * 3600 + self.secs % 3600; + Some(NaiveTime { secs, ..*self }) + } + + /// Makes a new `NaiveTime` with the minute number changed. + /// + /// # Errors + /// + /// Returns `None` if the value for `minute` is invalid. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveTime, Timelike}; + /// + /// let dt = NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap(); + /// assert_eq!( + /// dt.with_minute(45), + /// Some(NaiveTime::from_hms_nano_opt(23, 45, 4, 12_345_678).unwrap()) + /// ); + /// assert_eq!(dt.with_minute(60), None); + /// ``` + #[inline] + fn with_minute(&self, min: u32) -> Option { + if min >= 60 { + return None; + } + let secs = self.secs / 3600 * 3600 + min * 60 + self.secs % 60; + Some(NaiveTime { secs, ..*self }) + } + + /// Makes a new `NaiveTime` with the second number changed. + /// + /// As with the [`second`](#method.second) method, + /// the input range is restricted to 0 through 59. + /// + /// # Errors + /// + /// Returns `None` if the value for `second` is invalid. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveTime, Timelike}; + /// + /// let dt = NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap(); + /// assert_eq!( + /// dt.with_second(17), + /// Some(NaiveTime::from_hms_nano_opt(23, 56, 17, 12_345_678).unwrap()) + /// ); + /// assert_eq!(dt.with_second(60), None); + /// ``` + #[inline] + fn with_second(&self, sec: u32) -> Option { + if sec >= 60 { + return None; + } + let secs = self.secs / 60 * 60 + sec; + Some(NaiveTime { secs, ..*self }) + } + + /// Makes a new `NaiveTime` with nanoseconds since the whole non-leap second changed. + /// + /// As with the [`nanosecond`](#method.nanosecond) method, + /// the input range can exceed 1,000,000,000 for leap seconds. + /// + /// # Errors + /// + /// Returns `None` if `nanosecond >= 2,000,000,000`. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveTime, Timelike}; + /// + /// let dt = NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap(); + /// assert_eq!( + /// dt.with_nanosecond(333_333_333), + /// Some(NaiveTime::from_hms_nano_opt(23, 56, 4, 333_333_333).unwrap()) + /// ); + /// assert_eq!(dt.with_nanosecond(2_000_000_000), None); + /// ``` + /// + /// Leap seconds can theoretically follow *any* whole second. + /// The following would be a proper leap second at the time zone offset of UTC-00:03:57 + /// (there are several historical examples comparable to this "non-sense" offset), + /// and therefore is allowed. + /// + /// ``` + /// # use chrono::{NaiveTime, Timelike}; + /// let dt = NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap(); + /// let strange_leap_second = dt.with_nanosecond(1_333_333_333).unwrap(); + /// assert_eq!(strange_leap_second.nanosecond(), 1_333_333_333); + /// ``` + #[inline] + fn with_nanosecond(&self, nano: u32) -> Option { + if nano >= 2_000_000_000 { + return None; + } + Some(NaiveTime { frac: nano, ..*self }) + } + + /// Returns the number of non-leap seconds past the last midnight. + /// + /// # Example + /// + /// ``` + /// use chrono::{NaiveTime, Timelike}; + /// + /// assert_eq!(NaiveTime::from_hms_opt(1, 2, 3).unwrap().num_seconds_from_midnight(), 3723); + /// assert_eq!( + /// NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap().num_seconds_from_midnight(), + /// 86164 + /// ); + /// assert_eq!( + /// NaiveTime::from_hms_milli_opt(23, 59, 59, 1_000).unwrap().num_seconds_from_midnight(), + /// 86399 + /// ); + /// ``` + #[inline] + fn num_seconds_from_midnight(&self) -> u32 { + self.secs // do not repeat the calculation! + } +} + +/// Add `TimeDelta` to `NaiveTime`. +/// +/// This wraps around and never overflows or underflows. +/// In particular the addition ignores integral number of days. +/// +/// As a part of Chrono's [leap second handling], the addition assumes that **there is no leap +/// second ever**, except when the `NaiveTime` itself represents a leap second in which case the +/// assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Example +/// +/// ``` +/// use chrono::{NaiveTime, TimeDelta}; +/// +/// let from_hmsm = |h, m, s, milli| NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap(); +/// +/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::zero(), from_hmsm(3, 5, 7, 0)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::try_seconds(1).unwrap(), from_hmsm(3, 5, 8, 0)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::try_seconds(-1).unwrap(), from_hmsm(3, 5, 6, 0)); +/// assert_eq!( +/// from_hmsm(3, 5, 7, 0) + TimeDelta::try_seconds(60 + 4).unwrap(), +/// from_hmsm(3, 6, 11, 0) +/// ); +/// assert_eq!( +/// from_hmsm(3, 5, 7, 0) + TimeDelta::try_seconds(7 * 60 * 60 - 6 * 60).unwrap(), +/// from_hmsm(9, 59, 7, 0) +/// ); +/// assert_eq!( +/// from_hmsm(3, 5, 7, 0) + TimeDelta::try_milliseconds(80).unwrap(), +/// from_hmsm(3, 5, 7, 80) +/// ); +/// assert_eq!( +/// from_hmsm(3, 5, 7, 950) + TimeDelta::try_milliseconds(280).unwrap(), +/// from_hmsm(3, 5, 8, 230) +/// ); +/// assert_eq!( +/// from_hmsm(3, 5, 7, 950) + TimeDelta::try_milliseconds(-980).unwrap(), +/// from_hmsm(3, 5, 6, 970) +/// ); +/// ``` +/// +/// The addition wraps around. +/// +/// ``` +/// # use chrono::{TimeDelta, NaiveTime}; +/// # let from_hmsm = |h, m, s, milli| { NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap() }; +/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::try_seconds(22*60*60).unwrap(), from_hmsm(1, 5, 7, 0)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::try_seconds(-8*60*60).unwrap(), from_hmsm(19, 5, 7, 0)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) + TimeDelta::try_days(800).unwrap(), from_hmsm(3, 5, 7, 0)); +/// ``` +/// +/// Leap seconds are handled, but the addition assumes that it is the only leap second happened. +/// +/// ``` +/// # use chrono::{TimeDelta, NaiveTime}; +/// # let from_hmsm = |h, m, s, milli| { NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap() }; +/// let leap = from_hmsm(3, 5, 59, 1_300); +/// assert_eq!(leap + TimeDelta::zero(), from_hmsm(3, 5, 59, 1_300)); +/// assert_eq!(leap + TimeDelta::try_milliseconds(-500).unwrap(), from_hmsm(3, 5, 59, 800)); +/// assert_eq!(leap + TimeDelta::try_milliseconds(500).unwrap(), from_hmsm(3, 5, 59, 1_800)); +/// assert_eq!(leap + TimeDelta::try_milliseconds(800).unwrap(), from_hmsm(3, 6, 0, 100)); +/// assert_eq!(leap + TimeDelta::try_seconds(10).unwrap(), from_hmsm(3, 6, 9, 300)); +/// assert_eq!(leap + TimeDelta::try_seconds(-10).unwrap(), from_hmsm(3, 5, 50, 300)); +/// assert_eq!(leap + TimeDelta::try_days(1).unwrap(), from_hmsm(3, 5, 59, 300)); +/// ``` +/// +/// [leap second handling]: crate::NaiveTime#leap-second-handling +impl Add for NaiveTime { + type Output = NaiveTime; + + #[inline] + fn add(self, rhs: TimeDelta) -> NaiveTime { + self.overflowing_add_signed(rhs).0 + } +} + +/// Add-assign `TimeDelta` to `NaiveTime`. +/// +/// This wraps around and never overflows or underflows. +/// In particular the addition ignores integral number of days. +impl AddAssign for NaiveTime { + #[inline] + fn add_assign(&mut self, rhs: TimeDelta) { + *self = self.add(rhs); + } +} + +/// Add `std::time::Duration` to `NaiveTime`. +/// +/// This wraps around and never overflows or underflows. +/// In particular the addition ignores integral number of days. +impl Add for NaiveTime { + type Output = NaiveTime; + + #[inline] + fn add(self, rhs: Duration) -> NaiveTime { + // We don't care about values beyond `24 * 60 * 60`, so we can take a modulus and avoid + // overflow during the conversion to `TimeDelta`. + // But we limit to double that just in case `self` is a leap-second. + let secs = rhs.as_secs() % (2 * 24 * 60 * 60); + let d = TimeDelta::new(secs as i64, rhs.subsec_nanos()).unwrap(); + self.overflowing_add_signed(d).0 + } +} + +/// Add-assign `std::time::Duration` to `NaiveTime`. +/// +/// This wraps around and never overflows or underflows. +/// In particular the addition ignores integral number of days. +impl AddAssign for NaiveTime { + #[inline] + fn add_assign(&mut self, rhs: Duration) { + *self = *self + rhs; + } +} + +/// Add `FixedOffset` to `NaiveTime`. +/// +/// This wraps around and never overflows or underflows. +/// In particular the addition ignores integral number of days. +impl Add for NaiveTime { + type Output = NaiveTime; + + #[inline] + fn add(self, rhs: FixedOffset) -> NaiveTime { + self.overflowing_add_offset(rhs).0 + } +} + +/// Subtract `TimeDelta` from `NaiveTime`. +/// +/// This wraps around and never overflows or underflows. +/// In particular the subtraction ignores integral number of days. +/// This is the same as addition with a negated `TimeDelta`. +/// +/// As a part of Chrono's [leap second handling], the subtraction assumes that **there is no leap +/// second ever**, except when the `NaiveTime` itself represents a leap second in which case the +/// assumption becomes that **there is exactly a single leap second ever**. +/// +/// # Example +/// +/// ``` +/// use chrono::{NaiveTime, TimeDelta}; +/// +/// let from_hmsm = |h, m, s, milli| NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap(); +/// +/// assert_eq!(from_hmsm(3, 5, 7, 0) - TimeDelta::zero(), from_hmsm(3, 5, 7, 0)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) - TimeDelta::try_seconds(1).unwrap(), from_hmsm(3, 5, 6, 0)); +/// assert_eq!( +/// from_hmsm(3, 5, 7, 0) - TimeDelta::try_seconds(60 + 5).unwrap(), +/// from_hmsm(3, 4, 2, 0) +/// ); +/// assert_eq!( +/// from_hmsm(3, 5, 7, 0) - TimeDelta::try_seconds(2 * 60 * 60 + 6 * 60).unwrap(), +/// from_hmsm(0, 59, 7, 0) +/// ); +/// assert_eq!( +/// from_hmsm(3, 5, 7, 0) - TimeDelta::try_milliseconds(80).unwrap(), +/// from_hmsm(3, 5, 6, 920) +/// ); +/// assert_eq!( +/// from_hmsm(3, 5, 7, 950) - TimeDelta::try_milliseconds(280).unwrap(), +/// from_hmsm(3, 5, 7, 670) +/// ); +/// ``` +/// +/// The subtraction wraps around. +/// +/// ``` +/// # use chrono::{TimeDelta, NaiveTime}; +/// # let from_hmsm = |h, m, s, milli| { NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap() }; +/// assert_eq!(from_hmsm(3, 5, 7, 0) - TimeDelta::try_seconds(8*60*60).unwrap(), from_hmsm(19, 5, 7, 0)); +/// assert_eq!(from_hmsm(3, 5, 7, 0) - TimeDelta::try_days(800).unwrap(), from_hmsm(3, 5, 7, 0)); +/// ``` +/// +/// Leap seconds are handled, but the subtraction assumes that it is the only leap second happened. +/// +/// ``` +/// # use chrono::{TimeDelta, NaiveTime}; +/// # let from_hmsm = |h, m, s, milli| { NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap() }; +/// let leap = from_hmsm(3, 5, 59, 1_300); +/// assert_eq!(leap - TimeDelta::zero(), from_hmsm(3, 5, 59, 1_300)); +/// assert_eq!(leap - TimeDelta::try_milliseconds(200).unwrap(), from_hmsm(3, 5, 59, 1_100)); +/// assert_eq!(leap - TimeDelta::try_milliseconds(500).unwrap(), from_hmsm(3, 5, 59, 800)); +/// assert_eq!(leap - TimeDelta::try_seconds(60).unwrap(), from_hmsm(3, 5, 0, 300)); +/// assert_eq!(leap - TimeDelta::try_days(1).unwrap(), from_hmsm(3, 6, 0, 300)); +/// ``` +/// +/// [leap second handling]: crate::NaiveTime#leap-second-handling +impl Sub for NaiveTime { + type Output = NaiveTime; + + #[inline] + fn sub(self, rhs: TimeDelta) -> NaiveTime { + self.overflowing_sub_signed(rhs).0 + } +} + +/// Subtract-assign `TimeDelta` from `NaiveTime`. +/// +/// This wraps around and never overflows or underflows. +/// In particular the subtraction ignores integral number of days. +impl SubAssign for NaiveTime { + #[inline] + fn sub_assign(&mut self, rhs: TimeDelta) { + *self = self.sub(rhs); + } +} + +/// Subtract `std::time::Duration` from `NaiveTime`. +/// +/// This wraps around and never overflows or underflows. +/// In particular the subtraction ignores integral number of days. +impl Sub for NaiveTime { + type Output = NaiveTime; + + #[inline] + fn sub(self, rhs: Duration) -> NaiveTime { + // We don't care about values beyond `24 * 60 * 60`, so we can take a modulus and avoid + // overflow during the conversion to `TimeDelta`. + // But we limit to double that just in case `self` is a leap-second. + let secs = rhs.as_secs() % (2 * 24 * 60 * 60); + let d = TimeDelta::new(secs as i64, rhs.subsec_nanos()).unwrap(); + self.overflowing_sub_signed(d).0 + } +} + +/// Subtract-assign `std::time::Duration` from `NaiveTime`. +/// +/// This wraps around and never overflows or underflows. +/// In particular the subtraction ignores integral number of days. +impl SubAssign for NaiveTime { + #[inline] + fn sub_assign(&mut self, rhs: Duration) { + *self = *self - rhs; + } +} + +/// Subtract `FixedOffset` from `NaiveTime`. +/// +/// This wraps around and never overflows or underflows. +/// In particular the subtraction ignores integral number of days. +impl Sub for NaiveTime { + type Output = NaiveTime; + + #[inline] + fn sub(self, rhs: FixedOffset) -> NaiveTime { + self.overflowing_sub_offset(rhs).0 + } +} + +/// Subtracts another `NaiveTime` from the current time. +/// Returns a `TimeDelta` within +/- 1 day. +/// This does not overflow or underflow at all. +/// +/// As a part of Chrono's [leap second handling](#leap-second-handling), +/// the subtraction assumes that **there is no leap second ever**, +/// except when any of the `NaiveTime`s themselves represents a leap second +/// in which case the assumption becomes that +/// **there are exactly one (or two) leap second(s) ever**. +/// +/// The implementation is a wrapper around +/// [`NaiveTime::signed_duration_since`](#method.signed_duration_since). +/// +/// # Example +/// +/// ``` +/// use chrono::{NaiveTime, TimeDelta}; +/// +/// let from_hmsm = |h, m, s, milli| NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap(); +/// +/// assert_eq!(from_hmsm(3, 5, 7, 900) - from_hmsm(3, 5, 7, 900), TimeDelta::zero()); +/// assert_eq!( +/// from_hmsm(3, 5, 7, 900) - from_hmsm(3, 5, 7, 875), +/// TimeDelta::try_milliseconds(25).unwrap() +/// ); +/// assert_eq!( +/// from_hmsm(3, 5, 7, 900) - from_hmsm(3, 5, 6, 925), +/// TimeDelta::try_milliseconds(975).unwrap() +/// ); +/// assert_eq!( +/// from_hmsm(3, 5, 7, 900) - from_hmsm(3, 5, 0, 900), +/// TimeDelta::try_seconds(7).unwrap() +/// ); +/// assert_eq!( +/// from_hmsm(3, 5, 7, 900) - from_hmsm(3, 0, 7, 900), +/// TimeDelta::try_seconds(5 * 60).unwrap() +/// ); +/// assert_eq!( +/// from_hmsm(3, 5, 7, 900) - from_hmsm(0, 5, 7, 900), +/// TimeDelta::try_seconds(3 * 3600).unwrap() +/// ); +/// assert_eq!( +/// from_hmsm(3, 5, 7, 900) - from_hmsm(4, 5, 7, 900), +/// TimeDelta::try_seconds(-3600).unwrap() +/// ); +/// assert_eq!( +/// from_hmsm(3, 5, 7, 900) - from_hmsm(2, 4, 6, 800), +/// TimeDelta::try_seconds(3600 + 60 + 1).unwrap() + TimeDelta::try_milliseconds(100).unwrap() +/// ); +/// ``` +/// +/// Leap seconds are handled, but the subtraction assumes that +/// there were no other leap seconds happened. +/// +/// ``` +/// # use chrono::{TimeDelta, NaiveTime}; +/// # let from_hmsm = |h, m, s, milli| { NaiveTime::from_hms_milli_opt(h, m, s, milli).unwrap() }; +/// assert_eq!(from_hmsm(3, 0, 59, 1_000) - from_hmsm(3, 0, 59, 0), TimeDelta::try_seconds(1).unwrap()); +/// assert_eq!(from_hmsm(3, 0, 59, 1_500) - from_hmsm(3, 0, 59, 0), +/// TimeDelta::try_milliseconds(1500).unwrap()); +/// assert_eq!(from_hmsm(3, 0, 59, 1_000) - from_hmsm(3, 0, 0, 0), TimeDelta::try_seconds(60).unwrap()); +/// assert_eq!(from_hmsm(3, 0, 0, 0) - from_hmsm(2, 59, 59, 1_000), TimeDelta::try_seconds(1).unwrap()); +/// assert_eq!(from_hmsm(3, 0, 59, 1_000) - from_hmsm(2, 59, 59, 1_000), +/// TimeDelta::try_seconds(61).unwrap()); +/// ``` +impl Sub for NaiveTime { + type Output = TimeDelta; + + #[inline] + fn sub(self, rhs: NaiveTime) -> TimeDelta { + self.signed_duration_since(rhs) + } +} + +/// The `Debug` output of the naive time `t` is the same as +/// [`t.format("%H:%M:%S%.f")`](crate::format::strftime). +/// +/// The string printed can be readily parsed via the `parse` method on `str`. +/// +/// It should be noted that, for leap seconds not on the minute boundary, +/// it may print a representation not distinguishable from non-leap seconds. +/// This doesn't matter in practice, since such leap seconds never happened. +/// (By the time of the first leap second on 1972-06-30, +/// every time zone offset around the world has standardized to the 5-minute alignment.) +/// +/// # Example +/// +/// ``` +/// use chrono::NaiveTime; +/// +/// assert_eq!(format!("{:?}", NaiveTime::from_hms_opt(23, 56, 4).unwrap()), "23:56:04"); +/// assert_eq!( +/// format!("{:?}", NaiveTime::from_hms_milli_opt(23, 56, 4, 12).unwrap()), +/// "23:56:04.012" +/// ); +/// assert_eq!( +/// format!("{:?}", NaiveTime::from_hms_micro_opt(23, 56, 4, 1234).unwrap()), +/// "23:56:04.001234" +/// ); +/// assert_eq!( +/// format!("{:?}", NaiveTime::from_hms_nano_opt(23, 56, 4, 123456).unwrap()), +/// "23:56:04.000123456" +/// ); +/// ``` +/// +/// Leap seconds may also be used. +/// +/// ``` +/// # use chrono::NaiveTime; +/// assert_eq!( +/// format!("{:?}", NaiveTime::from_hms_milli_opt(6, 59, 59, 1_500).unwrap()), +/// "06:59:60.500" +/// ); +/// ``` +impl fmt::Debug for NaiveTime { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let (hour, min, sec) = self.hms(); + let (sec, nano) = if self.frac >= 1_000_000_000 { + (sec + 1, self.frac - 1_000_000_000) + } else { + (sec, self.frac) + }; + + use core::fmt::Write; + write_hundreds(f, hour as u8)?; + f.write_char(':')?; + write_hundreds(f, min as u8)?; + f.write_char(':')?; + write_hundreds(f, sec as u8)?; + + if nano == 0 { + Ok(()) + } else if nano % 1_000_000 == 0 { + write!(f, ".{:03}", nano / 1_000_000) + } else if nano % 1_000 == 0 { + write!(f, ".{:06}", nano / 1_000) + } else { + write!(f, ".{:09}", nano) + } + } +} + +/// The `Display` output of the naive time `t` is the same as +/// [`t.format("%H:%M:%S%.f")`](crate::format::strftime). +/// +/// The string printed can be readily parsed via the `parse` method on `str`. +/// +/// It should be noted that, for leap seconds not on the minute boundary, +/// it may print a representation not distinguishable from non-leap seconds. +/// This doesn't matter in practice, since such leap seconds never happened. +/// (By the time of the first leap second on 1972-06-30, +/// every time zone offset around the world has standardized to the 5-minute alignment.) +/// +/// # Example +/// +/// ``` +/// use chrono::NaiveTime; +/// +/// assert_eq!(format!("{}", NaiveTime::from_hms_opt(23, 56, 4).unwrap()), "23:56:04"); +/// assert_eq!( +/// format!("{}", NaiveTime::from_hms_milli_opt(23, 56, 4, 12).unwrap()), +/// "23:56:04.012" +/// ); +/// assert_eq!( +/// format!("{}", NaiveTime::from_hms_micro_opt(23, 56, 4, 1234).unwrap()), +/// "23:56:04.001234" +/// ); +/// assert_eq!( +/// format!("{}", NaiveTime::from_hms_nano_opt(23, 56, 4, 123456).unwrap()), +/// "23:56:04.000123456" +/// ); +/// ``` +/// +/// Leap seconds may also be used. +/// +/// ``` +/// # use chrono::NaiveTime; +/// assert_eq!( +/// format!("{}", NaiveTime::from_hms_milli_opt(6, 59, 59, 1_500).unwrap()), +/// "06:59:60.500" +/// ); +/// ``` +impl fmt::Display for NaiveTime { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(self, f) + } +} + +/// Parsing a `str` into a `NaiveTime` uses the same format, +/// [`%H:%M:%S%.f`](crate::format::strftime), as in `Debug` and `Display`. +/// +/// # Example +/// +/// ``` +/// use chrono::NaiveTime; +/// +/// let t = NaiveTime::from_hms_opt(23, 56, 4).unwrap(); +/// assert_eq!("23:56:04".parse::(), Ok(t)); +/// +/// let t = NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap(); +/// assert_eq!("23:56:4.012345678".parse::(), Ok(t)); +/// +/// let t = NaiveTime::from_hms_nano_opt(23, 59, 59, 1_234_567_890).unwrap(); // leap second +/// assert_eq!("23:59:60.23456789".parse::(), Ok(t)); +/// +/// // Seconds are optional +/// let t = NaiveTime::from_hms_opt(23, 56, 0).unwrap(); +/// assert_eq!("23:56".parse::(), Ok(t)); +/// +/// assert!("foo".parse::().is_err()); +/// ``` +impl str::FromStr for NaiveTime { + type Err = ParseError; + + fn from_str(s: &str) -> ParseResult { + const HOUR_AND_MINUTE: &[Item<'static>] = &[ + Item::Numeric(Numeric::Hour, Pad::Zero), + Item::Space(""), + Item::Literal(":"), + Item::Numeric(Numeric::Minute, Pad::Zero), + ]; + const SECOND_AND_NANOS: &[Item<'static>] = &[ + Item::Space(""), + Item::Literal(":"), + Item::Numeric(Numeric::Second, Pad::Zero), + Item::Fixed(Fixed::Nanosecond), + Item::Space(""), + ]; + const TRAILING_WHITESPACE: [Item<'static>; 1] = [Item::Space("")]; + + let mut parsed = Parsed::new(); + let s = parse_and_remainder(&mut parsed, s, HOUR_AND_MINUTE.iter())?; + // Seconds are optional, don't fail if parsing them doesn't succeed. + let s = parse_and_remainder(&mut parsed, s, SECOND_AND_NANOS.iter()).unwrap_or(s); + parse(&mut parsed, s, TRAILING_WHITESPACE.iter())?; + parsed.to_naive_time() + } +} + +/// The default value for a NaiveTime is midnight, 00:00:00 exactly. +/// +/// # Example +/// +/// ```rust +/// use chrono::NaiveTime; +/// +/// let default_time = NaiveTime::default(); +/// assert_eq!(default_time, NaiveTime::from_hms_opt(0, 0, 0).unwrap()); +/// ``` +impl Default for NaiveTime { + fn default() -> Self { + NaiveTime::from_hms_opt(0, 0, 0).unwrap() + } +} diff --git a/third_party/rust/chrono/src/naive/time/serde.rs b/third_party/rust/chrono/src/naive/time/serde.rs new file mode 100644 index 00000000000..f9b1dde2153 --- /dev/null +++ b/third_party/rust/chrono/src/naive/time/serde.rs @@ -0,0 +1,143 @@ +use super::NaiveTime; +use core::fmt; +use serde::{de, ser}; + +// TODO not very optimized for space (binary formats would want something better) +// TODO round-trip for general leap seconds (not just those with second = 60) + +impl ser::Serialize for NaiveTime { + fn serialize(&self, serializer: S) -> Result + where + S: ser::Serializer, + { + serializer.collect_str(&self) + } +} + +struct NaiveTimeVisitor; + +impl de::Visitor<'_> for NaiveTimeVisitor { + type Value = NaiveTime; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a formatted time string") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + value.parse().map_err(E::custom) + } +} + +impl<'de> de::Deserialize<'de> for NaiveTime { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + deserializer.deserialize_str(NaiveTimeVisitor) + } +} + +#[cfg(test)] +mod tests { + use crate::NaiveTime; + + #[test] + fn test_serde_serialize() { + assert_eq!( + serde_json::to_string(&NaiveTime::from_hms_opt(0, 0, 0).unwrap()).ok(), + Some(r#""00:00:00""#.into()) + ); + assert_eq!( + serde_json::to_string(&NaiveTime::from_hms_milli_opt(0, 0, 0, 950).unwrap()).ok(), + Some(r#""00:00:00.950""#.into()) + ); + assert_eq!( + serde_json::to_string(&NaiveTime::from_hms_milli_opt(0, 0, 59, 1_000).unwrap()).ok(), + Some(r#""00:00:60""#.into()) + ); + assert_eq!( + serde_json::to_string(&NaiveTime::from_hms_opt(0, 1, 2).unwrap()).ok(), + Some(r#""00:01:02""#.into()) + ); + assert_eq!( + serde_json::to_string(&NaiveTime::from_hms_nano_opt(3, 5, 7, 98765432).unwrap()).ok(), + Some(r#""03:05:07.098765432""#.into()) + ); + assert_eq!( + serde_json::to_string(&NaiveTime::from_hms_opt(7, 8, 9).unwrap()).ok(), + Some(r#""07:08:09""#.into()) + ); + assert_eq!( + serde_json::to_string(&NaiveTime::from_hms_micro_opt(12, 34, 56, 789).unwrap()).ok(), + Some(r#""12:34:56.000789""#.into()) + ); + let leap = NaiveTime::from_hms_nano_opt(23, 59, 59, 1_999_999_999).unwrap(); + assert_eq!(serde_json::to_string(&leap).ok(), Some(r#""23:59:60.999999999""#.into())); + } + + #[test] + fn test_serde_deserialize() { + let from_str = serde_json::from_str::; + + assert_eq!(from_str(r#""00:00:00""#).ok(), Some(NaiveTime::from_hms_opt(0, 0, 0).unwrap())); + assert_eq!(from_str(r#""0:0:0""#).ok(), Some(NaiveTime::from_hms_opt(0, 0, 0).unwrap())); + assert_eq!( + from_str(r#""00:00:00.950""#).ok(), + Some(NaiveTime::from_hms_milli_opt(0, 0, 0, 950).unwrap()) + ); + assert_eq!( + from_str(r#""0:0:0.95""#).ok(), + Some(NaiveTime::from_hms_milli_opt(0, 0, 0, 950).unwrap()) + ); + assert_eq!( + from_str(r#""00:00:60""#).ok(), + Some(NaiveTime::from_hms_milli_opt(0, 0, 59, 1_000).unwrap()) + ); + assert_eq!(from_str(r#""00:01:02""#).ok(), Some(NaiveTime::from_hms_opt(0, 1, 2).unwrap())); + assert_eq!( + from_str(r#""03:05:07.098765432""#).ok(), + Some(NaiveTime::from_hms_nano_opt(3, 5, 7, 98765432).unwrap()) + ); + assert_eq!(from_str(r#""07:08:09""#).ok(), Some(NaiveTime::from_hms_opt(7, 8, 9).unwrap())); + assert_eq!( + from_str(r#""12:34:56.000789""#).ok(), + Some(NaiveTime::from_hms_micro_opt(12, 34, 56, 789).unwrap()) + ); + assert_eq!( + from_str(r#""23:59:60.999999999""#).ok(), + Some(NaiveTime::from_hms_nano_opt(23, 59, 59, 1_999_999_999).unwrap()) + ); + assert_eq!( + from_str(r#""23:59:60.9999999999997""#).ok(), // excess digits are ignored + Some(NaiveTime::from_hms_nano_opt(23, 59, 59, 1_999_999_999).unwrap()) + ); + + // bad formats + assert!(from_str(r#""""#).is_err()); + assert!(from_str(r#""000000""#).is_err()); + assert!(from_str(r#""00:00:61""#).is_err()); + assert!(from_str(r#""00:60:00""#).is_err()); + assert!(from_str(r#""24:00:00""#).is_err()); + assert!(from_str(r#""23:59:59,1""#).is_err()); + assert!(from_str(r#""012:34:56""#).is_err()); + assert!(from_str(r#""hh:mm:ss""#).is_err()); + assert!(from_str(r#"0"#).is_err()); + assert!(from_str(r#"86399"#).is_err()); + assert!(from_str(r#"{}"#).is_err()); + } + + #[test] + fn test_serde_bincode() { + // Bincode is relevant to test separately from JSON because + // it is not self-describing. + use bincode::{deserialize, serialize}; + + let t = NaiveTime::from_hms_nano_opt(3, 5, 7, 98765432).unwrap(); + let encoded = serialize(&t).unwrap(); + let decoded: NaiveTime = deserialize(&encoded).unwrap(); + assert_eq!(t, decoded); + } +} diff --git a/third_party/rust/chrono/src/naive/time/tests.rs b/third_party/rust/chrono/src/naive/time/tests.rs new file mode 100644 index 00000000000..a8754aebd62 --- /dev/null +++ b/third_party/rust/chrono/src/naive/time/tests.rs @@ -0,0 +1,393 @@ +use super::NaiveTime; +use crate::{FixedOffset, TimeDelta, Timelike}; + +#[test] +fn test_time_from_hms_milli() { + assert_eq!( + NaiveTime::from_hms_milli_opt(3, 5, 7, 0), + Some(NaiveTime::from_hms_nano_opt(3, 5, 7, 0).unwrap()) + ); + assert_eq!( + NaiveTime::from_hms_milli_opt(3, 5, 7, 777), + Some(NaiveTime::from_hms_nano_opt(3, 5, 7, 777_000_000).unwrap()) + ); + assert_eq!( + NaiveTime::from_hms_milli_opt(3, 5, 59, 1_999), + Some(NaiveTime::from_hms_nano_opt(3, 5, 59, 1_999_000_000).unwrap()) + ); + assert_eq!(NaiveTime::from_hms_milli_opt(3, 5, 59, 2_000), None); + assert_eq!(NaiveTime::from_hms_milli_opt(3, 5, 59, 5_000), None); // overflow check + assert_eq!(NaiveTime::from_hms_milli_opt(3, 5, 59, u32::MAX), None); +} + +#[test] +fn test_time_from_hms_micro() { + assert_eq!( + NaiveTime::from_hms_micro_opt(3, 5, 7, 0), + Some(NaiveTime::from_hms_nano_opt(3, 5, 7, 0).unwrap()) + ); + assert_eq!( + NaiveTime::from_hms_micro_opt(3, 5, 7, 333), + Some(NaiveTime::from_hms_nano_opt(3, 5, 7, 333_000).unwrap()) + ); + assert_eq!( + NaiveTime::from_hms_micro_opt(3, 5, 7, 777_777), + Some(NaiveTime::from_hms_nano_opt(3, 5, 7, 777_777_000).unwrap()) + ); + assert_eq!( + NaiveTime::from_hms_micro_opt(3, 5, 59, 1_999_999), + Some(NaiveTime::from_hms_nano_opt(3, 5, 59, 1_999_999_000).unwrap()) + ); + assert_eq!(NaiveTime::from_hms_micro_opt(3, 5, 59, 2_000_000), None); + assert_eq!(NaiveTime::from_hms_micro_opt(3, 5, 59, 5_000_000), None); // overflow check + assert_eq!(NaiveTime::from_hms_micro_opt(3, 5, 59, u32::MAX), None); +} + +#[test] +fn test_time_hms() { + assert_eq!(NaiveTime::from_hms_opt(3, 5, 7).unwrap().hour(), 3); + assert_eq!( + NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_hour(0), + Some(NaiveTime::from_hms_opt(0, 5, 7).unwrap()) + ); + assert_eq!( + NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_hour(23), + Some(NaiveTime::from_hms_opt(23, 5, 7).unwrap()) + ); + assert_eq!(NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_hour(24), None); + assert_eq!(NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_hour(u32::MAX), None); + + assert_eq!(NaiveTime::from_hms_opt(3, 5, 7).unwrap().minute(), 5); + assert_eq!( + NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_minute(0), + Some(NaiveTime::from_hms_opt(3, 0, 7).unwrap()) + ); + assert_eq!( + NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_minute(59), + Some(NaiveTime::from_hms_opt(3, 59, 7).unwrap()) + ); + assert_eq!(NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_minute(60), None); + assert_eq!(NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_minute(u32::MAX), None); + + assert_eq!(NaiveTime::from_hms_opt(3, 5, 7).unwrap().second(), 7); + assert_eq!( + NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_second(0), + Some(NaiveTime::from_hms_opt(3, 5, 0).unwrap()) + ); + assert_eq!( + NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_second(59), + Some(NaiveTime::from_hms_opt(3, 5, 59).unwrap()) + ); + assert_eq!(NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_second(60), None); + assert_eq!(NaiveTime::from_hms_opt(3, 5, 7).unwrap().with_second(u32::MAX), None); +} + +#[test] +fn test_time_add() { + macro_rules! check { + ($lhs:expr, $rhs:expr, $sum:expr) => {{ + assert_eq!($lhs + $rhs, $sum); + //assert_eq!($rhs + $lhs, $sum); + }}; + } + + let hmsm = |h, m, s, ms| NaiveTime::from_hms_milli_opt(h, m, s, ms).unwrap(); + + check!(hmsm(3, 5, 59, 900), TimeDelta::zero(), hmsm(3, 5, 59, 900)); + check!(hmsm(3, 5, 59, 900), TimeDelta::try_milliseconds(100).unwrap(), hmsm(3, 6, 0, 0)); + check!(hmsm(3, 5, 59, 1_300), TimeDelta::try_milliseconds(-1800).unwrap(), hmsm(3, 5, 58, 500)); + check!(hmsm(3, 5, 59, 1_300), TimeDelta::try_milliseconds(-800).unwrap(), hmsm(3, 5, 59, 500)); + check!( + hmsm(3, 5, 59, 1_300), + TimeDelta::try_milliseconds(-100).unwrap(), + hmsm(3, 5, 59, 1_200) + ); + check!(hmsm(3, 5, 59, 1_300), TimeDelta::try_milliseconds(100).unwrap(), hmsm(3, 5, 59, 1_400)); + check!(hmsm(3, 5, 59, 1_300), TimeDelta::try_milliseconds(800).unwrap(), hmsm(3, 6, 0, 100)); + check!(hmsm(3, 5, 59, 1_300), TimeDelta::try_milliseconds(1800).unwrap(), hmsm(3, 6, 1, 100)); + check!(hmsm(3, 5, 59, 900), TimeDelta::try_seconds(86399).unwrap(), hmsm(3, 5, 58, 900)); // overwrap + check!(hmsm(3, 5, 59, 900), TimeDelta::try_seconds(-86399).unwrap(), hmsm(3, 6, 0, 900)); + check!(hmsm(3, 5, 59, 900), TimeDelta::try_days(12345).unwrap(), hmsm(3, 5, 59, 900)); + check!(hmsm(3, 5, 59, 1_300), TimeDelta::try_days(1).unwrap(), hmsm(3, 5, 59, 300)); + check!(hmsm(3, 5, 59, 1_300), TimeDelta::try_days(-1).unwrap(), hmsm(3, 6, 0, 300)); + + // regression tests for #37 + check!(hmsm(0, 0, 0, 0), TimeDelta::try_milliseconds(-990).unwrap(), hmsm(23, 59, 59, 10)); + check!(hmsm(0, 0, 0, 0), TimeDelta::try_milliseconds(-9990).unwrap(), hmsm(23, 59, 50, 10)); +} + +#[test] +fn test_time_overflowing_add() { + let hmsm = |h, m, s, ms| NaiveTime::from_hms_milli_opt(h, m, s, ms).unwrap(); + + assert_eq!( + hmsm(3, 4, 5, 678).overflowing_add_signed(TimeDelta::try_hours(11).unwrap()), + (hmsm(14, 4, 5, 678), 0) + ); + assert_eq!( + hmsm(3, 4, 5, 678).overflowing_add_signed(TimeDelta::try_hours(23).unwrap()), + (hmsm(2, 4, 5, 678), 86_400) + ); + assert_eq!( + hmsm(3, 4, 5, 678).overflowing_add_signed(TimeDelta::try_hours(-7).unwrap()), + (hmsm(20, 4, 5, 678), -86_400) + ); + + // overflowing_add_signed with leap seconds may be counter-intuitive + assert_eq!( + hmsm(3, 4, 59, 1_678).overflowing_add_signed(TimeDelta::try_days(1).unwrap()), + (hmsm(3, 4, 59, 678), 86_400) + ); + assert_eq!( + hmsm(3, 4, 59, 1_678).overflowing_add_signed(TimeDelta::try_days(-1).unwrap()), + (hmsm(3, 5, 0, 678), -86_400) + ); +} + +#[test] +fn test_time_addassignment() { + let hms = |h, m, s| NaiveTime::from_hms_opt(h, m, s).unwrap(); + let mut time = hms(12, 12, 12); + time += TimeDelta::try_hours(10).unwrap(); + assert_eq!(time, hms(22, 12, 12)); + time += TimeDelta::try_hours(10).unwrap(); + assert_eq!(time, hms(8, 12, 12)); +} + +#[test] +fn test_time_subassignment() { + let hms = |h, m, s| NaiveTime::from_hms_opt(h, m, s).unwrap(); + let mut time = hms(12, 12, 12); + time -= TimeDelta::try_hours(10).unwrap(); + assert_eq!(time, hms(2, 12, 12)); + time -= TimeDelta::try_hours(10).unwrap(); + assert_eq!(time, hms(16, 12, 12)); +} + +#[test] +fn test_time_sub() { + macro_rules! check { + ($lhs:expr, $rhs:expr, $diff:expr) => {{ + // `time1 - time2 = duration` is equivalent to `time2 - time1 = -duration` + assert_eq!($lhs.signed_duration_since($rhs), $diff); + assert_eq!($rhs.signed_duration_since($lhs), -$diff); + }}; + } + + let hmsm = |h, m, s, ms| NaiveTime::from_hms_milli_opt(h, m, s, ms).unwrap(); + + check!(hmsm(3, 5, 7, 900), hmsm(3, 5, 7, 900), TimeDelta::zero()); + check!(hmsm(3, 5, 7, 900), hmsm(3, 5, 7, 600), TimeDelta::try_milliseconds(300).unwrap()); + check!(hmsm(3, 5, 7, 200), hmsm(2, 4, 6, 200), TimeDelta::try_seconds(3600 + 60 + 1).unwrap()); + check!( + hmsm(3, 5, 7, 200), + hmsm(2, 4, 6, 300), + TimeDelta::try_seconds(3600 + 60).unwrap() + TimeDelta::try_milliseconds(900).unwrap() + ); + + // treats the leap second as if it coincides with the prior non-leap second, + // as required by `time1 - time2 = duration` and `time2 - time1 = -duration` equivalence. + check!(hmsm(3, 6, 0, 200), hmsm(3, 5, 59, 1_800), TimeDelta::try_milliseconds(400).unwrap()); + //check!(hmsm(3, 5, 7, 1_200), hmsm(3, 5, 6, 1_800), TimeDelta::try_milliseconds(1400).unwrap()); + //check!(hmsm(3, 5, 7, 1_200), hmsm(3, 5, 6, 800), TimeDelta::try_milliseconds(1400).unwrap()); + + // additional equality: `time1 + duration = time2` is equivalent to + // `time2 - time1 = duration` IF AND ONLY IF `time2` represents a non-leap second. + assert_eq!(hmsm(3, 5, 6, 800) + TimeDelta::try_milliseconds(400).unwrap(), hmsm(3, 5, 7, 200)); + //assert_eq!(hmsm(3, 5, 6, 1_800) + TimeDelta::try_milliseconds(400).unwrap(), hmsm(3, 5, 7, 200)); +} + +#[test] +fn test_core_duration_ops() { + use core::time::Duration; + + let mut t = NaiveTime::from_hms_opt(11, 34, 23).unwrap(); + let same = t + Duration::ZERO; + assert_eq!(t, same); + + t += Duration::new(3600, 0); + assert_eq!(t, NaiveTime::from_hms_opt(12, 34, 23).unwrap()); + + t -= Duration::new(7200, 0); + assert_eq!(t, NaiveTime::from_hms_opt(10, 34, 23).unwrap()); +} + +#[test] +fn test_time_fmt() { + assert_eq!( + format!("{}", NaiveTime::from_hms_milli_opt(23, 59, 59, 999).unwrap()), + "23:59:59.999" + ); + assert_eq!( + format!("{}", NaiveTime::from_hms_milli_opt(23, 59, 59, 1_000).unwrap()), + "23:59:60" + ); + assert_eq!( + format!("{}", NaiveTime::from_hms_milli_opt(23, 59, 59, 1_001).unwrap()), + "23:59:60.001" + ); + assert_eq!( + format!("{}", NaiveTime::from_hms_micro_opt(0, 0, 0, 43210).unwrap()), + "00:00:00.043210" + ); + assert_eq!( + format!("{}", NaiveTime::from_hms_nano_opt(0, 0, 0, 6543210).unwrap()), + "00:00:00.006543210" + ); + + // the format specifier should have no effect on `NaiveTime` + assert_eq!( + format!("{:30}", NaiveTime::from_hms_milli_opt(3, 5, 7, 9).unwrap()), + "03:05:07.009" + ); +} + +#[test] +fn test_time_from_str() { + // valid cases + let valid = [ + "0:0:0", + "0:0:0.0000000", + "0:0:0.0000003", + " 4 : 3 : 2.1 ", + " 09:08:07 ", + " 09:08 ", + " 9:8:07 ", + "01:02:03", + "4:3:2.1", + "9:8:7", + "09:8:7", + "9:08:7", + "9:8:07", + "09:08:7", + "09:8:07", + "09:08:7", + "9:08:07", + "09:08:07", + "9:8:07.123", + "9:08:7.123", + "09:8:7.123", + "09:08:7.123", + "9:08:07.123", + "09:8:07.123", + "09:08:07.123", + "09:08:07.123", + "09:08:07.1234", + "09:08:07.12345", + "09:08:07.123456", + "09:08:07.1234567", + "09:08:07.12345678", + "09:08:07.123456789", + "09:08:07.1234567891", + "09:08:07.12345678912", + "23:59:60.373929310237", + ]; + for &s in &valid { + eprintln!("test_time_parse_from_str valid {:?}", s); + let d = match s.parse::() { + Ok(d) => d, + Err(e) => panic!("parsing `{}` has failed: {}", s, e), + }; + let s_ = format!("{:?}", d); + // `s` and `s_` may differ, but `s.parse()` and `s_.parse()` must be same + let d_ = match s_.parse::() { + Ok(d) => d, + Err(e) => { + panic!("`{}` is parsed into `{:?}`, but reparsing that has failed: {}", s, d, e) + } + }; + assert!( + d == d_, + "`{}` is parsed into `{:?}`, but reparsed result \ + `{:?}` does not match", + s, + d, + d_ + ); + } + + // some invalid cases + // since `ParseErrorKind` is private, all we can do is to check if there was an error + let invalid = [ + "", // empty + "x", // invalid + "15", // missing data + "15:8:", // trailing colon + "15:8:x", // invalid data + "15:8:9x", // invalid data + "23:59:61", // invalid second (out of bounds) + "23:54:35 GMT", // invalid (timezone non-sensical for NaiveTime) + "23:54:35 +0000", // invalid (timezone non-sensical for NaiveTime) + "1441497364.649", // valid datetime, not a NaiveTime + "+1441497364.649", // valid datetime, not a NaiveTime + "+1441497364", // valid datetime, not a NaiveTime + "001:02:03", // invalid hour + "01:002:03", // invalid minute + "01:02:003", // invalid second + "12:34:56.x", // invalid fraction + "12:34:56. 0", // invalid fraction format + "09:08:00000000007", // invalid second / invalid fraction format + ]; + for &s in &invalid { + eprintln!("test_time_parse_from_str invalid {:?}", s); + assert!(s.parse::().is_err()); + } +} + +#[test] +fn test_time_parse_from_str() { + let hms = |h, m, s| NaiveTime::from_hms_opt(h, m, s).unwrap(); + assert_eq!( + NaiveTime::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), + Ok(hms(12, 34, 56)) + ); // ignore date and offset + assert_eq!(NaiveTime::parse_from_str("PM 12:59", "%P %H:%M"), Ok(hms(12, 59, 0))); + assert_eq!(NaiveTime::parse_from_str("12:59 \n\t PM", "%H:%M \n\t %P"), Ok(hms(12, 59, 0))); + assert_eq!(NaiveTime::parse_from_str("\t\t12:59\tPM\t", "\t\t%H:%M\t%P\t"), Ok(hms(12, 59, 0))); + assert_eq!( + NaiveTime::parse_from_str("\t\t1259\t\tPM\t", "\t\t%H%M\t\t%P\t"), + Ok(hms(12, 59, 0)) + ); + assert!(NaiveTime::parse_from_str("12:59 PM", "%H:%M\t%P").is_ok()); + assert!(NaiveTime::parse_from_str("\t\t12:59 PM\t", "\t\t%H:%M\t%P\t").is_ok()); + assert!(NaiveTime::parse_from_str("12:59 PM", "%H:%M %P").is_ok()); + assert!(NaiveTime::parse_from_str("12:3456", "%H:%M:%S").is_err()); +} + +#[test] +fn test_overflowing_offset() { + let hmsm = |h, m, s, n| NaiveTime::from_hms_milli_opt(h, m, s, n).unwrap(); + + let positive_offset = FixedOffset::east_opt(4 * 60 * 60).unwrap(); + // regular time + let t = hmsm(5, 6, 7, 890); + assert_eq!(t.overflowing_add_offset(positive_offset), (hmsm(9, 6, 7, 890), 0)); + assert_eq!(t.overflowing_sub_offset(positive_offset), (hmsm(1, 6, 7, 890), 0)); + // leap second is preserved, and wrap to next day + let t = hmsm(23, 59, 59, 1_000); + assert_eq!(t.overflowing_add_offset(positive_offset), (hmsm(3, 59, 59, 1_000), 1)); + assert_eq!(t.overflowing_sub_offset(positive_offset), (hmsm(19, 59, 59, 1_000), 0)); + // wrap to previous day + let t = hmsm(1, 2, 3, 456); + assert_eq!(t.overflowing_sub_offset(positive_offset), (hmsm(21, 2, 3, 456), -1)); + // an odd offset + let negative_offset = FixedOffset::west_opt(((2 * 60) + 3) * 60 + 4).unwrap(); + let t = hmsm(5, 6, 7, 890); + assert_eq!(t.overflowing_add_offset(negative_offset), (hmsm(3, 3, 3, 890), 0)); + assert_eq!(t.overflowing_sub_offset(negative_offset), (hmsm(7, 9, 11, 890), 0)); + + assert_eq!(t.overflowing_add_offset(positive_offset).0, t + positive_offset); + assert_eq!(t.overflowing_sub_offset(positive_offset).0, t - positive_offset); +} + +#[test] +#[cfg(feature = "rkyv-validation")] +fn test_rkyv_validation() { + let t_min = NaiveTime::MIN; + let bytes = rkyv::to_bytes::<_, 8>(&t_min).unwrap(); + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), t_min); + + let t_max = NaiveTime::MAX; + let bytes = rkyv::to_bytes::<_, 8>(&t_max).unwrap(); + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), t_max); +} diff --git a/third_party/rust/chrono/src/offset/fixed.rs b/third_party/rust/chrono/src/offset/fixed.rs index 83f42a1a41b..f32f0f05e9a 100644 --- a/third_party/rust/chrono/src/offset/fixed.rs +++ b/third_party/rust/chrono/src/offset/fixed.rs @@ -4,22 +4,29 @@ //! The time zone which has a fixed offset from UTC. use core::fmt; -use core::ops::{Add, Sub}; -use oldtime::Duration as OldDuration; +use core::str::FromStr; -use super::{LocalResult, Offset, TimeZone}; -use div::div_mod_floor; -use naive::{NaiveDate, NaiveDateTime, NaiveTime}; -use DateTime; -use Timelike; +#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] +use rkyv::{Archive, Deserialize, Serialize}; + +use super::{MappedLocalTime, Offset, TimeZone}; +use crate::format::{OUT_OF_RANGE, ParseError, scan}; +use crate::naive::{NaiveDate, NaiveDateTime}; /// The time zone with fixed offset, from UTC-23:59:59 to UTC+23:59:59. /// /// Using the [`TimeZone`](./trait.TimeZone.html) methods /// on a `FixedOffset` struct is the preferred way to construct -/// `DateTime` instances. See the [`east`](#method.east) and -/// [`west`](#method.west) methods for examples. +/// `DateTime` instances. See the [`east_opt`](#method.east_opt) and +/// [`west_opt`](#method.west_opt) methods for examples. #[derive(PartialEq, Eq, Hash, Copy, Clone)] +#[cfg_attr( + any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), + derive(Archive, Deserialize, Serialize), + archive(compare(PartialEq)), + archive_attr(derive(Clone, Copy, PartialEq, Eq, Hash, Debug)) +)] +#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] pub struct FixedOffset { local_minus_utc: i32, } @@ -29,16 +36,8 @@ impl FixedOffset { /// The negative `secs` means the Western Hemisphere. /// /// Panics on the out-of-bound `secs`. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{FixedOffset, TimeZone}; - /// let hour = 3600; - /// let datetime = FixedOffset::east(5 * hour).ymd(2016, 11, 08) - /// .and_hms(0, 0, 0); - /// assert_eq!(&datetime.to_rfc3339(), "2016-11-08T00:00:00+05:00") - /// ~~~~ + #[deprecated(since = "0.4.23", note = "use `east_opt()` instead")] + #[must_use] pub fn east(secs: i32) -> FixedOffset { FixedOffset::east_opt(secs).expect("FixedOffset::east out of bounds") } @@ -47,7 +46,20 @@ impl FixedOffset { /// The negative `secs` means the Western Hemisphere. /// /// Returns `None` on the out-of-bound `secs`. - pub fn east_opt(secs: i32) -> Option { + /// + /// # Example + /// + /// ``` + /// # #[cfg(feature = "alloc")] { + /// use chrono::{FixedOffset, TimeZone}; + /// let hour = 3600; + /// let datetime = + /// FixedOffset::east_opt(5 * hour).unwrap().with_ymd_and_hms(2016, 11, 08, 0, 0, 0).unwrap(); + /// assert_eq!(&datetime.to_rfc3339(), "2016-11-08T00:00:00+05:00") + /// # } + /// ``` + #[must_use] + pub const fn east_opt(secs: i32) -> Option { if -86_400 < secs && secs < 86_400 { Some(FixedOffset { local_minus_utc: secs }) } else { @@ -59,16 +71,8 @@ impl FixedOffset { /// The negative `secs` means the Eastern Hemisphere. /// /// Panics on the out-of-bound `secs`. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{FixedOffset, TimeZone}; - /// let hour = 3600; - /// let datetime = FixedOffset::west(5 * hour).ymd(2016, 11, 08) - /// .and_hms(0, 0, 0); - /// assert_eq!(&datetime.to_rfc3339(), "2016-11-08T00:00:00-05:00") - /// ~~~~ + #[deprecated(since = "0.4.23", note = "use `west_opt()` instead")] + #[must_use] pub fn west(secs: i32) -> FixedOffset { FixedOffset::west_opt(secs).expect("FixedOffset::west out of bounds") } @@ -77,7 +81,20 @@ impl FixedOffset { /// The negative `secs` means the Eastern Hemisphere. /// /// Returns `None` on the out-of-bound `secs`. - pub fn west_opt(secs: i32) -> Option { + /// + /// # Example + /// + /// ``` + /// # #[cfg(feature = "alloc")] { + /// use chrono::{FixedOffset, TimeZone}; + /// let hour = 3600; + /// let datetime = + /// FixedOffset::west_opt(5 * hour).unwrap().with_ymd_and_hms(2016, 11, 08, 0, 0, 0).unwrap(); + /// assert_eq!(&datetime.to_rfc3339(), "2016-11-08T00:00:00-05:00") + /// # } + /// ``` + #[must_use] + pub const fn west_opt(secs: i32) -> Option { if -86_400 < secs && secs < 86_400 { Some(FixedOffset { local_minus_utc: -secs }) } else { @@ -87,17 +104,26 @@ impl FixedOffset { /// Returns the number of seconds to add to convert from UTC to the local time. #[inline] - pub fn local_minus_utc(&self) -> i32 { + pub const fn local_minus_utc(&self) -> i32 { self.local_minus_utc } /// Returns the number of seconds to add to convert from the local time to UTC. #[inline] - pub fn utc_minus_local(&self) -> i32 { + pub const fn utc_minus_local(&self) -> i32 { -self.local_minus_utc } } +/// Parsing a `str` into a `FixedOffset` uses the format [`%z`](crate::format::strftime). +impl FromStr for FixedOffset { + type Err = ParseError; + fn from_str(s: &str) -> Result { + let (_, offset) = scan::timezone_offset(s, scan::colon_or_space, false, false, true)?; + Self::east_opt(offset).ok_or(OUT_OF_RANGE) + } +} + impl TimeZone for FixedOffset { type Offset = FixedOffset; @@ -105,11 +131,11 @@ impl TimeZone for FixedOffset { *offset } - fn offset_from_local_date(&self, _local: &NaiveDate) -> LocalResult { - LocalResult::Single(*self) + fn offset_from_local_date(&self, _local: &NaiveDate) -> MappedLocalTime { + MappedLocalTime::Single(*self) } - fn offset_from_local_datetime(&self, _local: &NaiveDateTime) -> LocalResult { - LocalResult::Single(*self) + fn offset_from_local_datetime(&self, _local: &NaiveDateTime) -> MappedLocalTime { + MappedLocalTime::Single(*self) } fn offset_from_utc_date(&self, _utc: &NaiveDate) -> FixedOffset { @@ -130,8 +156,10 @@ impl fmt::Debug for FixedOffset { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let offset = self.local_minus_utc; let (sign, offset) = if offset < 0 { ('-', -offset) } else { ('+', offset) }; - let (mins, sec) = div_mod_floor(offset, 60); - let (hour, min) = div_mod_floor(mins, 60); + let sec = offset.rem_euclid(60); + let mins = offset.div_euclid(60); + let min = mins.rem_euclid(60); + let hour = mins.div_euclid(60); if sec == 0 { write!(f, "{}{:02}:{:02}", sign, hour, min) } else { @@ -146,99 +174,63 @@ impl fmt::Display for FixedOffset { } } -// addition or subtraction of FixedOffset to/from Timelike values is the same as -// adding or subtracting the offset's local_minus_utc value -// but keep keeps the leap second information. -// this should be implemented more efficiently, but for the time being, this is generic right now. - -fn add_with_leapsecond(lhs: &T, rhs: i32) -> T -where - T: Timelike + Add, -{ - // extract and temporarily remove the fractional part and later recover it - let nanos = lhs.nanosecond(); - let lhs = lhs.with_nanosecond(0).unwrap(); - (lhs + OldDuration::seconds(i64::from(rhs))).with_nanosecond(nanos).unwrap() -} - -impl Add for NaiveTime { - type Output = NaiveTime; - - #[inline] - fn add(self, rhs: FixedOffset) -> NaiveTime { - add_with_leapsecond(&self, rhs.local_minus_utc) - } -} - -impl Sub for NaiveTime { - type Output = NaiveTime; - - #[inline] - fn sub(self, rhs: FixedOffset) -> NaiveTime { - add_with_leapsecond(&self, -rhs.local_minus_utc) - } -} - -impl Add for NaiveDateTime { - type Output = NaiveDateTime; - - #[inline] - fn add(self, rhs: FixedOffset) -> NaiveDateTime { - add_with_leapsecond(&self, rhs.local_minus_utc) - } -} - -impl Sub for NaiveDateTime { - type Output = NaiveDateTime; - - #[inline] - fn sub(self, rhs: FixedOffset) -> NaiveDateTime { - add_with_leapsecond(&self, -rhs.local_minus_utc) - } -} - -impl Add for DateTime { - type Output = DateTime; - - #[inline] - fn add(self, rhs: FixedOffset) -> DateTime { - add_with_leapsecond(&self, rhs.local_minus_utc) - } -} - -impl Sub for DateTime { - type Output = DateTime; - - #[inline] - fn sub(self, rhs: FixedOffset) -> DateTime { - add_with_leapsecond(&self, -rhs.local_minus_utc) +#[cfg(all(feature = "arbitrary", feature = "std"))] +impl arbitrary::Arbitrary<'_> for FixedOffset { + fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result { + let secs = u.int_in_range(-86_399..=86_399)?; + let fixed_offset = FixedOffset::east_opt(secs) + .expect("Could not generate a valid chrono::FixedOffset. It looks like implementation of Arbitrary for FixedOffset is erroneous."); + Ok(fixed_offset) } } #[cfg(test)] mod tests { use super::FixedOffset; - use offset::TimeZone; + use crate::offset::TimeZone; + use std::str::FromStr; #[test] fn test_date_extreme_offset() { // starting from 0.3 we don't have an offset exceeding one day. // this makes everything easier! + let offset = FixedOffset::east_opt(86399).unwrap(); assert_eq!( - format!("{:?}", FixedOffset::east(86399).ymd(2012, 2, 29)), - "2012-02-29+23:59:59".to_string() + format!("{:?}", offset.with_ymd_and_hms(2012, 2, 29, 5, 6, 7).unwrap()), + "2012-02-29T05:06:07+23:59:59" ); + let offset = FixedOffset::east_opt(-86399).unwrap(); assert_eq!( - format!("{:?}", FixedOffset::east(86399).ymd(2012, 2, 29).and_hms(5, 6, 7)), - "2012-02-29T05:06:07+23:59:59".to_string() + format!("{:?}", offset.with_ymd_and_hms(2012, 2, 29, 5, 6, 7).unwrap()), + "2012-02-29T05:06:07-23:59:59" ); + let offset = FixedOffset::west_opt(86399).unwrap(); assert_eq!( - format!("{:?}", FixedOffset::west(86399).ymd(2012, 3, 4)), - "2012-03-04-23:59:59".to_string() + format!("{:?}", offset.with_ymd_and_hms(2012, 3, 4, 5, 6, 7).unwrap()), + "2012-03-04T05:06:07-23:59:59" ); + let offset = FixedOffset::west_opt(-86399).unwrap(); assert_eq!( - format!("{:?}", FixedOffset::west(86399).ymd(2012, 3, 4).and_hms(5, 6, 7)), - "2012-03-04T05:06:07-23:59:59".to_string() + format!("{:?}", offset.with_ymd_and_hms(2012, 3, 4, 5, 6, 7).unwrap()), + "2012-03-04T05:06:07+23:59:59" ); } + + #[test] + fn test_parse_offset() { + let offset = FixedOffset::from_str("-0500").unwrap(); + assert_eq!(offset.local_minus_utc, -5 * 3600); + let offset = FixedOffset::from_str("-08:00").unwrap(); + assert_eq!(offset.local_minus_utc, -8 * 3600); + let offset = FixedOffset::from_str("+06:30").unwrap(); + assert_eq!(offset.local_minus_utc, (6 * 3600) + 1800); + } + + #[test] + #[cfg(feature = "rkyv-validation")] + fn test_rkyv_validation() { + let offset = FixedOffset::from_str("-0500").unwrap(); + let bytes = rkyv::to_bytes::<_, 4>(&offset).unwrap(); + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), offset); + } } diff --git a/third_party/rust/chrono/src/offset/local.rs b/third_party/rust/chrono/src/offset/local.rs deleted file mode 100644 index 1abb3a9db05..00000000000 --- a/third_party/rust/chrono/src/offset/local.rs +++ /dev/null @@ -1,227 +0,0 @@ -// This is a part of Chrono. -// See README.md and LICENSE.txt for details. - -//! The local (system) time zone. - -#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] -use sys::{self, Timespec}; - -use super::fixed::FixedOffset; -use super::{LocalResult, TimeZone}; -#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] -use naive::NaiveTime; -use naive::{NaiveDate, NaiveDateTime}; -use {Date, DateTime}; -#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] -use {Datelike, Timelike}; - -/// Converts a `time::Tm` struct into the timezone-aware `DateTime`. -/// This assumes that `time` is working correctly, i.e. any error is fatal. -#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] -fn tm_to_datetime(mut tm: sys::Tm) -> DateTime { - if tm.tm_sec >= 60 { - tm.tm_nsec += (tm.tm_sec - 59) * 1_000_000_000; - tm.tm_sec = 59; - } - - #[cfg(not(windows))] - fn tm_to_naive_date(tm: &sys::Tm) -> NaiveDate { - // from_yo is more efficient than from_ymd (since it's the internal representation). - NaiveDate::from_yo(tm.tm_year + 1900, tm.tm_yday as u32 + 1) - } - - #[cfg(windows)] - fn tm_to_naive_date(tm: &sys::Tm) -> NaiveDate { - // ...but tm_yday is broken in Windows (issue #85) - NaiveDate::from_ymd(tm.tm_year + 1900, tm.tm_mon as u32 + 1, tm.tm_mday as u32) - } - - let date = tm_to_naive_date(&tm); - let time = NaiveTime::from_hms_nano( - tm.tm_hour as u32, - tm.tm_min as u32, - tm.tm_sec as u32, - tm.tm_nsec as u32, - ); - let offset = FixedOffset::east(tm.tm_utcoff); - DateTime::from_utc(date.and_time(time) - offset, offset) -} - -/// Converts a local `NaiveDateTime` to the `time::Timespec`. -#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] -fn datetime_to_timespec(d: &NaiveDateTime, local: bool) -> sys::Timespec { - // well, this exploits an undocumented `Tm::to_timespec` behavior - // to get the exact function we want (either `timegm` or `mktime`). - // the number 1 is arbitrary but should be non-zero to trigger `mktime`. - let tm_utcoff = if local { 1 } else { 0 }; - - let tm = sys::Tm { - tm_sec: d.second() as i32, - tm_min: d.minute() as i32, - tm_hour: d.hour() as i32, - tm_mday: d.day() as i32, - tm_mon: d.month0() as i32, // yes, C is that strange... - tm_year: d.year() - 1900, // this doesn't underflow, we know that d is `NaiveDateTime`. - tm_wday: 0, // to_local ignores this - tm_yday: 0, // and this - tm_isdst: -1, - tm_utcoff: tm_utcoff, - // do not set this, OS APIs are heavily inconsistent in terms of leap second handling - tm_nsec: 0, - }; - - tm.to_timespec() -} - -/// The local timescale. This is implemented via the standard `time` crate. -/// -/// Using the [`TimeZone`](./trait.TimeZone.html) methods -/// on the Local struct is the preferred way to construct `DateTime` -/// instances. -/// -/// # Example -/// -/// ~~~~ -/// use chrono::{Local, DateTime, TimeZone}; -/// -/// let dt: DateTime = Local::now(); -/// let dt: DateTime = Local.timestamp(0, 0); -/// ~~~~ -#[derive(Copy, Clone, Debug)] -pub struct Local; - -impl Local { - /// Returns a `Date` which corresponds to the current date. - pub fn today() -> Date { - Local::now().date() - } - - /// Returns a `DateTime` which corresponds to the current date. - #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] - pub fn now() -> DateTime { - tm_to_datetime(Timespec::now().local()) - } - - /// Returns a `DateTime` which corresponds to the current date. - #[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind"))] - pub fn now() -> DateTime { - use super::Utc; - let now: DateTime = super::Utc::now(); - - // Workaround missing timezone logic in `time` crate - let offset = FixedOffset::west((js_sys::Date::new_0().get_timezone_offset() as i32) * 60); - DateTime::from_utc(now.naive_utc(), offset) - } -} - -impl TimeZone for Local { - type Offset = FixedOffset; - - fn from_offset(_offset: &FixedOffset) -> Local { - Local - } - - // they are easier to define in terms of the finished date and time unlike other offsets - fn offset_from_local_date(&self, local: &NaiveDate) -> LocalResult { - self.from_local_date(local).map(|date| *date.offset()) - } - - fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult { - self.from_local_datetime(local).map(|datetime| *datetime.offset()) - } - - fn offset_from_utc_date(&self, utc: &NaiveDate) -> FixedOffset { - *self.from_utc_date(utc).offset() - } - - fn offset_from_utc_datetime(&self, utc: &NaiveDateTime) -> FixedOffset { - *self.from_utc_datetime(utc).offset() - } - - // override them for avoiding redundant works - fn from_local_date(&self, local: &NaiveDate) -> LocalResult> { - // this sounds very strange, but required for keeping `TimeZone::ymd` sane. - // in the other words, we use the offset at the local midnight - // but keep the actual date unaltered (much like `FixedOffset`). - let midnight = self.from_local_datetime(&local.and_hms(0, 0, 0)); - midnight.map(|datetime| Date::from_utc(*local, *datetime.offset())) - } - - #[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind"))] - fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult> { - let mut local = local.clone(); - // Get the offset from the js runtime - let offset = FixedOffset::west((js_sys::Date::new_0().get_timezone_offset() as i32) * 60); - local -= ::Duration::seconds(offset.local_minus_utc() as i64); - LocalResult::Single(DateTime::from_utc(local, offset)) - } - - #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] - fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult> { - let timespec = datetime_to_timespec(local, true); - - // datetime_to_timespec completely ignores leap seconds, so we need to adjust for them - let mut tm = timespec.local(); - assert_eq!(tm.tm_nsec, 0); - tm.tm_nsec = local.nanosecond() as i32; - - LocalResult::Single(tm_to_datetime(tm)) - } - - fn from_utc_date(&self, utc: &NaiveDate) -> Date { - let midnight = self.from_utc_datetime(&utc.and_hms(0, 0, 0)); - Date::from_utc(*utc, *midnight.offset()) - } - - #[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind"))] - fn from_utc_datetime(&self, utc: &NaiveDateTime) -> DateTime { - // Get the offset from the js runtime - let offset = FixedOffset::west((js_sys::Date::new_0().get_timezone_offset() as i32) * 60); - DateTime::from_utc(*utc, offset) - } - - #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] - fn from_utc_datetime(&self, utc: &NaiveDateTime) -> DateTime { - let timespec = datetime_to_timespec(utc, false); - - // datetime_to_timespec completely ignores leap seconds, so we need to adjust for them - let mut tm = timespec.local(); - assert_eq!(tm.tm_nsec, 0); - tm.tm_nsec = utc.nanosecond() as i32; - - tm_to_datetime(tm) - } -} - -#[cfg(test)] -mod tests { - use super::Local; - use offset::TimeZone; - use Datelike; - - #[test] - fn test_local_date_sanity_check() { - // issue #27 - assert_eq!(Local.ymd(2999, 12, 28).day(), 28); - } - - #[test] - fn test_leap_second() { - // issue #123 - let today = Local::today(); - - let dt = today.and_hms_milli(1, 2, 59, 1000); - let timestr = dt.time().to_string(); - // the OS API may or may not support the leap second, - // but there are only two sensible options. - assert!(timestr == "01:02:60" || timestr == "01:03:00", "unexpected timestr {:?}", timestr); - - let dt = today.and_hms_milli(1, 2, 3, 1234); - let timestr = dt.time().to_string(); - assert!( - timestr == "01:02:03.234" || timestr == "01:02:04.234", - "unexpected timestr {:?}", - timestr - ); - } -} diff --git a/third_party/rust/chrono/src/offset/local/mod.rs b/third_party/rust/chrono/src/offset/local/mod.rs new file mode 100644 index 00000000000..a1ba3fac652 --- /dev/null +++ b/third_party/rust/chrono/src/offset/local/mod.rs @@ -0,0 +1,541 @@ +// This is a part of Chrono. +// See README.md and LICENSE.txt for details. + +//! The local (system) time zone. + +#[cfg(windows)] +use std::cmp::Ordering; + +#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] +use rkyv::{Archive, Deserialize, Serialize}; + +use super::fixed::FixedOffset; +use super::{MappedLocalTime, TimeZone}; +#[allow(deprecated)] +use crate::Date; +use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; +use crate::{DateTime, Utc}; + +#[cfg(unix)] +#[path = "unix.rs"] +mod inner; + +#[cfg(windows)] +#[path = "windows.rs"] +mod inner; + +#[cfg(all(windows, feature = "clock"))] +#[allow(unreachable_pub)] +mod win_bindings; + +#[cfg(all( + not(unix), + not(windows), + not(all( + target_arch = "wasm32", + feature = "wasmbind", + not(any(target_os = "emscripten", target_os = "wasi")) + )) +))] +mod inner { + use crate::{FixedOffset, MappedLocalTime, NaiveDateTime}; + + pub(super) fn offset_from_utc_datetime( + _utc_time: &NaiveDateTime, + ) -> MappedLocalTime { + MappedLocalTime::Single(FixedOffset::east_opt(0).unwrap()) + } + + pub(super) fn offset_from_local_datetime( + _local_time: &NaiveDateTime, + ) -> MappedLocalTime { + MappedLocalTime::Single(FixedOffset::east_opt(0).unwrap()) + } +} + +#[cfg(all( + target_arch = "wasm32", + feature = "wasmbind", + not(any(target_os = "emscripten", target_os = "wasi")) +))] +mod inner { + use crate::{Datelike, FixedOffset, MappedLocalTime, NaiveDateTime, Timelike}; + + pub(super) fn offset_from_utc_datetime(utc: &NaiveDateTime) -> MappedLocalTime { + let offset = js_sys::Date::from(utc.and_utc()).get_timezone_offset(); + MappedLocalTime::Single(FixedOffset::west_opt((offset as i32) * 60).unwrap()) + } + + pub(super) fn offset_from_local_datetime( + local: &NaiveDateTime, + ) -> MappedLocalTime { + let mut year = local.year(); + if year < 100 { + // The API in `js_sys` does not let us create a `Date` with negative years. + // And values for years from `0` to `99` map to the years `1900` to `1999`. + // Shift the value by a multiple of 400 years until it is `>= 100`. + let shift_cycles = (year - 100).div_euclid(400); + year -= shift_cycles * 400; + } + let js_date = js_sys::Date::new_with_year_month_day_hr_min_sec( + year as u32, + local.month0() as i32, + local.day() as i32, + local.hour() as i32, + local.minute() as i32, + local.second() as i32, + // ignore milliseconds, our representation of leap seconds may be problematic + ); + let offset = js_date.get_timezone_offset(); + // We always get a result, even if this time does not exist or is ambiguous. + MappedLocalTime::Single(FixedOffset::west_opt((offset as i32) * 60).unwrap()) + } +} + +#[cfg(unix)] +mod tz_info; + +/// The local timescale. +/// +/// Using the [`TimeZone`](./trait.TimeZone.html) methods +/// on the Local struct is the preferred way to construct `DateTime` +/// instances. +/// +/// # Example +/// +/// ``` +/// use chrono::{DateTime, Local, TimeZone}; +/// +/// let dt1: DateTime = Local::now(); +/// let dt2: DateTime = Local.timestamp_opt(0, 0).unwrap(); +/// assert!(dt1 >= dt2); +/// ``` +#[derive(Copy, Clone, Debug)] +#[cfg_attr( + any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), + derive(Archive, Deserialize, Serialize), + archive(compare(PartialEq)), + archive_attr(derive(Clone, Copy, Debug)) +)] +#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +pub struct Local; + +impl Local { + /// Returns a `Date` which corresponds to the current date. + #[deprecated(since = "0.4.23", note = "use `Local::now()` instead")] + #[allow(deprecated)] + #[must_use] + pub fn today() -> Date { + Local::now().date() + } + + /// Returns a `DateTime` which corresponds to the current date, time and offset from + /// UTC. + /// + /// See also the similar [`Utc::now()`] which returns `DateTime`, i.e. without the local + /// offset. + /// + /// # Example + /// + /// ``` + /// # #![allow(unused_variables)] + /// # use chrono::{DateTime, FixedOffset, Local}; + /// // Current local time + /// let now = Local::now(); + /// + /// // Current local date + /// let today = now.date_naive(); + /// + /// // Current local time, converted to `DateTime` + /// let now_fixed_offset = Local::now().fixed_offset(); + /// // or + /// let now_fixed_offset: DateTime = Local::now().into(); + /// + /// // Current time in some timezone (let's use +05:00) + /// // Note that it is usually more efficient to use `Utc::now` for this use case. + /// let offset = FixedOffset::east_opt(5 * 60 * 60).unwrap(); + /// let now_with_offset = Local::now().with_timezone(&offset); + /// ``` + pub fn now() -> DateTime { + Utc::now().with_timezone(&Local) + } +} + +impl TimeZone for Local { + type Offset = FixedOffset; + + fn from_offset(_offset: &FixedOffset) -> Local { + Local + } + + #[allow(deprecated)] + fn offset_from_local_date(&self, local: &NaiveDate) -> MappedLocalTime { + // Get the offset at local midnight. + self.offset_from_local_datetime(&local.and_time(NaiveTime::MIN)) + } + + fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> MappedLocalTime { + inner::offset_from_local_datetime(local) + } + + #[allow(deprecated)] + fn offset_from_utc_date(&self, utc: &NaiveDate) -> FixedOffset { + // Get the offset at midnight. + self.offset_from_utc_datetime(&utc.and_time(NaiveTime::MIN)) + } + + fn offset_from_utc_datetime(&self, utc: &NaiveDateTime) -> FixedOffset { + inner::offset_from_utc_datetime(utc).unwrap() + } +} + +#[cfg(windows)] +#[derive(Copy, Clone, Eq, PartialEq)] +struct Transition { + transition_utc: NaiveDateTime, + offset_before: FixedOffset, + offset_after: FixedOffset, +} + +#[cfg(windows)] +impl Transition { + fn new( + transition_local: NaiveDateTime, + offset_before: FixedOffset, + offset_after: FixedOffset, + ) -> Transition { + // It is no problem if the transition time in UTC falls a couple of hours inside the buffer + // space around the `NaiveDateTime` range (although it is very theoretical to have a + // transition at midnight around `NaiveDate::(MIN|MAX)`. + let transition_utc = transition_local.overflowing_sub_offset(offset_before); + Transition { transition_utc, offset_before, offset_after } + } +} + +#[cfg(windows)] +impl PartialOrd for Transition { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.transition_utc.cmp(&other.transition_utc)) + } +} + +#[cfg(windows)] +impl Ord for Transition { + fn cmp(&self, other: &Self) -> Ordering { + self.transition_utc.cmp(&other.transition_utc) + } +} + +// Calculate the time in UTC given a local time and transitions. +// `transitions` must be sorted. +#[cfg(windows)] +fn lookup_with_dst_transitions( + transitions: &[Transition], + dt: NaiveDateTime, +) -> MappedLocalTime { + for t in transitions.iter() { + // A transition can result in the wall clock time going forward (creating a gap) or going + // backward (creating a fold). We are interested in the earliest and latest wall time of the + // transition, as this are the times between which `dt` does may not exist or is ambiguous. + // + // It is no problem if the transition times falls a couple of hours inside the buffer + // space around the `NaiveDateTime` range (although it is very theoretical to have a + // transition at midnight around `NaiveDate::(MIN|MAX)`. + let (offset_min, offset_max) = + match t.offset_after.local_minus_utc() > t.offset_before.local_minus_utc() { + true => (t.offset_before, t.offset_after), + false => (t.offset_after, t.offset_before), + }; + let wall_earliest = t.transition_utc.overflowing_add_offset(offset_min); + let wall_latest = t.transition_utc.overflowing_add_offset(offset_max); + + if dt < wall_earliest { + return MappedLocalTime::Single(t.offset_before); + } else if dt <= wall_latest { + return match t.offset_after.local_minus_utc().cmp(&t.offset_before.local_minus_utc()) { + Ordering::Equal => MappedLocalTime::Single(t.offset_before), + Ordering::Less => MappedLocalTime::Ambiguous(t.offset_before, t.offset_after), + Ordering::Greater => { + if dt == wall_earliest { + MappedLocalTime::Single(t.offset_before) + } else if dt == wall_latest { + MappedLocalTime::Single(t.offset_after) + } else { + MappedLocalTime::None + } + } + }; + } + } + MappedLocalTime::Single(transitions.last().unwrap().offset_after) +} + +#[cfg(test)] +mod tests { + use super::Local; + use crate::offset::TimeZone; + #[cfg(windows)] + use crate::offset::local::{Transition, lookup_with_dst_transitions}; + use crate::{Datelike, Days, Utc}; + #[cfg(windows)] + use crate::{FixedOffset, MappedLocalTime, NaiveDate, NaiveDateTime}; + + #[test] + fn verify_correct_offsets() { + let now = Local::now(); + let from_local = Local.from_local_datetime(&now.naive_local()).unwrap(); + let from_utc = Local.from_utc_datetime(&now.naive_utc()); + + assert_eq!(now.offset().local_minus_utc(), from_local.offset().local_minus_utc()); + assert_eq!(now.offset().local_minus_utc(), from_utc.offset().local_minus_utc()); + + assert_eq!(now, from_local); + assert_eq!(now, from_utc); + } + + #[test] + fn verify_correct_offsets_distant_past() { + let distant_past = Local::now() - Days::new(365 * 500); + let from_local = Local.from_local_datetime(&distant_past.naive_local()).unwrap(); + let from_utc = Local.from_utc_datetime(&distant_past.naive_utc()); + + assert_eq!(distant_past.offset().local_minus_utc(), from_local.offset().local_minus_utc()); + assert_eq!(distant_past.offset().local_minus_utc(), from_utc.offset().local_minus_utc()); + + assert_eq!(distant_past, from_local); + assert_eq!(distant_past, from_utc); + } + + #[test] + fn verify_correct_offsets_distant_future() { + let distant_future = Local::now() + Days::new(365 * 35000); + let from_local = Local.from_local_datetime(&distant_future.naive_local()).unwrap(); + let from_utc = Local.from_utc_datetime(&distant_future.naive_utc()); + + assert_eq!( + distant_future.offset().local_minus_utc(), + from_local.offset().local_minus_utc() + ); + assert_eq!(distant_future.offset().local_minus_utc(), from_utc.offset().local_minus_utc()); + + assert_eq!(distant_future, from_local); + assert_eq!(distant_future, from_utc); + } + + #[test] + fn test_local_date_sanity_check() { + // issue #27 + assert_eq!(Local.with_ymd_and_hms(2999, 12, 28, 0, 0, 0).unwrap().day(), 28); + } + + #[test] + fn test_leap_second() { + // issue #123 + let today = Utc::now().date_naive(); + + if let Some(dt) = today.and_hms_milli_opt(15, 2, 59, 1000) { + let timestr = dt.time().to_string(); + // the OS API may or may not support the leap second, + // but there are only two sensible options. + assert!( + timestr == "15:02:60" || timestr == "15:03:00", + "unexpected timestr {:?}", + timestr + ); + } + + if let Some(dt) = today.and_hms_milli_opt(15, 2, 3, 1234) { + let timestr = dt.time().to_string(); + assert!( + timestr == "15:02:03.234" || timestr == "15:02:04.234", + "unexpected timestr {:?}", + timestr + ); + } + } + + #[test] + #[cfg(windows)] + fn test_lookup_with_dst_transitions() { + let ymdhms = |y, m, d, h, n, s| { + NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap() + }; + + #[track_caller] + #[allow(clippy::too_many_arguments)] + fn compare_lookup( + transitions: &[Transition], + y: i32, + m: u32, + d: u32, + h: u32, + n: u32, + s: u32, + result: MappedLocalTime, + ) { + let dt = NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap(); + assert_eq!(lookup_with_dst_transitions(transitions, dt), result); + } + + // dst transition before std transition + // dst offset > std offset + let std = FixedOffset::east_opt(3 * 60 * 60).unwrap(); + let dst = FixedOffset::east_opt(4 * 60 * 60).unwrap(); + let transitions = [ + Transition::new(ymdhms(2023, 3, 26, 2, 0, 0), std, dst), + Transition::new(ymdhms(2023, 10, 29, 3, 0, 0), dst, std), + ]; + compare_lookup(&transitions, 2023, 3, 26, 1, 0, 0, MappedLocalTime::Single(std)); + compare_lookup(&transitions, 2023, 3, 26, 2, 0, 0, MappedLocalTime::Single(std)); + compare_lookup(&transitions, 2023, 3, 26, 2, 30, 0, MappedLocalTime::None); + compare_lookup(&transitions, 2023, 3, 26, 3, 0, 0, MappedLocalTime::Single(dst)); + compare_lookup(&transitions, 2023, 3, 26, 4, 0, 0, MappedLocalTime::Single(dst)); + + compare_lookup(&transitions, 2023, 10, 29, 1, 0, 0, MappedLocalTime::Single(dst)); + compare_lookup(&transitions, 2023, 10, 29, 2, 0, 0, MappedLocalTime::Ambiguous(dst, std)); + compare_lookup(&transitions, 2023, 10, 29, 2, 30, 0, MappedLocalTime::Ambiguous(dst, std)); + compare_lookup(&transitions, 2023, 10, 29, 3, 0, 0, MappedLocalTime::Ambiguous(dst, std)); + compare_lookup(&transitions, 2023, 10, 29, 4, 0, 0, MappedLocalTime::Single(std)); + + // std transition before dst transition + // dst offset > std offset + let std = FixedOffset::east_opt(-5 * 60 * 60).unwrap(); + let dst = FixedOffset::east_opt(-4 * 60 * 60).unwrap(); + let transitions = [ + Transition::new(ymdhms(2023, 3, 24, 3, 0, 0), dst, std), + Transition::new(ymdhms(2023, 10, 27, 2, 0, 0), std, dst), + ]; + compare_lookup(&transitions, 2023, 3, 24, 1, 0, 0, MappedLocalTime::Single(dst)); + compare_lookup(&transitions, 2023, 3, 24, 2, 0, 0, MappedLocalTime::Ambiguous(dst, std)); + compare_lookup(&transitions, 2023, 3, 24, 2, 30, 0, MappedLocalTime::Ambiguous(dst, std)); + compare_lookup(&transitions, 2023, 3, 24, 3, 0, 0, MappedLocalTime::Ambiguous(dst, std)); + compare_lookup(&transitions, 2023, 3, 24, 4, 0, 0, MappedLocalTime::Single(std)); + + compare_lookup(&transitions, 2023, 10, 27, 1, 0, 0, MappedLocalTime::Single(std)); + compare_lookup(&transitions, 2023, 10, 27, 2, 0, 0, MappedLocalTime::Single(std)); + compare_lookup(&transitions, 2023, 10, 27, 2, 30, 0, MappedLocalTime::None); + compare_lookup(&transitions, 2023, 10, 27, 3, 0, 0, MappedLocalTime::Single(dst)); + compare_lookup(&transitions, 2023, 10, 27, 4, 0, 0, MappedLocalTime::Single(dst)); + + // dst transition before std transition + // dst offset < std offset + let std = FixedOffset::east_opt(3 * 60 * 60).unwrap(); + let dst = FixedOffset::east_opt((2 * 60 + 30) * 60).unwrap(); + let transitions = [ + Transition::new(ymdhms(2023, 3, 26, 2, 30, 0), std, dst), + Transition::new(ymdhms(2023, 10, 29, 2, 0, 0), dst, std), + ]; + compare_lookup(&transitions, 2023, 3, 26, 1, 0, 0, MappedLocalTime::Single(std)); + compare_lookup(&transitions, 2023, 3, 26, 2, 0, 0, MappedLocalTime::Ambiguous(std, dst)); + compare_lookup(&transitions, 2023, 3, 26, 2, 15, 0, MappedLocalTime::Ambiguous(std, dst)); + compare_lookup(&transitions, 2023, 3, 26, 2, 30, 0, MappedLocalTime::Ambiguous(std, dst)); + compare_lookup(&transitions, 2023, 3, 26, 3, 0, 0, MappedLocalTime::Single(dst)); + + compare_lookup(&transitions, 2023, 10, 29, 1, 0, 0, MappedLocalTime::Single(dst)); + compare_lookup(&transitions, 2023, 10, 29, 2, 0, 0, MappedLocalTime::Single(dst)); + compare_lookup(&transitions, 2023, 10, 29, 2, 15, 0, MappedLocalTime::None); + compare_lookup(&transitions, 2023, 10, 29, 2, 30, 0, MappedLocalTime::Single(std)); + compare_lookup(&transitions, 2023, 10, 29, 3, 0, 0, MappedLocalTime::Single(std)); + + // std transition before dst transition + // dst offset < std offset + let std = FixedOffset::east_opt(-(4 * 60 + 30) * 60).unwrap(); + let dst = FixedOffset::east_opt(-5 * 60 * 60).unwrap(); + let transitions = [ + Transition::new(ymdhms(2023, 3, 24, 2, 0, 0), dst, std), + Transition::new(ymdhms(2023, 10, 27, 2, 30, 0), std, dst), + ]; + compare_lookup(&transitions, 2023, 3, 24, 1, 0, 0, MappedLocalTime::Single(dst)); + compare_lookup(&transitions, 2023, 3, 24, 2, 0, 0, MappedLocalTime::Single(dst)); + compare_lookup(&transitions, 2023, 3, 24, 2, 15, 0, MappedLocalTime::None); + compare_lookup(&transitions, 2023, 3, 24, 2, 30, 0, MappedLocalTime::Single(std)); + compare_lookup(&transitions, 2023, 3, 24, 3, 0, 0, MappedLocalTime::Single(std)); + + compare_lookup(&transitions, 2023, 10, 27, 1, 0, 0, MappedLocalTime::Single(std)); + compare_lookup(&transitions, 2023, 10, 27, 2, 0, 0, MappedLocalTime::Ambiguous(std, dst)); + compare_lookup(&transitions, 2023, 10, 27, 2, 15, 0, MappedLocalTime::Ambiguous(std, dst)); + compare_lookup(&transitions, 2023, 10, 27, 2, 30, 0, MappedLocalTime::Ambiguous(std, dst)); + compare_lookup(&transitions, 2023, 10, 27, 3, 0, 0, MappedLocalTime::Single(dst)); + + // offset stays the same + let std = FixedOffset::east_opt(3 * 60 * 60).unwrap(); + let transitions = [ + Transition::new(ymdhms(2023, 3, 26, 2, 0, 0), std, std), + Transition::new(ymdhms(2023, 10, 29, 3, 0, 0), std, std), + ]; + compare_lookup(&transitions, 2023, 3, 26, 2, 0, 0, MappedLocalTime::Single(std)); + compare_lookup(&transitions, 2023, 10, 29, 3, 0, 0, MappedLocalTime::Single(std)); + + // single transition + let std = FixedOffset::east_opt(3 * 60 * 60).unwrap(); + let dst = FixedOffset::east_opt(4 * 60 * 60).unwrap(); + let transitions = [Transition::new(ymdhms(2023, 3, 26, 2, 0, 0), std, dst)]; + compare_lookup(&transitions, 2023, 3, 26, 1, 0, 0, MappedLocalTime::Single(std)); + compare_lookup(&transitions, 2023, 3, 26, 2, 0, 0, MappedLocalTime::Single(std)); + compare_lookup(&transitions, 2023, 3, 26, 2, 30, 0, MappedLocalTime::None); + compare_lookup(&transitions, 2023, 3, 26, 3, 0, 0, MappedLocalTime::Single(dst)); + compare_lookup(&transitions, 2023, 3, 26, 4, 0, 0, MappedLocalTime::Single(dst)); + } + + #[test] + #[cfg(windows)] + fn test_lookup_with_dst_transitions_limits() { + // Transition beyond UTC year end doesn't panic in year of `NaiveDate::MAX` + let std = FixedOffset::east_opt(3 * 60 * 60).unwrap(); + let dst = FixedOffset::east_opt(4 * 60 * 60).unwrap(); + let transitions = [ + Transition::new(NaiveDateTime::MAX.with_month(7).unwrap(), std, dst), + Transition::new(NaiveDateTime::MAX, dst, std), + ]; + assert_eq!( + lookup_with_dst_transitions(&transitions, NaiveDateTime::MAX.with_month(3).unwrap()), + MappedLocalTime::Single(std) + ); + assert_eq!( + lookup_with_dst_transitions(&transitions, NaiveDateTime::MAX.with_month(8).unwrap()), + MappedLocalTime::Single(dst) + ); + // Doesn't panic with `NaiveDateTime::MAX` as argument (which would be out of range when + // converted to UTC). + assert_eq!( + lookup_with_dst_transitions(&transitions, NaiveDateTime::MAX), + MappedLocalTime::Ambiguous(dst, std) + ); + + // Transition before UTC year end doesn't panic in year of `NaiveDate::MIN` + let std = FixedOffset::west_opt(3 * 60 * 60).unwrap(); + let dst = FixedOffset::west_opt(4 * 60 * 60).unwrap(); + let transitions = [ + Transition::new(NaiveDateTime::MIN, std, dst), + Transition::new(NaiveDateTime::MIN.with_month(6).unwrap(), dst, std), + ]; + assert_eq!( + lookup_with_dst_transitions(&transitions, NaiveDateTime::MIN.with_month(3).unwrap()), + MappedLocalTime::Single(dst) + ); + assert_eq!( + lookup_with_dst_transitions(&transitions, NaiveDateTime::MIN.with_month(8).unwrap()), + MappedLocalTime::Single(std) + ); + // Doesn't panic with `NaiveDateTime::MIN` as argument (which would be out of range when + // converted to UTC). + assert_eq!( + lookup_with_dst_transitions(&transitions, NaiveDateTime::MIN), + MappedLocalTime::Ambiguous(std, dst) + ); + } + + #[test] + #[cfg(feature = "rkyv-validation")] + fn test_rkyv_validation() { + let local = Local; + // Local is a ZST and serializes to 0 bytes + let bytes = rkyv::to_bytes::<_, 0>(&local).unwrap(); + assert_eq!(bytes.len(), 0); + + // but is deserialized to an archived variant without a + // wrapping object + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), super::ArchivedLocal); + } +} diff --git a/third_party/rust/chrono/src/offset/local/tz_info/mod.rs b/third_party/rust/chrono/src/offset/local/tz_info/mod.rs new file mode 100644 index 00000000000..780e15ace97 --- /dev/null +++ b/third_party/rust/chrono/src/offset/local/tz_info/mod.rs @@ -0,0 +1,116 @@ +#![deny(missing_docs)] +#![allow(dead_code)] +#![warn(unreachable_pub)] + +use std::num::ParseIntError; +use std::str::Utf8Error; +use std::time::SystemTimeError; +use std::{error, fmt, io}; + +mod timezone; +pub(crate) use timezone::TimeZone; + +mod parser; +mod rule; + +/// Unified error type for everything in the crate +#[derive(Debug)] +pub(crate) enum Error { + /// Date time error + DateTime(&'static str), + /// Local time type search error + FindLocalTimeType(&'static str), + /// Local time type error + LocalTimeType(&'static str), + /// Invalid slice for integer conversion + InvalidSlice(&'static str), + /// Invalid Tzif file + InvalidTzFile(&'static str), + /// Invalid TZ string + InvalidTzString(&'static str), + /// I/O error + Io(io::Error), + /// Out of range error + OutOfRange(&'static str), + /// Integer parsing error + ParseInt(ParseIntError), + /// Date time projection error + ProjectDateTime(&'static str), + /// System time error + SystemTime(SystemTimeError), + /// Time zone error + TimeZone(&'static str), + /// Transition rule error + TransitionRule(&'static str), + /// Unsupported Tzif file + UnsupportedTzFile(&'static str), + /// Unsupported TZ string + UnsupportedTzString(&'static str), + /// UTF-8 error + Utf8(Utf8Error), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use Error::*; + match self { + DateTime(error) => write!(f, "invalid date time: {}", error), + FindLocalTimeType(error) => error.fmt(f), + LocalTimeType(error) => write!(f, "invalid local time type: {}", error), + InvalidSlice(error) => error.fmt(f), + InvalidTzString(error) => write!(f, "invalid TZ string: {}", error), + InvalidTzFile(error) => error.fmt(f), + Io(error) => error.fmt(f), + OutOfRange(error) => error.fmt(f), + ParseInt(error) => error.fmt(f), + ProjectDateTime(error) => error.fmt(f), + SystemTime(error) => error.fmt(f), + TransitionRule(error) => write!(f, "invalid transition rule: {}", error), + TimeZone(error) => write!(f, "invalid time zone: {}", error), + UnsupportedTzFile(error) => error.fmt(f), + UnsupportedTzString(error) => write!(f, "unsupported TZ string: {}", error), + Utf8(error) => error.fmt(f), + } + } +} + +impl error::Error for Error {} + +impl From for Error { + fn from(error: io::Error) -> Self { + Error::Io(error) + } +} + +impl From for Error { + fn from(error: ParseIntError) -> Self { + Error::ParseInt(error) + } +} + +impl From for Error { + fn from(error: SystemTimeError) -> Self { + Error::SystemTime(error) + } +} + +impl From for Error { + fn from(error: Utf8Error) -> Self { + Error::Utf8(error) + } +} + +/// Number of hours in one day +const HOURS_PER_DAY: i64 = 24; +/// Number of seconds in one hour +const SECONDS_PER_HOUR: i64 = 3600; +/// Number of seconds in one day +const SECONDS_PER_DAY: i64 = SECONDS_PER_HOUR * HOURS_PER_DAY; +/// Number of days in one week +const DAYS_PER_WEEK: i64 = 7; + +/// Month days in a normal year +const DAY_IN_MONTHS_NORMAL_YEAR: [i64; 12] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; +/// Cumulated month days in a normal year +const CUMUL_DAY_IN_MONTHS_NORMAL_YEAR: [i64; 12] = + [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]; diff --git a/third_party/rust/chrono/src/offset/local/tz_info/parser.rs b/third_party/rust/chrono/src/offset/local/tz_info/parser.rs new file mode 100644 index 00000000000..84c99063c15 --- /dev/null +++ b/third_party/rust/chrono/src/offset/local/tz_info/parser.rs @@ -0,0 +1,348 @@ +use std::io::{self, ErrorKind}; +use std::iter; +use std::num::ParseIntError; +use std::str::{self, FromStr}; + +use super::Error; +use super::rule::TransitionRule; +use super::timezone::{LeapSecond, LocalTimeType, TimeZone, Transition}; + +pub(super) fn parse(bytes: &[u8]) -> Result { + let mut cursor = Cursor::new(bytes); + let state = State::new(&mut cursor, true)?; + let (state, footer) = match state.header.version { + Version::V1 => match cursor.is_empty() { + true => (state, None), + false => { + return Err(Error::InvalidTzFile("remaining data after end of TZif v1 data block")); + } + }, + Version::V2 | Version::V3 => { + let state = State::new(&mut cursor, false)?; + (state, Some(cursor.remaining())) + } + }; + + let mut transitions = Vec::with_capacity(state.header.transition_count); + for (arr_time, &local_time_type_index) in + state.transition_times.chunks_exact(state.time_size).zip(state.transition_types) + { + let unix_leap_time = + state.parse_time(&arr_time[0..state.time_size], state.header.version)?; + let local_time_type_index = local_time_type_index as usize; + transitions.push(Transition::new(unix_leap_time, local_time_type_index)); + } + + let mut local_time_types = Vec::with_capacity(state.header.type_count); + for arr in state.local_time_types.chunks_exact(6) { + let ut_offset = read_be_i32(&arr[..4])?; + + let is_dst = match arr[4] { + 0 => false, + 1 => true, + _ => return Err(Error::InvalidTzFile("invalid DST indicator")), + }; + + let char_index = arr[5] as usize; + if char_index >= state.header.char_count { + return Err(Error::InvalidTzFile("invalid time zone name char index")); + } + + let position = match state.names[char_index..].iter().position(|&c| c == b'\0') { + Some(position) => position, + None => return Err(Error::InvalidTzFile("invalid time zone name char index")), + }; + + let name = &state.names[char_index..char_index + position]; + let name = if !name.is_empty() { Some(name) } else { None }; + local_time_types.push(LocalTimeType::new(ut_offset, is_dst, name)?); + } + + let mut leap_seconds = Vec::with_capacity(state.header.leap_count); + for arr in state.leap_seconds.chunks_exact(state.time_size + 4) { + let unix_leap_time = state.parse_time(&arr[0..state.time_size], state.header.version)?; + let correction = read_be_i32(&arr[state.time_size..state.time_size + 4])?; + leap_seconds.push(LeapSecond::new(unix_leap_time, correction)); + } + + let std_walls_iter = state.std_walls.iter().copied().chain(iter::repeat(0)); + let ut_locals_iter = state.ut_locals.iter().copied().chain(iter::repeat(0)); + if std_walls_iter.zip(ut_locals_iter).take(state.header.type_count).any(|pair| pair == (0, 1)) { + return Err(Error::InvalidTzFile( + "invalid couple of standard/wall and UT/local indicators", + )); + } + + let extra_rule = match footer { + Some(footer) => { + let footer = str::from_utf8(footer)?; + if !(footer.starts_with('\n') && footer.ends_with('\n')) { + return Err(Error::InvalidTzFile("invalid footer")); + } + + let tz_string = footer.trim_matches(|c: char| c.is_ascii_whitespace()); + if tz_string.starts_with(':') || tz_string.contains('\0') { + return Err(Error::InvalidTzFile("invalid footer")); + } + + match tz_string.is_empty() { + true => None, + false => Some(TransitionRule::from_tz_string( + tz_string.as_bytes(), + state.header.version == Version::V3, + )?), + } + } + None => None, + }; + + TimeZone::new(transitions, local_time_types, leap_seconds, extra_rule) +} + +/// TZif data blocks +struct State<'a> { + header: Header, + /// Time size in bytes + time_size: usize, + /// Transition times data block + transition_times: &'a [u8], + /// Transition types data block + transition_types: &'a [u8], + /// Local time types data block + local_time_types: &'a [u8], + /// Time zone names data block + names: &'a [u8], + /// Leap seconds data block + leap_seconds: &'a [u8], + /// UT/local indicators data block + std_walls: &'a [u8], + /// Standard/wall indicators data block + ut_locals: &'a [u8], +} + +impl<'a> State<'a> { + /// Read TZif data blocks + fn new(cursor: &mut Cursor<'a>, first: bool) -> Result { + let header = Header::new(cursor)?; + let time_size = match first { + true => 4, // We always parse V1 first + false => 8, + }; + + Ok(Self { + time_size, + transition_times: cursor.read_exact(header.transition_count * time_size)?, + transition_types: cursor.read_exact(header.transition_count)?, + local_time_types: cursor.read_exact(header.type_count * 6)?, + names: cursor.read_exact(header.char_count)?, + leap_seconds: cursor.read_exact(header.leap_count * (time_size + 4))?, + std_walls: cursor.read_exact(header.std_wall_count)?, + ut_locals: cursor.read_exact(header.ut_local_count)?, + header, + }) + } + + /// Parse time values + fn parse_time(&self, arr: &[u8], version: Version) -> Result { + match version { + Version::V1 => Ok(read_be_i32(&arr[..4])?.into()), + Version::V2 | Version::V3 => read_be_i64(arr), + } + } +} + +/// TZif header +#[derive(Debug)] +struct Header { + /// TZif version + version: Version, + /// Number of UT/local indicators + ut_local_count: usize, + /// Number of standard/wall indicators + std_wall_count: usize, + /// Number of leap-second records + leap_count: usize, + /// Number of transition times + transition_count: usize, + /// Number of local time type records + type_count: usize, + /// Number of time zone names bytes + char_count: usize, +} + +impl Header { + fn new(cursor: &mut Cursor) -> Result { + let magic = cursor.read_exact(4)?; + if magic != *b"TZif" { + return Err(Error::InvalidTzFile("invalid magic number")); + } + + let version = match cursor.read_exact(1)? { + [0x00] => Version::V1, + [0x32] => Version::V2, + [0x33] => Version::V3, + _ => return Err(Error::UnsupportedTzFile("unsupported TZif version")), + }; + + cursor.read_exact(15)?; + let ut_local_count = cursor.read_be_u32()?; + let std_wall_count = cursor.read_be_u32()?; + let leap_count = cursor.read_be_u32()?; + let transition_count = cursor.read_be_u32()?; + let type_count = cursor.read_be_u32()?; + let char_count = cursor.read_be_u32()?; + + if !(type_count != 0 + && char_count != 0 + && (ut_local_count == 0 || ut_local_count == type_count) + && (std_wall_count == 0 || std_wall_count == type_count)) + { + return Err(Error::InvalidTzFile("invalid header")); + } + + Ok(Self { + version, + ut_local_count: ut_local_count as usize, + std_wall_count: std_wall_count as usize, + leap_count: leap_count as usize, + transition_count: transition_count as usize, + type_count: type_count as usize, + char_count: char_count as usize, + }) + } +} + +/// A `Cursor` contains a slice of a buffer and a read count. +#[derive(Debug, Eq, PartialEq)] +pub(crate) struct Cursor<'a> { + /// Slice representing the remaining data to be read + remaining: &'a [u8], + /// Number of already read bytes + read_count: usize, +} + +impl<'a> Cursor<'a> { + /// Construct a new `Cursor` from remaining data + pub(crate) const fn new(remaining: &'a [u8]) -> Self { + Self { remaining, read_count: 0 } + } + + pub(crate) fn peek(&self) -> Option<&u8> { + self.remaining().first() + } + + /// Returns remaining data + pub(crate) const fn remaining(&self) -> &'a [u8] { + self.remaining + } + + /// Returns `true` if data is remaining + pub(crate) const fn is_empty(&self) -> bool { + self.remaining.is_empty() + } + + pub(crate) fn read_be_u32(&mut self) -> Result { + let mut buf = [0; 4]; + buf.copy_from_slice(self.read_exact(4)?); + Ok(u32::from_be_bytes(buf)) + } + + #[cfg(target_env = "ohos")] + pub(crate) fn seek_after(&mut self, offset: usize) -> Result { + if offset < self.read_count { + return Err(io::Error::from(ErrorKind::UnexpectedEof)); + } + match self.remaining.get((offset - self.read_count)..) { + Some(remaining) => { + self.remaining = remaining; + self.read_count = offset; + Ok(offset) + } + _ => Err(io::Error::from(ErrorKind::UnexpectedEof)), + } + } + + /// Read exactly `count` bytes, reducing remaining data and incrementing read count + pub(crate) fn read_exact(&mut self, count: usize) -> Result<&'a [u8], io::Error> { + match (self.remaining.get(..count), self.remaining.get(count..)) { + (Some(result), Some(remaining)) => { + self.remaining = remaining; + self.read_count += count; + Ok(result) + } + _ => Err(io::Error::from(ErrorKind::UnexpectedEof)), + } + } + + /// Read bytes and compare them to the provided tag + pub(crate) fn read_tag(&mut self, tag: &[u8]) -> Result<(), io::Error> { + if self.read_exact(tag.len())? == tag { + Ok(()) + } else { + Err(io::Error::from(ErrorKind::InvalidData)) + } + } + + /// Read bytes if the remaining data is prefixed by the provided tag + pub(crate) fn read_optional_tag(&mut self, tag: &[u8]) -> Result { + if self.remaining.starts_with(tag) { + self.read_exact(tag.len())?; + Ok(true) + } else { + Ok(false) + } + } + + /// Read bytes as long as the provided predicate is true + pub(crate) fn read_while bool>(&mut self, f: F) -> Result<&'a [u8], io::Error> { + match self.remaining.iter().position(|x| !f(x)) { + None => self.read_exact(self.remaining.len()), + Some(position) => self.read_exact(position), + } + } + + // Parse an integer out of the ASCII digits + pub(crate) fn read_int>(&mut self) -> Result { + let bytes = self.read_while(u8::is_ascii_digit)?; + Ok(str::from_utf8(bytes)?.parse()?) + } + + /// Read bytes until the provided predicate is true + pub(crate) fn read_until bool>(&mut self, f: F) -> Result<&'a [u8], io::Error> { + match self.remaining.iter().position(f) { + None => self.read_exact(self.remaining.len()), + Some(position) => self.read_exact(position), + } + } +} + +pub(crate) fn read_be_i32(bytes: &[u8]) -> Result { + if bytes.len() != 4 { + return Err(Error::InvalidSlice("too short for i32")); + } + + let mut buf = [0; 4]; + buf.copy_from_slice(bytes); + Ok(i32::from_be_bytes(buf)) +} + +pub(crate) fn read_be_i64(bytes: &[u8]) -> Result { + if bytes.len() != 8 { + return Err(Error::InvalidSlice("too short for i64")); + } + + let mut buf = [0; 8]; + buf.copy_from_slice(bytes); + Ok(i64::from_be_bytes(buf)) +} + +/// TZif version +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +enum Version { + /// Version 1 + V1, + /// Version 2 + V2, + /// Version 3 + V3, +} diff --git a/third_party/rust/chrono/src/offset/local/tz_info/rule.rs b/third_party/rust/chrono/src/offset/local/tz_info/rule.rs new file mode 100644 index 00000000000..d428395db0c --- /dev/null +++ b/third_party/rust/chrono/src/offset/local/tz_info/rule.rs @@ -0,0 +1,1037 @@ +use super::parser::Cursor; +use super::timezone::{LocalTimeType, SECONDS_PER_WEEK}; +use super::{ + CUMUL_DAY_IN_MONTHS_NORMAL_YEAR, DAY_IN_MONTHS_NORMAL_YEAR, DAYS_PER_WEEK, Error, + SECONDS_PER_DAY, +}; +use crate::{Datelike, NaiveDateTime}; +use std::cmp::Ordering; + +/// Transition rule +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub(super) enum TransitionRule { + /// Fixed local time type + Fixed(LocalTimeType), + /// Alternate local time types + Alternate(AlternateTime), +} + +impl TransitionRule { + /// Parse a POSIX TZ string containing a time zone description, as described in [the POSIX documentation of the `TZ` environment variable](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html). + /// + /// TZ string extensions from [RFC 8536](https://datatracker.ietf.org/doc/html/rfc8536#section-3.3.1) may be used. + pub(super) fn from_tz_string( + tz_string: &[u8], + use_string_extensions: bool, + ) -> Result { + let mut cursor = Cursor::new(tz_string); + + let std_time_zone = Some(parse_name(&mut cursor)?); + let std_offset = parse_offset(&mut cursor)?; + + if cursor.is_empty() { + return Ok(LocalTimeType::new(-std_offset, false, std_time_zone)?.into()); + } + + let dst_time_zone = Some(parse_name(&mut cursor)?); + + let dst_offset = match cursor.peek() { + Some(&b',') => std_offset - 3600, + Some(_) => parse_offset(&mut cursor)?, + None => { + return Err(Error::UnsupportedTzString("DST start and end rules must be provided")); + } + }; + + if cursor.is_empty() { + return Err(Error::UnsupportedTzString("DST start and end rules must be provided")); + } + + cursor.read_tag(b",")?; + let (dst_start, dst_start_time) = RuleDay::parse(&mut cursor, use_string_extensions)?; + + cursor.read_tag(b",")?; + let (dst_end, dst_end_time) = RuleDay::parse(&mut cursor, use_string_extensions)?; + + if !cursor.is_empty() { + return Err(Error::InvalidTzString("remaining data after parsing TZ string")); + } + + Ok(AlternateTime::new( + LocalTimeType::new(-std_offset, false, std_time_zone)?, + LocalTimeType::new(-dst_offset, true, dst_time_zone)?, + dst_start, + dst_start_time, + dst_end, + dst_end_time, + )? + .into()) + } + + /// Find the local time type associated to the transition rule at the specified Unix time in seconds + pub(super) fn find_local_time_type(&self, unix_time: i64) -> Result<&LocalTimeType, Error> { + match self { + TransitionRule::Fixed(local_time_type) => Ok(local_time_type), + TransitionRule::Alternate(alternate_time) => { + alternate_time.find_local_time_type(unix_time) + } + } + } + + /// Find the local time type associated to the transition rule at the specified Unix time in seconds + pub(super) fn find_local_time_type_from_local( + &self, + local_time: NaiveDateTime, + ) -> Result, Error> { + match self { + TransitionRule::Fixed(local_time_type) => { + Ok(crate::MappedLocalTime::Single(*local_time_type)) + } + TransitionRule::Alternate(alternate_time) => { + alternate_time.find_local_time_type_from_local(local_time) + } + } + } +} + +impl From for TransitionRule { + fn from(inner: LocalTimeType) -> Self { + TransitionRule::Fixed(inner) + } +} + +impl From for TransitionRule { + fn from(inner: AlternateTime) -> Self { + TransitionRule::Alternate(inner) + } +} + +/// Transition rule representing alternate local time types +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub(super) struct AlternateTime { + /// Local time type for standard time + pub(super) std: LocalTimeType, + /// Local time type for Daylight Saving Time + pub(super) dst: LocalTimeType, + /// Start day of Daylight Saving Time + dst_start: RuleDay, + /// Local start day time of Daylight Saving Time, in seconds + dst_start_time: i32, + /// End day of Daylight Saving Time + dst_end: RuleDay, + /// Local end day time of Daylight Saving Time, in seconds + dst_end_time: i32, +} + +impl AlternateTime { + /// Construct a transition rule representing alternate local time types + const fn new( + std: LocalTimeType, + dst: LocalTimeType, + dst_start: RuleDay, + dst_start_time: i32, + dst_end: RuleDay, + dst_end_time: i32, + ) -> Result { + // Overflow is not possible + if !((dst_start_time as i64).abs() < SECONDS_PER_WEEK + && (dst_end_time as i64).abs() < SECONDS_PER_WEEK) + { + return Err(Error::TransitionRule("invalid DST start or end time")); + } + + Ok(Self { std, dst, dst_start, dst_start_time, dst_end, dst_end_time }) + } + + /// Find the local time type associated to the alternate transition rule at the specified Unix time in seconds + fn find_local_time_type(&self, unix_time: i64) -> Result<&LocalTimeType, Error> { + // Overflow is not possible + let dst_start_time_in_utc = self.dst_start_time as i64 - self.std.ut_offset as i64; + let dst_end_time_in_utc = self.dst_end_time as i64 - self.dst.ut_offset as i64; + + let current_year = match UtcDateTime::from_timespec(unix_time) { + Ok(dt) => dt.year, + Err(error) => return Err(error), + }; + + // Check if the current year is valid for the following computations + if !(i32::MIN + 2..=i32::MAX - 2).contains(¤t_year) { + return Err(Error::OutOfRange("out of range date time")); + } + + let current_year_dst_start_unix_time = + self.dst_start.unix_time(current_year, dst_start_time_in_utc); + let current_year_dst_end_unix_time = + self.dst_end.unix_time(current_year, dst_end_time_in_utc); + + // Check DST start/end Unix times for previous/current/next years to support for transition day times outside of [0h, 24h] range + let is_dst = + match Ord::cmp(¤t_year_dst_start_unix_time, ¤t_year_dst_end_unix_time) { + Ordering::Less | Ordering::Equal => { + if unix_time < current_year_dst_start_unix_time { + let previous_year_dst_end_unix_time = + self.dst_end.unix_time(current_year - 1, dst_end_time_in_utc); + if unix_time < previous_year_dst_end_unix_time { + let previous_year_dst_start_unix_time = + self.dst_start.unix_time(current_year - 1, dst_start_time_in_utc); + previous_year_dst_start_unix_time <= unix_time + } else { + false + } + } else if unix_time < current_year_dst_end_unix_time { + true + } else { + let next_year_dst_start_unix_time = + self.dst_start.unix_time(current_year + 1, dst_start_time_in_utc); + if next_year_dst_start_unix_time <= unix_time { + let next_year_dst_end_unix_time = + self.dst_end.unix_time(current_year + 1, dst_end_time_in_utc); + unix_time < next_year_dst_end_unix_time + } else { + false + } + } + } + Ordering::Greater => { + if unix_time < current_year_dst_end_unix_time { + let previous_year_dst_start_unix_time = + self.dst_start.unix_time(current_year - 1, dst_start_time_in_utc); + if unix_time < previous_year_dst_start_unix_time { + let previous_year_dst_end_unix_time = + self.dst_end.unix_time(current_year - 1, dst_end_time_in_utc); + unix_time < previous_year_dst_end_unix_time + } else { + true + } + } else if unix_time < current_year_dst_start_unix_time { + false + } else { + let next_year_dst_end_unix_time = + self.dst_end.unix_time(current_year + 1, dst_end_time_in_utc); + if next_year_dst_end_unix_time <= unix_time { + let next_year_dst_start_unix_time = + self.dst_start.unix_time(current_year + 1, dst_start_time_in_utc); + next_year_dst_start_unix_time <= unix_time + } else { + true + } + } + } + }; + + if is_dst { Ok(&self.dst) } else { Ok(&self.std) } + } + + fn find_local_time_type_from_local( + &self, + local_time: NaiveDateTime, + ) -> Result, Error> { + // Year must be between i32::MIN + 2 and i32::MAX - 2, year in NaiveDate is always smaller. + let current_year = local_time.year(); + let local_time = local_time.and_utc().timestamp(); + + let dst_start_transition_start = + self.dst_start.unix_time(current_year, 0) + i64::from(self.dst_start_time); + let dst_start_transition_end = self.dst_start.unix_time(current_year, 0) + + i64::from(self.dst_start_time) + + i64::from(self.dst.ut_offset) + - i64::from(self.std.ut_offset); + + let dst_end_transition_start = + self.dst_end.unix_time(current_year, 0) + i64::from(self.dst_end_time); + let dst_end_transition_end = self.dst_end.unix_time(current_year, 0) + + i64::from(self.dst_end_time) + + i64::from(self.std.ut_offset) + - i64::from(self.dst.ut_offset); + + match self.std.ut_offset.cmp(&self.dst.ut_offset) { + Ordering::Equal => Ok(crate::MappedLocalTime::Single(self.std)), + Ordering::Less => { + if self.dst_start.transition_date(current_year).0 + < self.dst_end.transition_date(current_year).0 + { + // northern hemisphere + // For the DST END transition, the `start` happens at a later timestamp than the `end`. + if local_time <= dst_start_transition_start { + Ok(crate::MappedLocalTime::Single(self.std)) + } else if local_time > dst_start_transition_start + && local_time < dst_start_transition_end + { + Ok(crate::MappedLocalTime::None) + } else if local_time >= dst_start_transition_end + && local_time < dst_end_transition_end + { + Ok(crate::MappedLocalTime::Single(self.dst)) + } else if local_time >= dst_end_transition_end + && local_time <= dst_end_transition_start + { + Ok(crate::MappedLocalTime::Ambiguous(self.std, self.dst)) + } else { + Ok(crate::MappedLocalTime::Single(self.std)) + } + } else { + // southern hemisphere regular DST + // For the DST END transition, the `start` happens at a later timestamp than the `end`. + if local_time < dst_end_transition_end { + Ok(crate::MappedLocalTime::Single(self.dst)) + } else if local_time >= dst_end_transition_end + && local_time <= dst_end_transition_start + { + Ok(crate::MappedLocalTime::Ambiguous(self.std, self.dst)) + } else if local_time > dst_end_transition_end + && local_time < dst_start_transition_start + { + Ok(crate::MappedLocalTime::Single(self.std)) + } else if local_time >= dst_start_transition_start + && local_time < dst_start_transition_end + { + Ok(crate::MappedLocalTime::None) + } else { + Ok(crate::MappedLocalTime::Single(self.dst)) + } + } + } + Ordering::Greater => { + if self.dst_start.transition_date(current_year).0 + < self.dst_end.transition_date(current_year).0 + { + // southern hemisphere reverse DST + // For the DST END transition, the `start` happens at a later timestamp than the `end`. + if local_time < dst_start_transition_end { + Ok(crate::MappedLocalTime::Single(self.std)) + } else if local_time >= dst_start_transition_end + && local_time <= dst_start_transition_start + { + Ok(crate::MappedLocalTime::Ambiguous(self.dst, self.std)) + } else if local_time > dst_start_transition_start + && local_time < dst_end_transition_start + { + Ok(crate::MappedLocalTime::Single(self.dst)) + } else if local_time >= dst_end_transition_start + && local_time < dst_end_transition_end + { + Ok(crate::MappedLocalTime::None) + } else { + Ok(crate::MappedLocalTime::Single(self.std)) + } + } else { + // northern hemisphere reverse DST + // For the DST END transition, the `start` happens at a later timestamp than the `end`. + if local_time <= dst_end_transition_start { + Ok(crate::MappedLocalTime::Single(self.dst)) + } else if local_time > dst_end_transition_start + && local_time < dst_end_transition_end + { + Ok(crate::MappedLocalTime::None) + } else if local_time >= dst_end_transition_end + && local_time < dst_start_transition_end + { + Ok(crate::MappedLocalTime::Single(self.std)) + } else if local_time >= dst_start_transition_end + && local_time <= dst_start_transition_start + { + Ok(crate::MappedLocalTime::Ambiguous(self.dst, self.std)) + } else { + Ok(crate::MappedLocalTime::Single(self.dst)) + } + } + } + } + } +} + +/// Parse time zone name +fn parse_name<'a>(cursor: &mut Cursor<'a>) -> Result<&'a [u8], Error> { + match cursor.peek() { + Some(b'<') => {} + _ => return Ok(cursor.read_while(u8::is_ascii_alphabetic)?), + } + + cursor.read_exact(1)?; + let unquoted = cursor.read_until(|&x| x == b'>')?; + cursor.read_exact(1)?; + Ok(unquoted) +} + +/// Parse time zone offset +fn parse_offset(cursor: &mut Cursor) -> Result { + let (sign, hour, minute, second) = parse_signed_hhmmss(cursor)?; + + if !(0..=24).contains(&hour) { + return Err(Error::InvalidTzString("invalid offset hour")); + } + if !(0..=59).contains(&minute) { + return Err(Error::InvalidTzString("invalid offset minute")); + } + if !(0..=59).contains(&second) { + return Err(Error::InvalidTzString("invalid offset second")); + } + + Ok(sign * (hour * 3600 + minute * 60 + second)) +} + +/// Parse transition rule time +fn parse_rule_time(cursor: &mut Cursor) -> Result { + let (hour, minute, second) = parse_hhmmss(cursor)?; + + if !(0..=24).contains(&hour) { + return Err(Error::InvalidTzString("invalid day time hour")); + } + if !(0..=59).contains(&minute) { + return Err(Error::InvalidTzString("invalid day time minute")); + } + if !(0..=59).contains(&second) { + return Err(Error::InvalidTzString("invalid day time second")); + } + + Ok(hour * 3600 + minute * 60 + second) +} + +/// Parse transition rule time with TZ string extensions +fn parse_rule_time_extended(cursor: &mut Cursor) -> Result { + let (sign, hour, minute, second) = parse_signed_hhmmss(cursor)?; + + if !(-167..=167).contains(&hour) { + return Err(Error::InvalidTzString("invalid day time hour")); + } + if !(0..=59).contains(&minute) { + return Err(Error::InvalidTzString("invalid day time minute")); + } + if !(0..=59).contains(&second) { + return Err(Error::InvalidTzString("invalid day time second")); + } + + Ok(sign * (hour * 3600 + minute * 60 + second)) +} + +/// Parse hours, minutes and seconds +fn parse_hhmmss(cursor: &mut Cursor) -> Result<(i32, i32, i32), Error> { + let hour = cursor.read_int()?; + + let mut minute = 0; + let mut second = 0; + + if cursor.read_optional_tag(b":")? { + minute = cursor.read_int()?; + + if cursor.read_optional_tag(b":")? { + second = cursor.read_int()?; + } + } + + Ok((hour, minute, second)) +} + +/// Parse signed hours, minutes and seconds +fn parse_signed_hhmmss(cursor: &mut Cursor) -> Result<(i32, i32, i32, i32), Error> { + let mut sign = 1; + if let Some(&c) = cursor.peek() { + if c == b'+' || c == b'-' { + cursor.read_exact(1)?; + if c == b'-' { + sign = -1; + } + } + } + + let (hour, minute, second) = parse_hhmmss(cursor)?; + Ok((sign, hour, minute, second)) +} + +/// Transition rule day +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +enum RuleDay { + /// Julian day in `[1, 365]`, without taking occasional Feb 29 into account, which is not referenceable + Julian1WithoutLeap(u16), + /// Zero-based Julian day in `[0, 365]`, taking occasional Feb 29 into account + Julian0WithLeap(u16), + /// Day represented by a month, a month week and a week day + MonthWeekday { + /// Month in `[1, 12]` + month: u8, + /// Week of the month in `[1, 5]`, with `5` representing the last week of the month + week: u8, + /// Day of the week in `[0, 6]` from Sunday + week_day: u8, + }, +} + +impl RuleDay { + /// Parse transition rule + fn parse(cursor: &mut Cursor, use_string_extensions: bool) -> Result<(Self, i32), Error> { + let date = match cursor.peek() { + Some(b'M') => { + cursor.read_exact(1)?; + let month = cursor.read_int()?; + cursor.read_tag(b".")?; + let week = cursor.read_int()?; + cursor.read_tag(b".")?; + let week_day = cursor.read_int()?; + RuleDay::month_weekday(month, week, week_day)? + } + Some(b'J') => { + cursor.read_exact(1)?; + RuleDay::julian_1(cursor.read_int()?)? + } + _ => RuleDay::julian_0(cursor.read_int()?)?, + }; + + Ok(( + date, + match (cursor.read_optional_tag(b"/")?, use_string_extensions) { + (false, _) => 2 * 3600, + (true, true) => parse_rule_time_extended(cursor)?, + (true, false) => parse_rule_time(cursor)?, + }, + )) + } + + /// Construct a transition rule day represented by a Julian day in `[1, 365]`, without taking occasional Feb 29 into account, which is not referenceable + fn julian_1(julian_day_1: u16) -> Result { + if !(1..=365).contains(&julian_day_1) { + return Err(Error::TransitionRule("invalid rule day julian day")); + } + + Ok(RuleDay::Julian1WithoutLeap(julian_day_1)) + } + + /// Construct a transition rule day represented by a zero-based Julian day in `[0, 365]`, taking occasional Feb 29 into account + const fn julian_0(julian_day_0: u16) -> Result { + if julian_day_0 > 365 { + return Err(Error::TransitionRule("invalid rule day julian day")); + } + + Ok(RuleDay::Julian0WithLeap(julian_day_0)) + } + + /// Construct a transition rule day represented by a month, a month week and a week day + fn month_weekday(month: u8, week: u8, week_day: u8) -> Result { + if !(1..=12).contains(&month) { + return Err(Error::TransitionRule("invalid rule day month")); + } + + if !(1..=5).contains(&week) { + return Err(Error::TransitionRule("invalid rule day week")); + } + + if week_day > 6 { + return Err(Error::TransitionRule("invalid rule day week day")); + } + + Ok(RuleDay::MonthWeekday { month, week, week_day }) + } + + /// Get the transition date for the provided year + /// + /// ## Outputs + /// + /// * `month`: Month in `[1, 12]` + /// * `month_day`: Day of the month in `[1, 31]` + fn transition_date(&self, year: i32) -> (usize, i64) { + match *self { + RuleDay::Julian1WithoutLeap(year_day) => { + let year_day = year_day as i64; + + let month = match CUMUL_DAY_IN_MONTHS_NORMAL_YEAR.binary_search(&(year_day - 1)) { + Ok(x) => x + 1, + Err(x) => x, + }; + + let month_day = year_day - CUMUL_DAY_IN_MONTHS_NORMAL_YEAR[month - 1]; + + (month, month_day) + } + RuleDay::Julian0WithLeap(year_day) => { + let leap = is_leap_year(year) as i64; + + let cumul_day_in_months = [ + 0, + 31, + 59 + leap, + 90 + leap, + 120 + leap, + 151 + leap, + 181 + leap, + 212 + leap, + 243 + leap, + 273 + leap, + 304 + leap, + 334 + leap, + ]; + + let year_day = year_day as i64; + + let month = match cumul_day_in_months.binary_search(&year_day) { + Ok(x) => x + 1, + Err(x) => x, + }; + + let month_day = 1 + year_day - cumul_day_in_months[month - 1]; + + (month, month_day) + } + RuleDay::MonthWeekday { month: rule_month, week, week_day } => { + let leap = is_leap_year(year) as i64; + + let month = rule_month as usize; + + let mut day_in_month = DAY_IN_MONTHS_NORMAL_YEAR[month - 1]; + if month == 2 { + day_in_month += leap; + } + + let week_day_of_first_month_day = + (4 + days_since_unix_epoch(year, month, 1)).rem_euclid(DAYS_PER_WEEK); + let first_week_day_occurrence_in_month = + 1 + (week_day as i64 - week_day_of_first_month_day).rem_euclid(DAYS_PER_WEEK); + + let mut month_day = + first_week_day_occurrence_in_month + (week as i64 - 1) * DAYS_PER_WEEK; + if month_day > day_in_month { + month_day -= DAYS_PER_WEEK + } + + (month, month_day) + } + } + } + + /// Returns the UTC Unix time in seconds associated to the transition date for the provided year + fn unix_time(&self, year: i32, day_time_in_utc: i64) -> i64 { + let (month, month_day) = self.transition_date(year); + days_since_unix_epoch(year, month, month_day) * SECONDS_PER_DAY + day_time_in_utc + } +} + +/// UTC date time exprimed in the [proleptic gregorian calendar](https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar) +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +pub(crate) struct UtcDateTime { + /// Year + pub(crate) year: i32, + /// Month in `[1, 12]` + pub(crate) month: u8, + /// Day of the month in `[1, 31]` + pub(crate) month_day: u8, + /// Hours since midnight in `[0, 23]` + pub(crate) hour: u8, + /// Minutes in `[0, 59]` + pub(crate) minute: u8, + /// Seconds in `[0, 60]`, with a possible leap second + pub(crate) second: u8, +} + +impl UtcDateTime { + /// Construct a UTC date time from a Unix time in seconds and nanoseconds + pub(crate) fn from_timespec(unix_time: i64) -> Result { + let seconds = match unix_time.checked_sub(UNIX_OFFSET_SECS) { + Some(seconds) => seconds, + None => return Err(Error::OutOfRange("out of range operation")), + }; + + let mut remaining_days = seconds / SECONDS_PER_DAY; + let mut remaining_seconds = seconds % SECONDS_PER_DAY; + if remaining_seconds < 0 { + remaining_seconds += SECONDS_PER_DAY; + remaining_days -= 1; + } + + let mut cycles_400_years = remaining_days / DAYS_PER_400_YEARS; + remaining_days %= DAYS_PER_400_YEARS; + if remaining_days < 0 { + remaining_days += DAYS_PER_400_YEARS; + cycles_400_years -= 1; + } + + let cycles_100_years = Ord::min(remaining_days / DAYS_PER_100_YEARS, 3); + remaining_days -= cycles_100_years * DAYS_PER_100_YEARS; + + let cycles_4_years = Ord::min(remaining_days / DAYS_PER_4_YEARS, 24); + remaining_days -= cycles_4_years * DAYS_PER_4_YEARS; + + let remaining_years = Ord::min(remaining_days / DAYS_PER_NORMAL_YEAR, 3); + remaining_days -= remaining_years * DAYS_PER_NORMAL_YEAR; + + let mut year = OFFSET_YEAR + + remaining_years + + cycles_4_years * 4 + + cycles_100_years * 100 + + cycles_400_years * 400; + + let mut month = 0; + while month < DAY_IN_MONTHS_LEAP_YEAR_FROM_MARCH.len() { + let days = DAY_IN_MONTHS_LEAP_YEAR_FROM_MARCH[month]; + if remaining_days < days { + break; + } + remaining_days -= days; + month += 1; + } + month += 2; + + if month >= MONTHS_PER_YEAR as usize { + month -= MONTHS_PER_YEAR as usize; + year += 1; + } + month += 1; + + let month_day = 1 + remaining_days; + + let hour = remaining_seconds / SECONDS_PER_HOUR; + let minute = (remaining_seconds / SECONDS_PER_MINUTE) % MINUTES_PER_HOUR; + let second = remaining_seconds % SECONDS_PER_MINUTE; + + let year = match year >= i32::MIN as i64 && year <= i32::MAX as i64 { + true => year as i32, + false => return Err(Error::OutOfRange("i64 is out of range for i32")), + }; + + Ok(Self { + year, + month: month as u8, + month_day: month_day as u8, + hour: hour as u8, + minute: minute as u8, + second: second as u8, + }) + } +} + +/// Number of nanoseconds in one second +const NANOSECONDS_PER_SECOND: u32 = 1_000_000_000; +/// Number of seconds in one minute +const SECONDS_PER_MINUTE: i64 = 60; +/// Number of seconds in one hour +const SECONDS_PER_HOUR: i64 = 3600; +/// Number of minutes in one hour +const MINUTES_PER_HOUR: i64 = 60; +/// Number of months in one year +const MONTHS_PER_YEAR: i64 = 12; +/// Number of days in a normal year +const DAYS_PER_NORMAL_YEAR: i64 = 365; +/// Number of days in 4 years (including 1 leap year) +const DAYS_PER_4_YEARS: i64 = DAYS_PER_NORMAL_YEAR * 4 + 1; +/// Number of days in 100 years (including 24 leap years) +const DAYS_PER_100_YEARS: i64 = DAYS_PER_NORMAL_YEAR * 100 + 24; +/// Number of days in 400 years (including 97 leap years) +const DAYS_PER_400_YEARS: i64 = DAYS_PER_NORMAL_YEAR * 400 + 97; +/// Unix time at `2000-03-01T00:00:00Z` (Wednesday) +const UNIX_OFFSET_SECS: i64 = 951868800; +/// Offset year +const OFFSET_YEAR: i64 = 2000; +/// Month days in a leap year from March +const DAY_IN_MONTHS_LEAP_YEAR_FROM_MARCH: [i64; 12] = + [31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31, 29]; + +/// Compute the number of days since Unix epoch (`1970-01-01T00:00:00Z`). +/// +/// ## Inputs +/// +/// * `year`: Year +/// * `month`: Month in `[1, 12]` +/// * `month_day`: Day of the month in `[1, 31]` +pub(crate) const fn days_since_unix_epoch(year: i32, month: usize, month_day: i64) -> i64 { + let is_leap_year = is_leap_year(year); + + let year = year as i64; + + let mut result = (year - 1970) * 365; + + if year >= 1970 { + result += (year - 1968) / 4; + result -= (year - 1900) / 100; + result += (year - 1600) / 400; + + if is_leap_year && month < 3 { + result -= 1; + } + } else { + result += (year - 1972) / 4; + result -= (year - 2000) / 100; + result += (year - 2000) / 400; + + if is_leap_year && month >= 3 { + result += 1; + } + } + + result += CUMUL_DAY_IN_MONTHS_NORMAL_YEAR[month - 1] + month_day - 1; + + result +} + +/// Check if a year is a leap year +pub(crate) const fn is_leap_year(year: i32) -> bool { + year % 400 == 0 || (year % 4 == 0 && year % 100 != 0) +} + +#[cfg(test)] +mod tests { + use super::super::timezone::Transition; + use super::super::{Error, TimeZone}; + use super::{AlternateTime, LocalTimeType, RuleDay, TransitionRule}; + + #[test] + fn test_quoted() -> Result<(), Error> { + let transition_rule = TransitionRule::from_tz_string(b"<-03>+3<+03>-3,J1,J365", false)?; + assert_eq!( + transition_rule, + AlternateTime::new( + LocalTimeType::new(-10800, false, Some(b"-03"))?, + LocalTimeType::new(10800, true, Some(b"+03"))?, + RuleDay::julian_1(1)?, + 7200, + RuleDay::julian_1(365)?, + 7200, + )? + .into() + ); + Ok(()) + } + + #[test] + fn test_full() -> Result<(), Error> { + let tz_string = b"NZST-12:00:00NZDT-13:00:00,M10.1.0/02:00:00,M3.3.0/02:00:00"; + let transition_rule = TransitionRule::from_tz_string(tz_string, false)?; + assert_eq!( + transition_rule, + AlternateTime::new( + LocalTimeType::new(43200, false, Some(b"NZST"))?, + LocalTimeType::new(46800, true, Some(b"NZDT"))?, + RuleDay::month_weekday(10, 1, 0)?, + 7200, + RuleDay::month_weekday(3, 3, 0)?, + 7200, + )? + .into() + ); + Ok(()) + } + + #[test] + fn test_negative_dst() -> Result<(), Error> { + let tz_string = b"IST-1GMT0,M10.5.0,M3.5.0/1"; + let transition_rule = TransitionRule::from_tz_string(tz_string, false)?; + assert_eq!( + transition_rule, + AlternateTime::new( + LocalTimeType::new(3600, false, Some(b"IST"))?, + LocalTimeType::new(0, true, Some(b"GMT"))?, + RuleDay::month_weekday(10, 5, 0)?, + 7200, + RuleDay::month_weekday(3, 5, 0)?, + 3600, + )? + .into() + ); + Ok(()) + } + + #[test] + fn test_negative_hour() -> Result<(), Error> { + let tz_string = b"<-03>3<-02>,M3.5.0/-2,M10.5.0/-1"; + assert!(TransitionRule::from_tz_string(tz_string, false).is_err()); + + assert_eq!( + TransitionRule::from_tz_string(tz_string, true)?, + AlternateTime::new( + LocalTimeType::new(-10800, false, Some(b"-03"))?, + LocalTimeType::new(-7200, true, Some(b"-02"))?, + RuleDay::month_weekday(3, 5, 0)?, + -7200, + RuleDay::month_weekday(10, 5, 0)?, + -3600, + )? + .into() + ); + Ok(()) + } + + #[test] + fn test_all_year_dst() -> Result<(), Error> { + let tz_string = b"EST5EDT,0/0,J365/25"; + assert!(TransitionRule::from_tz_string(tz_string, false).is_err()); + + assert_eq!( + TransitionRule::from_tz_string(tz_string, true)?, + AlternateTime::new( + LocalTimeType::new(-18000, false, Some(b"EST"))?, + LocalTimeType::new(-14400, true, Some(b"EDT"))?, + RuleDay::julian_0(0)?, + 0, + RuleDay::julian_1(365)?, + 90000, + )? + .into() + ); + Ok(()) + } + + #[test] + fn test_v3_file() -> Result<(), Error> { + let bytes = b"TZif3\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x01\0\0\0\x04\0\0\x1c\x20\0\0IST\0TZif3\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x01\0\0\0\x01\0\0\0\0\0\0\0\x01\0\0\0\x01\0\0\0\x04\0\0\0\0\x7f\xe8\x17\x80\0\0\0\x1c\x20\0\0IST\0\x01\x01\x0aIST-2IDT,M3.4.4/26,M10.5.0\x0a"; + + let time_zone = TimeZone::from_tz_data(bytes)?; + + let time_zone_result = TimeZone::new( + vec![Transition::new(2145916800, 0)], + vec![LocalTimeType::new(7200, false, Some(b"IST"))?], + Vec::new(), + Some(TransitionRule::from(AlternateTime::new( + LocalTimeType::new(7200, false, Some(b"IST"))?, + LocalTimeType::new(10800, true, Some(b"IDT"))?, + RuleDay::month_weekday(3, 4, 4)?, + 93600, + RuleDay::month_weekday(10, 5, 0)?, + 7200, + )?)), + )?; + + assert_eq!(time_zone, time_zone_result); + + Ok(()) + } + + #[test] + fn test_rule_day() -> Result<(), Error> { + let rule_day_j1 = RuleDay::julian_1(60)?; + assert_eq!(rule_day_j1.transition_date(2000), (3, 1)); + assert_eq!(rule_day_j1.transition_date(2001), (3, 1)); + assert_eq!(rule_day_j1.unix_time(2000, 43200), 951912000); + + let rule_day_j0 = RuleDay::julian_0(59)?; + assert_eq!(rule_day_j0.transition_date(2000), (2, 29)); + assert_eq!(rule_day_j0.transition_date(2001), (3, 1)); + assert_eq!(rule_day_j0.unix_time(2000, 43200), 951825600); + + let rule_day_mwd = RuleDay::month_weekday(2, 5, 2)?; + assert_eq!(rule_day_mwd.transition_date(2000), (2, 29)); + assert_eq!(rule_day_mwd.transition_date(2001), (2, 27)); + assert_eq!(rule_day_mwd.unix_time(2000, 43200), 951825600); + assert_eq!(rule_day_mwd.unix_time(2001, 43200), 983275200); + + Ok(()) + } + + #[test] + fn test_transition_rule() -> Result<(), Error> { + let transition_rule_fixed = TransitionRule::from(LocalTimeType::new(-36000, false, None)?); + assert_eq!(transition_rule_fixed.find_local_time_type(0)?.offset(), -36000); + + let transition_rule_dst = TransitionRule::from(AlternateTime::new( + LocalTimeType::new(43200, false, Some(b"NZST"))?, + LocalTimeType::new(46800, true, Some(b"NZDT"))?, + RuleDay::month_weekday(10, 1, 0)?, + 7200, + RuleDay::month_weekday(3, 3, 0)?, + 7200, + )?); + + assert_eq!(transition_rule_dst.find_local_time_type(953384399)?.offset(), 46800); + assert_eq!(transition_rule_dst.find_local_time_type(953384400)?.offset(), 43200); + assert_eq!(transition_rule_dst.find_local_time_type(970322399)?.offset(), 43200); + assert_eq!(transition_rule_dst.find_local_time_type(970322400)?.offset(), 46800); + + let transition_rule_negative_dst = TransitionRule::from(AlternateTime::new( + LocalTimeType::new(3600, false, Some(b"IST"))?, + LocalTimeType::new(0, true, Some(b"GMT"))?, + RuleDay::month_weekday(10, 5, 0)?, + 7200, + RuleDay::month_weekday(3, 5, 0)?, + 3600, + )?); + + assert_eq!(transition_rule_negative_dst.find_local_time_type(954032399)?.offset(), 0); + assert_eq!(transition_rule_negative_dst.find_local_time_type(954032400)?.offset(), 3600); + assert_eq!(transition_rule_negative_dst.find_local_time_type(972781199)?.offset(), 3600); + assert_eq!(transition_rule_negative_dst.find_local_time_type(972781200)?.offset(), 0); + + let transition_rule_negative_time_1 = TransitionRule::from(AlternateTime::new( + LocalTimeType::new(0, false, None)?, + LocalTimeType::new(0, true, None)?, + RuleDay::julian_0(100)?, + 0, + RuleDay::julian_0(101)?, + -86500, + )?); + + assert!(transition_rule_negative_time_1.find_local_time_type(8639899)?.is_dst()); + assert!(!transition_rule_negative_time_1.find_local_time_type(8639900)?.is_dst()); + assert!(!transition_rule_negative_time_1.find_local_time_type(8639999)?.is_dst()); + assert!(transition_rule_negative_time_1.find_local_time_type(8640000)?.is_dst()); + + let transition_rule_negative_time_2 = TransitionRule::from(AlternateTime::new( + LocalTimeType::new(-10800, false, Some(b"-03"))?, + LocalTimeType::new(-7200, true, Some(b"-02"))?, + RuleDay::month_weekday(3, 5, 0)?, + -7200, + RuleDay::month_weekday(10, 5, 0)?, + -3600, + )?); + + assert_eq!( + transition_rule_negative_time_2.find_local_time_type(954032399)?.offset(), + -10800 + ); + assert_eq!( + transition_rule_negative_time_2.find_local_time_type(954032400)?.offset(), + -7200 + ); + assert_eq!( + transition_rule_negative_time_2.find_local_time_type(972781199)?.offset(), + -7200 + ); + assert_eq!( + transition_rule_negative_time_2.find_local_time_type(972781200)?.offset(), + -10800 + ); + + let transition_rule_all_year_dst = TransitionRule::from(AlternateTime::new( + LocalTimeType::new(-18000, false, Some(b"EST"))?, + LocalTimeType::new(-14400, true, Some(b"EDT"))?, + RuleDay::julian_0(0)?, + 0, + RuleDay::julian_1(365)?, + 90000, + )?); + + assert_eq!(transition_rule_all_year_dst.find_local_time_type(946702799)?.offset(), -14400); + assert_eq!(transition_rule_all_year_dst.find_local_time_type(946702800)?.offset(), -14400); + + Ok(()) + } + + #[test] + fn test_transition_rule_overflow() -> Result<(), Error> { + let transition_rule_1 = TransitionRule::from(AlternateTime::new( + LocalTimeType::new(-1, false, None)?, + LocalTimeType::new(-1, true, None)?, + RuleDay::julian_1(365)?, + 0, + RuleDay::julian_1(1)?, + 0, + )?); + + let transition_rule_2 = TransitionRule::from(AlternateTime::new( + LocalTimeType::new(1, false, None)?, + LocalTimeType::new(1, true, None)?, + RuleDay::julian_1(365)?, + 0, + RuleDay::julian_1(1)?, + 0, + )?); + + let min_unix_time = -67768100567971200; + let max_unix_time = 67767976233532799; + + assert!(matches!( + transition_rule_1.find_local_time_type(min_unix_time), + Err(Error::OutOfRange(_)) + )); + assert!(matches!( + transition_rule_2.find_local_time_type(max_unix_time), + Err(Error::OutOfRange(_)) + )); + + Ok(()) + } +} diff --git a/third_party/rust/chrono/src/offset/local/tz_info/timezone.rs b/third_party/rust/chrono/src/offset/local/tz_info/timezone.rs new file mode 100644 index 00000000000..61d4498a942 --- /dev/null +++ b/third_party/rust/chrono/src/offset/local/tz_info/timezone.rs @@ -0,0 +1,1007 @@ +//! Types related to a time zone. + +use std::fs::{self, File}; +use std::io::{self, Read}; +use std::path::{Path, PathBuf}; +use std::{cmp::Ordering, fmt, str}; + +use super::rule::{AlternateTime, TransitionRule}; +use super::{DAYS_PER_WEEK, Error, SECONDS_PER_DAY, parser}; +use crate::NaiveDateTime; +#[cfg(target_env = "ohos")] +use crate::offset::local::tz_info::parser::Cursor; + +/// Time zone +#[derive(Debug, Clone, Eq, PartialEq)] +pub(crate) struct TimeZone { + /// List of transitions + transitions: Vec, + /// List of local time types (cannot be empty) + local_time_types: Vec, + /// List of leap seconds + leap_seconds: Vec, + /// Extra transition rule applicable after the last transition + extra_rule: Option, +} + +impl TimeZone { + /// Returns local time zone. + /// + /// This method in not supported on non-UNIX platforms, and returns the UTC time zone instead. + pub(crate) fn local(env_tz: Option<&str>) -> Result { + match env_tz { + Some(tz) => Self::from_posix_tz(tz), + None => Self::from_posix_tz("localtime"), + } + } + + /// Construct a time zone from a POSIX TZ string, as described in [the POSIX documentation of the `TZ` environment variable](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html). + fn from_posix_tz(tz_string: &str) -> Result { + if tz_string.is_empty() { + return Err(Error::InvalidTzString("empty TZ string")); + } + + if tz_string == "localtime" { + return Self::from_tz_data(&fs::read("/etc/localtime")?); + } + + // attributes are not allowed on if blocks in Rust 1.38 + #[cfg(target_os = "android")] + { + if let Ok(bytes) = android_tzdata::find_tz_data(tz_string) { + return Self::from_tz_data(&bytes); + } + } + + // ohos merge all file into tzdata since ver35 + #[cfg(target_env = "ohos")] + { + return Self::from_tz_data(&find_ohos_tz_data(tz_string)?); + } + + let mut chars = tz_string.chars(); + if chars.next() == Some(':') { + return Self::from_file(&mut find_tz_file(chars.as_str())?); + } + + if let Ok(mut file) = find_tz_file(tz_string) { + return Self::from_file(&mut file); + } + + // TZ string extensions are not allowed + let tz_string = tz_string.trim_matches(|c: char| c.is_ascii_whitespace()); + let rule = TransitionRule::from_tz_string(tz_string.as_bytes(), false)?; + Self::new( + vec![], + match rule { + TransitionRule::Fixed(local_time_type) => vec![local_time_type], + TransitionRule::Alternate(AlternateTime { std, dst, .. }) => vec![std, dst], + }, + vec![], + Some(rule), + ) + } + + /// Construct a time zone + pub(super) fn new( + transitions: Vec, + local_time_types: Vec, + leap_seconds: Vec, + extra_rule: Option, + ) -> Result { + let new = Self { transitions, local_time_types, leap_seconds, extra_rule }; + new.as_ref().validate()?; + Ok(new) + } + + /// Construct a time zone from the contents of a time zone file + fn from_file(file: &mut File) -> Result { + let mut bytes = Vec::new(); + file.read_to_end(&mut bytes)?; + Self::from_tz_data(&bytes) + } + + /// Construct a time zone from the contents of a time zone file + /// + /// Parse TZif data as described in [RFC 8536](https://datatracker.ietf.org/doc/html/rfc8536). + pub(crate) fn from_tz_data(bytes: &[u8]) -> Result { + parser::parse(bytes) + } + + /// Construct a time zone with the specified UTC offset in seconds + fn fixed(ut_offset: i32) -> Result { + Ok(Self { + transitions: Vec::new(), + local_time_types: vec![LocalTimeType::with_offset(ut_offset)?], + leap_seconds: Vec::new(), + extra_rule: None, + }) + } + + /// Construct the time zone associated to UTC + pub(crate) fn utc() -> Self { + Self { + transitions: Vec::new(), + local_time_types: vec![LocalTimeType::UTC], + leap_seconds: Vec::new(), + extra_rule: None, + } + } + + /// Find the local time type associated to the time zone at the specified Unix time in seconds + pub(crate) fn find_local_time_type(&self, unix_time: i64) -> Result<&LocalTimeType, Error> { + self.as_ref().find_local_time_type(unix_time) + } + + pub(crate) fn find_local_time_type_from_local( + &self, + local_time: NaiveDateTime, + ) -> Result, Error> { + self.as_ref().find_local_time_type_from_local(local_time) + } + + /// Returns a reference to the time zone + fn as_ref(&self) -> TimeZoneRef { + TimeZoneRef { + transitions: &self.transitions, + local_time_types: &self.local_time_types, + leap_seconds: &self.leap_seconds, + extra_rule: &self.extra_rule, + } + } +} + +/// Reference to a time zone +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub(crate) struct TimeZoneRef<'a> { + /// List of transitions + transitions: &'a [Transition], + /// List of local time types (cannot be empty) + local_time_types: &'a [LocalTimeType], + /// List of leap seconds + leap_seconds: &'a [LeapSecond], + /// Extra transition rule applicable after the last transition + extra_rule: &'a Option, +} + +impl<'a> TimeZoneRef<'a> { + /// Find the local time type associated to the time zone at the specified Unix time in seconds + pub(crate) fn find_local_time_type(&self, unix_time: i64) -> Result<&'a LocalTimeType, Error> { + let extra_rule = match self.transitions.last() { + None => match self.extra_rule { + Some(extra_rule) => extra_rule, + None => return Ok(&self.local_time_types[0]), + }, + Some(last_transition) => { + let unix_leap_time = match self.unix_time_to_unix_leap_time(unix_time) { + Ok(unix_leap_time) => unix_leap_time, + Err(Error::OutOfRange(error)) => return Err(Error::FindLocalTimeType(error)), + Err(err) => return Err(err), + }; + + if unix_leap_time >= last_transition.unix_leap_time { + match self.extra_rule { + Some(extra_rule) => extra_rule, + None => { + // RFC 8536 3.2: + // "Local time for timestamps on or after the last transition is + // specified by the TZ string in the footer (Section 3.3) if present + // and nonempty; otherwise, it is unspecified." + // + // Older versions of macOS (1.12 and before?) have TZif file with a + // missing TZ string, and use the offset given by the last transition. + return Ok( + &self.local_time_types[last_transition.local_time_type_index] + ); + } + } + } else { + let index = match self + .transitions + .binary_search_by_key(&unix_leap_time, Transition::unix_leap_time) + { + Ok(x) => x + 1, + Err(x) => x, + }; + + let local_time_type_index = if index > 0 { + self.transitions[index - 1].local_time_type_index + } else { + 0 + }; + return Ok(&self.local_time_types[local_time_type_index]); + } + } + }; + + match extra_rule.find_local_time_type(unix_time) { + Ok(local_time_type) => Ok(local_time_type), + Err(Error::OutOfRange(error)) => Err(Error::FindLocalTimeType(error)), + err => err, + } + } + + pub(crate) fn find_local_time_type_from_local( + &self, + local_time: NaiveDateTime, + ) -> Result, Error> { + // #TODO: this is wrong as we need 'local_time_to_local_leap_time ? + // but ... does the local time even include leap seconds ?? + // let unix_leap_time = match self.unix_time_to_unix_leap_time(local_time) { + // Ok(unix_leap_time) => unix_leap_time, + // Err(Error::OutOfRange(error)) => return Err(Error::FindLocalTimeType(error)), + // Err(err) => return Err(err), + // }; + let local_leap_time = local_time.and_utc().timestamp(); + + // if we have at least one transition, + // we must check _all_ of them, in case of any Overlapping (MappedLocalTime::Ambiguous) or Skipping (MappedLocalTime::None) transitions + let offset_after_last = if !self.transitions.is_empty() { + let mut prev = self.local_time_types[0]; + + for transition in self.transitions { + let after_ltt = self.local_time_types[transition.local_time_type_index]; + + // the end and start here refers to where the time starts prior to the transition + // and where it ends up after. not the temporal relationship. + let transition_end = transition.unix_leap_time + i64::from(after_ltt.ut_offset); + let transition_start = transition.unix_leap_time + i64::from(prev.ut_offset); + + match transition_start.cmp(&transition_end) { + Ordering::Greater => { + // backwards transition, eg from DST to regular + // this means a given local time could have one of two possible offsets + if local_leap_time < transition_end { + return Ok(crate::MappedLocalTime::Single(prev)); + } else if local_leap_time >= transition_end + && local_leap_time <= transition_start + { + if prev.ut_offset < after_ltt.ut_offset { + return Ok(crate::MappedLocalTime::Ambiguous(prev, after_ltt)); + } else { + return Ok(crate::MappedLocalTime::Ambiguous(after_ltt, prev)); + } + } + } + Ordering::Equal => { + // should this ever happen? presumably we have to handle it anyway. + if local_leap_time < transition_start { + return Ok(crate::MappedLocalTime::Single(prev)); + } else if local_leap_time == transition_end { + if prev.ut_offset < after_ltt.ut_offset { + return Ok(crate::MappedLocalTime::Ambiguous(prev, after_ltt)); + } else { + return Ok(crate::MappedLocalTime::Ambiguous(after_ltt, prev)); + } + } + } + Ordering::Less => { + // forwards transition, eg from regular to DST + // this means that times that are skipped are invalid local times + if local_leap_time <= transition_start { + return Ok(crate::MappedLocalTime::Single(prev)); + } else if local_leap_time < transition_end { + return Ok(crate::MappedLocalTime::None); + } else if local_leap_time == transition_end { + return Ok(crate::MappedLocalTime::Single(after_ltt)); + } + } + } + + // try the next transition, we are fully after this one + prev = after_ltt; + } + + prev + } else { + self.local_time_types[0] + }; + + if let Some(extra_rule) = self.extra_rule { + match extra_rule.find_local_time_type_from_local(local_time) { + Ok(local_time_type) => Ok(local_time_type), + Err(Error::OutOfRange(error)) => Err(Error::FindLocalTimeType(error)), + err => err, + } + } else { + Ok(crate::MappedLocalTime::Single(offset_after_last)) + } + } + + /// Check time zone inputs + fn validate(&self) -> Result<(), Error> { + // Check local time types + let local_time_types_size = self.local_time_types.len(); + if local_time_types_size == 0 { + return Err(Error::TimeZone("list of local time types must not be empty")); + } + + // Check transitions + let mut i_transition = 0; + while i_transition < self.transitions.len() { + if self.transitions[i_transition].local_time_type_index >= local_time_types_size { + return Err(Error::TimeZone("invalid local time type index")); + } + + if i_transition + 1 < self.transitions.len() + && self.transitions[i_transition].unix_leap_time + >= self.transitions[i_transition + 1].unix_leap_time + { + return Err(Error::TimeZone("invalid transition")); + } + + i_transition += 1; + } + + // Check leap seconds + if !(self.leap_seconds.is_empty() + || self.leap_seconds[0].unix_leap_time >= 0 + && self.leap_seconds[0].correction.saturating_abs() == 1) + { + return Err(Error::TimeZone("invalid leap second")); + } + + let min_interval = SECONDS_PER_28_DAYS - 1; + + let mut i_leap_second = 0; + while i_leap_second < self.leap_seconds.len() { + if i_leap_second + 1 < self.leap_seconds.len() { + let x0 = &self.leap_seconds[i_leap_second]; + let x1 = &self.leap_seconds[i_leap_second + 1]; + + let diff_unix_leap_time = x1.unix_leap_time.saturating_sub(x0.unix_leap_time); + let abs_diff_correction = + x1.correction.saturating_sub(x0.correction).saturating_abs(); + + if !(diff_unix_leap_time >= min_interval && abs_diff_correction == 1) { + return Err(Error::TimeZone("invalid leap second")); + } + } + i_leap_second += 1; + } + + // Check extra rule + let (extra_rule, last_transition) = match (&self.extra_rule, self.transitions.last()) { + (Some(rule), Some(trans)) => (rule, trans), + _ => return Ok(()), + }; + + let last_local_time_type = &self.local_time_types[last_transition.local_time_type_index]; + let unix_time = match self.unix_leap_time_to_unix_time(last_transition.unix_leap_time) { + Ok(unix_time) => unix_time, + Err(Error::OutOfRange(error)) => return Err(Error::TimeZone(error)), + Err(err) => return Err(err), + }; + + let rule_local_time_type = match extra_rule.find_local_time_type(unix_time) { + Ok(rule_local_time_type) => rule_local_time_type, + Err(Error::OutOfRange(error)) => return Err(Error::TimeZone(error)), + Err(err) => return Err(err), + }; + + let check = last_local_time_type.ut_offset == rule_local_time_type.ut_offset + && last_local_time_type.is_dst == rule_local_time_type.is_dst + && match (&last_local_time_type.name, &rule_local_time_type.name) { + (Some(x), Some(y)) => x.equal(y), + (None, None) => true, + _ => false, + }; + + if !check { + return Err(Error::TimeZone( + "extra transition rule is inconsistent with the last transition", + )); + } + + Ok(()) + } + + /// Convert Unix time to Unix leap time, from the list of leap seconds in a time zone + const fn unix_time_to_unix_leap_time(&self, unix_time: i64) -> Result { + let mut unix_leap_time = unix_time; + + let mut i = 0; + while i < self.leap_seconds.len() { + let leap_second = &self.leap_seconds[i]; + + if unix_leap_time < leap_second.unix_leap_time { + break; + } + + unix_leap_time = match unix_time.checked_add(leap_second.correction as i64) { + Some(unix_leap_time) => unix_leap_time, + None => return Err(Error::OutOfRange("out of range operation")), + }; + + i += 1; + } + + Ok(unix_leap_time) + } + + /// Convert Unix leap time to Unix time, from the list of leap seconds in a time zone + fn unix_leap_time_to_unix_time(&self, unix_leap_time: i64) -> Result { + if unix_leap_time == i64::MIN { + return Err(Error::OutOfRange("out of range operation")); + } + + let index = match self + .leap_seconds + .binary_search_by_key(&(unix_leap_time - 1), LeapSecond::unix_leap_time) + { + Ok(x) => x + 1, + Err(x) => x, + }; + + let correction = if index > 0 { self.leap_seconds[index - 1].correction } else { 0 }; + + match unix_leap_time.checked_sub(correction as i64) { + Some(unix_time) => Ok(unix_time), + None => Err(Error::OutOfRange("out of range operation")), + } + } + + /// The UTC time zone + const UTC: TimeZoneRef<'static> = TimeZoneRef { + transitions: &[], + local_time_types: &[LocalTimeType::UTC], + leap_seconds: &[], + extra_rule: &None, + }; +} + +/// Transition of a TZif file +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub(super) struct Transition { + /// Unix leap time + unix_leap_time: i64, + /// Index specifying the local time type of the transition + local_time_type_index: usize, +} + +impl Transition { + /// Construct a TZif file transition + pub(super) const fn new(unix_leap_time: i64, local_time_type_index: usize) -> Self { + Self { unix_leap_time, local_time_type_index } + } + + /// Returns Unix leap time + const fn unix_leap_time(&self) -> i64 { + self.unix_leap_time + } +} + +/// Leap second of a TZif file +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub(super) struct LeapSecond { + /// Unix leap time + unix_leap_time: i64, + /// Leap second correction + correction: i32, +} + +impl LeapSecond { + /// Construct a TZif file leap second + pub(super) const fn new(unix_leap_time: i64, correction: i32) -> Self { + Self { unix_leap_time, correction } + } + + /// Returns Unix leap time + const fn unix_leap_time(&self) -> i64 { + self.unix_leap_time + } +} + +/// ASCII-encoded fixed-capacity string, used for storing time zone names +#[derive(Copy, Clone, Eq, PartialEq)] +struct TimeZoneName { + /// Length-prefixed string buffer + bytes: [u8; 8], +} + +impl TimeZoneName { + /// Construct a time zone name + /// + /// man tzfile(5): + /// Time zone designations should consist of at least three (3) and no more than six (6) ASCII + /// characters from the set of alphanumerics, “-”, and “+”. This is for compatibility with + /// POSIX requirements for time zone abbreviations. + fn new(input: &[u8]) -> Result { + let len = input.len(); + + if !(3..=7).contains(&len) { + return Err(Error::LocalTimeType( + "time zone name must have between 3 and 7 characters", + )); + } + + let mut bytes = [0; 8]; + bytes[0] = input.len() as u8; + + let mut i = 0; + while i < len { + let b = input[i]; + match b { + b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z' | b'+' | b'-' => {} + _ => return Err(Error::LocalTimeType("invalid characters in time zone name")), + } + + bytes[i + 1] = b; + i += 1; + } + + Ok(Self { bytes }) + } + + /// Returns time zone name as a byte slice + fn as_bytes(&self) -> &[u8] { + match self.bytes[0] { + 3 => &self.bytes[1..4], + 4 => &self.bytes[1..5], + 5 => &self.bytes[1..6], + 6 => &self.bytes[1..7], + 7 => &self.bytes[1..8], + _ => unreachable!(), + } + } + + /// Check if two time zone names are equal + fn equal(&self, other: &Self) -> bool { + self.bytes == other.bytes + } +} + +impl AsRef for TimeZoneName { + fn as_ref(&self) -> &str { + // SAFETY: ASCII is valid UTF-8 + unsafe { str::from_utf8_unchecked(self.as_bytes()) } + } +} + +impl fmt::Debug for TimeZoneName { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.as_ref().fmt(f) + } +} + +/// Local time type associated to a time zone +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub(crate) struct LocalTimeType { + /// Offset from UTC in seconds + pub(super) ut_offset: i32, + /// Daylight Saving Time indicator + is_dst: bool, + /// Time zone name + name: Option, +} + +impl LocalTimeType { + /// Construct a local time type + pub(super) fn new(ut_offset: i32, is_dst: bool, name: Option<&[u8]>) -> Result { + if ut_offset == i32::MIN { + return Err(Error::LocalTimeType("invalid UTC offset")); + } + + let name = match name { + Some(name) => TimeZoneName::new(name)?, + None => return Ok(Self { ut_offset, is_dst, name: None }), + }; + + Ok(Self { ut_offset, is_dst, name: Some(name) }) + } + + /// Construct a local time type with the specified UTC offset in seconds + pub(super) const fn with_offset(ut_offset: i32) -> Result { + if ut_offset == i32::MIN { + return Err(Error::LocalTimeType("invalid UTC offset")); + } + + Ok(Self { ut_offset, is_dst: false, name: None }) + } + + /// Returns offset from UTC in seconds + pub(crate) const fn offset(&self) -> i32 { + self.ut_offset + } + + /// Returns daylight saving time indicator + pub(super) const fn is_dst(&self) -> bool { + self.is_dst + } + + pub(super) const UTC: LocalTimeType = Self { ut_offset: 0, is_dst: false, name: None }; +} + +/// Open the TZif file corresponding to a TZ string +fn find_tz_file(path: impl AsRef) -> Result { + // Don't check system timezone directories on non-UNIX platforms + #[cfg(not(unix))] + return Ok(File::open(path)?); + + #[cfg(unix)] + { + let path = path.as_ref(); + if path.is_absolute() { + return Ok(File::open(path)?); + } + + for folder in &ZONE_INFO_DIRECTORIES { + if let Ok(file) = File::open(PathBuf::from(folder).join(path)) { + return Ok(file); + } + } + + Err(Error::Io(io::ErrorKind::NotFound.into())) + } +} + +#[cfg(target_env = "ohos")] +fn from_tzdata_bytes(bytes: &mut Vec, tz_string: &str) -> Result, Error> { + const VERSION_SIZE: usize = 12; + const OFFSET_SIZE: usize = 4; + const INDEX_CHUNK_SIZE: usize = 48; + const ZONENAME_SIZE: usize = 40; + + let mut cursor = Cursor::new(&bytes); + // version head + let _ = cursor.read_exact(VERSION_SIZE)?; + let index_offset_offset = cursor.read_be_u32()?; + let data_offset_offset = cursor.read_be_u32()?; + // final offset + let _ = cursor.read_be_u32()?; + + cursor.seek_after(index_offset_offset as usize)?; + let mut idx = index_offset_offset; + while idx < data_offset_offset { + let index_buf = cursor.read_exact(ZONENAME_SIZE)?; + let offset = cursor.read_be_u32()?; + let length = cursor.read_be_u32()?; + let zone_name = str::from_utf8(index_buf)?.trim_end_matches('\0'); + if zone_name != tz_string { + idx += INDEX_CHUNK_SIZE as u32; + continue; + } + cursor.seek_after((data_offset_offset + offset) as usize)?; + return match cursor.read_exact(length as usize) { + Ok(result) => Ok(result.to_vec()), + Err(_err) => Err(Error::InvalidTzFile("invalid ohos tzdata chunk")), + }; + } + + Err(Error::InvalidTzString("cannot find tz string within tzdata")) +} + +#[cfg(target_env = "ohos")] +fn from_tzdata_file(file: &mut File, tz_string: &str) -> Result, Error> { + let mut bytes = Vec::new(); + file.read_to_end(&mut bytes)?; + from_tzdata_bytes(&mut bytes, tz_string) +} + +#[cfg(target_env = "ohos")] +fn find_ohos_tz_data(tz_string: &str) -> Result, Error> { + const TZDATA_PATH: &str = "/system/etc/zoneinfo/tzdata"; + match File::open(TZDATA_PATH) { + Ok(mut file) => from_tzdata_file(&mut file, tz_string), + Err(err) => Err(err.into()), + } +} + +// Possible system timezone directories +#[cfg(unix)] +const ZONE_INFO_DIRECTORIES: [&str; 4] = + ["/usr/share/zoneinfo", "/share/zoneinfo", "/etc/zoneinfo", "/usr/share/lib/zoneinfo"]; + +/// Number of seconds in one week +pub(crate) const SECONDS_PER_WEEK: i64 = SECONDS_PER_DAY * DAYS_PER_WEEK; +/// Number of seconds in 28 days +const SECONDS_PER_28_DAYS: i64 = SECONDS_PER_DAY * 28; + +#[cfg(test)] +mod tests { + use super::super::Error; + use super::{LeapSecond, LocalTimeType, TimeZone, TimeZoneName, Transition, TransitionRule}; + + #[test] + fn test_no_dst() -> Result<(), Error> { + let tz_string = b"HST10"; + let transition_rule = TransitionRule::from_tz_string(tz_string, false)?; + assert_eq!(transition_rule, LocalTimeType::new(-36000, false, Some(b"HST"))?.into()); + Ok(()) + } + + #[test] + fn test_error() -> Result<(), Error> { + assert!(matches!( + TransitionRule::from_tz_string(b"IST-1GMT0", false), + Err(Error::UnsupportedTzString(_)) + )); + assert!(matches!( + TransitionRule::from_tz_string(b"EET-2EEST", false), + Err(Error::UnsupportedTzString(_)) + )); + + Ok(()) + } + + #[test] + fn test_v1_file_with_leap_seconds() -> Result<(), Error> { + let bytes = b"TZif\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x01\0\0\0\x01\0\0\0\x1b\0\0\0\0\0\0\0\x01\0\0\0\x04\0\0\0\0\0\0UTC\0\x04\xb2\x58\0\0\0\0\x01\x05\xa4\xec\x01\0\0\0\x02\x07\x86\x1f\x82\0\0\0\x03\x09\x67\x53\x03\0\0\0\x04\x0b\x48\x86\x84\0\0\0\x05\x0d\x2b\x0b\x85\0\0\0\x06\x0f\x0c\x3f\x06\0\0\0\x07\x10\xed\x72\x87\0\0\0\x08\x12\xce\xa6\x08\0\0\0\x09\x15\x9f\xca\x89\0\0\0\x0a\x17\x80\xfe\x0a\0\0\0\x0b\x19\x62\x31\x8b\0\0\0\x0c\x1d\x25\xea\x0c\0\0\0\x0d\x21\xda\xe5\x0d\0\0\0\x0e\x25\x9e\x9d\x8e\0\0\0\x0f\x27\x7f\xd1\x0f\0\0\0\x10\x2a\x50\xf5\x90\0\0\0\x11\x2c\x32\x29\x11\0\0\0\x12\x2e\x13\x5c\x92\0\0\0\x13\x30\xe7\x24\x13\0\0\0\x14\x33\xb8\x48\x94\0\0\0\x15\x36\x8c\x10\x15\0\0\0\x16\x43\xb7\x1b\x96\0\0\0\x17\x49\x5c\x07\x97\0\0\0\x18\x4f\xef\x93\x18\0\0\0\x19\x55\x93\x2d\x99\0\0\0\x1a\x58\x68\x46\x9a\0\0\0\x1b\0\0"; + + let time_zone = TimeZone::from_tz_data(bytes)?; + + let time_zone_result = TimeZone::new( + Vec::new(), + vec![LocalTimeType::new(0, false, Some(b"UTC"))?], + vec![ + LeapSecond::new(78796800, 1), + LeapSecond::new(94694401, 2), + LeapSecond::new(126230402, 3), + LeapSecond::new(157766403, 4), + LeapSecond::new(189302404, 5), + LeapSecond::new(220924805, 6), + LeapSecond::new(252460806, 7), + LeapSecond::new(283996807, 8), + LeapSecond::new(315532808, 9), + LeapSecond::new(362793609, 10), + LeapSecond::new(394329610, 11), + LeapSecond::new(425865611, 12), + LeapSecond::new(489024012, 13), + LeapSecond::new(567993613, 14), + LeapSecond::new(631152014, 15), + LeapSecond::new(662688015, 16), + LeapSecond::new(709948816, 17), + LeapSecond::new(741484817, 18), + LeapSecond::new(773020818, 19), + LeapSecond::new(820454419, 20), + LeapSecond::new(867715220, 21), + LeapSecond::new(915148821, 22), + LeapSecond::new(1136073622, 23), + LeapSecond::new(1230768023, 24), + LeapSecond::new(1341100824, 25), + LeapSecond::new(1435708825, 26), + LeapSecond::new(1483228826, 27), + ], + None, + )?; + + assert_eq!(time_zone, time_zone_result); + + Ok(()) + } + + #[test] + fn test_v2_file() -> Result<(), Error> { + let bytes = b"TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x06\0\0\0\x06\0\0\0\0\0\0\0\x07\0\0\0\x06\0\0\0\x14\x80\0\0\0\xbb\x05\x43\x48\xbb\x21\x71\x58\xcb\x89\x3d\xc8\xd2\x23\xf4\x70\xd2\x61\x49\x38\xd5\x8d\x73\x48\x01\x02\x01\x03\x04\x01\x05\xff\xff\x6c\x02\0\0\xff\xff\x6c\x58\0\x04\xff\xff\x7a\x68\x01\x08\xff\xff\x7a\x68\x01\x0c\xff\xff\x7a\x68\x01\x10\xff\xff\x73\x60\0\x04LMT\0HST\0HDT\0HWT\0HPT\0\0\0\0\0\x01\0\0\0\0\0\x01\0TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x06\0\0\0\x06\0\0\0\0\0\0\0\x07\0\0\0\x06\0\0\0\x14\xff\xff\xff\xff\x74\xe0\x70\xbe\xff\xff\xff\xff\xbb\x05\x43\x48\xff\xff\xff\xff\xbb\x21\x71\x58\xff\xff\xff\xff\xcb\x89\x3d\xc8\xff\xff\xff\xff\xd2\x23\xf4\x70\xff\xff\xff\xff\xd2\x61\x49\x38\xff\xff\xff\xff\xd5\x8d\x73\x48\x01\x02\x01\x03\x04\x01\x05\xff\xff\x6c\x02\0\0\xff\xff\x6c\x58\0\x04\xff\xff\x7a\x68\x01\x08\xff\xff\x7a\x68\x01\x0c\xff\xff\x7a\x68\x01\x10\xff\xff\x73\x60\0\x04LMT\0HST\0HDT\0HWT\0HPT\0\0\0\0\0\x01\0\0\0\0\0\x01\0\x0aHST10\x0a"; + + let time_zone = TimeZone::from_tz_data(bytes)?; + + let time_zone_result = TimeZone::new( + vec![ + Transition::new(-2334101314, 1), + Transition::new(-1157283000, 2), + Transition::new(-1155436200, 1), + Transition::new(-880198200, 3), + Transition::new(-769395600, 4), + Transition::new(-765376200, 1), + Transition::new(-712150200, 5), + ], + vec![ + LocalTimeType::new(-37886, false, Some(b"LMT"))?, + LocalTimeType::new(-37800, false, Some(b"HST"))?, + LocalTimeType::new(-34200, true, Some(b"HDT"))?, + LocalTimeType::new(-34200, true, Some(b"HWT"))?, + LocalTimeType::new(-34200, true, Some(b"HPT"))?, + LocalTimeType::new(-36000, false, Some(b"HST"))?, + ], + Vec::new(), + Some(TransitionRule::from(LocalTimeType::new(-36000, false, Some(b"HST"))?)), + )?; + + assert_eq!(time_zone, time_zone_result); + + assert_eq!( + *time_zone.find_local_time_type(-1156939200)?, + LocalTimeType::new(-34200, true, Some(b"HDT"))? + ); + assert_eq!( + *time_zone.find_local_time_type(1546300800)?, + LocalTimeType::new(-36000, false, Some(b"HST"))? + ); + + Ok(()) + } + + #[test] + fn test_no_tz_string() -> Result<(), Error> { + // Guayaquil from macOS 10.11 + let bytes = b"TZif\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x02\0\0\0\x02\0\0\0\0\0\0\0\x01\0\0\0\x02\0\0\0\x08\xb6\xa4B\x18\x01\xff\xff\xb6h\0\0\xff\xff\xb9\xb0\0\x04QMT\0ECT\0\0\0\0\0"; + + let time_zone = TimeZone::from_tz_data(bytes)?; + dbg!(&time_zone); + + let time_zone_result = TimeZone::new( + vec![Transition::new(-1230749160, 1)], + vec![ + LocalTimeType::new(-18840, false, Some(b"QMT"))?, + LocalTimeType::new(-18000, false, Some(b"ECT"))?, + ], + Vec::new(), + None, + )?; + + assert_eq!(time_zone, time_zone_result); + + assert_eq!( + *time_zone.find_local_time_type(-1500000000)?, + LocalTimeType::new(-18840, false, Some(b"QMT"))? + ); + assert_eq!( + *time_zone.find_local_time_type(0)?, + LocalTimeType::new(-18000, false, Some(b"ECT"))? + ); + + Ok(()) + } + + #[test] + fn test_tz_ascii_str() -> Result<(), Error> { + assert!(matches!(TimeZoneName::new(b""), Err(Error::LocalTimeType(_)))); + assert!(matches!(TimeZoneName::new(b"A"), Err(Error::LocalTimeType(_)))); + assert!(matches!(TimeZoneName::new(b"AB"), Err(Error::LocalTimeType(_)))); + assert_eq!(TimeZoneName::new(b"CET")?.as_bytes(), b"CET"); + assert_eq!(TimeZoneName::new(b"CHADT")?.as_bytes(), b"CHADT"); + assert_eq!(TimeZoneName::new(b"abcdefg")?.as_bytes(), b"abcdefg"); + assert_eq!(TimeZoneName::new(b"UTC+02")?.as_bytes(), b"UTC+02"); + assert_eq!(TimeZoneName::new(b"-1230")?.as_bytes(), b"-1230"); + assert!(matches!(TimeZoneName::new("−0330".as_bytes()), Err(Error::LocalTimeType(_)))); // MINUS SIGN (U+2212) + assert!(matches!(TimeZoneName::new(b"\x00123"), Err(Error::LocalTimeType(_)))); + assert!(matches!(TimeZoneName::new(b"12345678"), Err(Error::LocalTimeType(_)))); + assert!(matches!(TimeZoneName::new(b"GMT\0\0\0"), Err(Error::LocalTimeType(_)))); + + Ok(()) + } + + #[test] + fn test_time_zone() -> Result<(), Error> { + let utc = LocalTimeType::UTC; + let cet = LocalTimeType::with_offset(3600)?; + + let utc_local_time_types = vec![utc]; + let fixed_extra_rule = TransitionRule::from(cet); + + let time_zone_1 = TimeZone::new(vec![], utc_local_time_types.clone(), vec![], None)?; + let time_zone_2 = + TimeZone::new(vec![], utc_local_time_types.clone(), vec![], Some(fixed_extra_rule))?; + let time_zone_3 = + TimeZone::new(vec![Transition::new(0, 0)], utc_local_time_types.clone(), vec![], None)?; + let time_zone_4 = TimeZone::new( + vec![Transition::new(i32::MIN.into(), 0), Transition::new(0, 1)], + vec![utc, cet], + Vec::new(), + Some(fixed_extra_rule), + )?; + + assert_eq!(*time_zone_1.find_local_time_type(0)?, utc); + assert_eq!(*time_zone_2.find_local_time_type(0)?, cet); + + assert_eq!(*time_zone_3.find_local_time_type(-1)?, utc); + assert_eq!(*time_zone_3.find_local_time_type(0)?, utc); + + assert_eq!(*time_zone_4.find_local_time_type(-1)?, utc); + assert_eq!(*time_zone_4.find_local_time_type(0)?, cet); + + let time_zone_err = TimeZone::new( + vec![Transition::new(0, 0)], + utc_local_time_types, + vec![], + Some(fixed_extra_rule), + ); + assert!(time_zone_err.is_err()); + + Ok(()) + } + + #[test] + fn test_time_zone_from_posix_tz() -> Result<(), Error> { + #[cfg(unix)] + { + // if the TZ var is set, this essentially _overrides_ the + // time set by the localtime symlink + // so just ensure that ::local() acts as expected + // in this case + if let Ok(tz) = std::env::var("TZ") { + let time_zone_local = TimeZone::local(Some(tz.as_str()))?; + let time_zone_local_1 = TimeZone::from_posix_tz(&tz)?; + assert_eq!(time_zone_local, time_zone_local_1); + } + + // `TimeZone::from_posix_tz("UTC")` will return `Error` if the environment does not have + // a time zone database, like for example some docker containers. + // In that case skip the test. + if let Ok(time_zone_utc) = TimeZone::from_posix_tz("UTC") { + assert_eq!(time_zone_utc.find_local_time_type(0)?.offset(), 0); + } + } + + assert!(TimeZone::from_posix_tz("EST5EDT,0/0,J365/25").is_err()); + assert!(TimeZone::from_posix_tz("").is_err()); + + Ok(()) + } + + #[test] + fn test_leap_seconds() -> Result<(), Error> { + let time_zone = TimeZone::new( + Vec::new(), + vec![LocalTimeType::new(0, false, Some(b"UTC"))?], + vec![ + LeapSecond::new(78796800, 1), + LeapSecond::new(94694401, 2), + LeapSecond::new(126230402, 3), + LeapSecond::new(157766403, 4), + LeapSecond::new(189302404, 5), + LeapSecond::new(220924805, 6), + LeapSecond::new(252460806, 7), + LeapSecond::new(283996807, 8), + LeapSecond::new(315532808, 9), + LeapSecond::new(362793609, 10), + LeapSecond::new(394329610, 11), + LeapSecond::new(425865611, 12), + LeapSecond::new(489024012, 13), + LeapSecond::new(567993613, 14), + LeapSecond::new(631152014, 15), + LeapSecond::new(662688015, 16), + LeapSecond::new(709948816, 17), + LeapSecond::new(741484817, 18), + LeapSecond::new(773020818, 19), + LeapSecond::new(820454419, 20), + LeapSecond::new(867715220, 21), + LeapSecond::new(915148821, 22), + LeapSecond::new(1136073622, 23), + LeapSecond::new(1230768023, 24), + LeapSecond::new(1341100824, 25), + LeapSecond::new(1435708825, 26), + LeapSecond::new(1483228826, 27), + ], + None, + )?; + + let time_zone_ref = time_zone.as_ref(); + + assert!(matches!(time_zone_ref.unix_leap_time_to_unix_time(1136073621), Ok(1136073599))); + assert!(matches!(time_zone_ref.unix_leap_time_to_unix_time(1136073622), Ok(1136073600))); + assert!(matches!(time_zone_ref.unix_leap_time_to_unix_time(1136073623), Ok(1136073600))); + assert!(matches!(time_zone_ref.unix_leap_time_to_unix_time(1136073624), Ok(1136073601))); + + assert!(matches!(time_zone_ref.unix_time_to_unix_leap_time(1136073599), Ok(1136073621))); + assert!(matches!(time_zone_ref.unix_time_to_unix_leap_time(1136073600), Ok(1136073623))); + assert!(matches!(time_zone_ref.unix_time_to_unix_leap_time(1136073601), Ok(1136073624))); + + Ok(()) + } + + #[test] + fn test_leap_seconds_overflow() -> Result<(), Error> { + let time_zone_err = TimeZone::new( + vec![Transition::new(i64::MIN, 0)], + vec![LocalTimeType::UTC], + vec![LeapSecond::new(0, 1)], + Some(TransitionRule::from(LocalTimeType::UTC)), + ); + assert!(time_zone_err.is_err()); + + let time_zone = TimeZone::new( + vec![Transition::new(i64::MAX, 0)], + vec![LocalTimeType::UTC], + vec![LeapSecond::new(0, 1)], + None, + )?; + assert!(matches!( + time_zone.find_local_time_type(i64::MAX), + Err(Error::FindLocalTimeType(_)) + )); + + Ok(()) + } +} diff --git a/third_party/rust/chrono/src/offset/local/unix.rs b/third_party/rust/chrono/src/offset/local/unix.rs new file mode 100644 index 00000000000..b5fb342b951 --- /dev/null +++ b/third_party/rust/chrono/src/offset/local/unix.rs @@ -0,0 +1,171 @@ +// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// 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. + +use std::{cell::RefCell, collections::hash_map, env, fs, hash::Hasher, time::SystemTime}; + +use super::tz_info::TimeZone; +use super::{FixedOffset, NaiveDateTime}; +use crate::MappedLocalTime; + +pub(super) fn offset_from_utc_datetime(utc: &NaiveDateTime) -> MappedLocalTime { + offset(utc, false) +} + +pub(super) fn offset_from_local_datetime(local: &NaiveDateTime) -> MappedLocalTime { + offset(local, true) +} + +fn offset(d: &NaiveDateTime, local: bool) -> MappedLocalTime { + TZ_INFO.with(|maybe_cache| { + maybe_cache.borrow_mut().get_or_insert_with(Cache::default).offset(*d, local) + }) +} + +// we have to store the `Cache` in an option as it can't +// be initialized in a static context. +thread_local! { + static TZ_INFO: RefCell> = Default::default(); +} + +enum Source { + LocalTime { mtime: SystemTime }, + Environment { hash: u64 }, +} + +impl Source { + fn new(env_tz: Option<&str>) -> Source { + match env_tz { + Some(tz) => { + let mut hasher = hash_map::DefaultHasher::new(); + hasher.write(tz.as_bytes()); + let hash = hasher.finish(); + Source::Environment { hash } + } + None => match fs::symlink_metadata("/etc/localtime") { + Ok(data) => Source::LocalTime { + // we have to pick a sensible default when the mtime fails + // by picking SystemTime::now() we raise the probability of + // the cache being invalidated if/when the mtime starts working + mtime: data.modified().unwrap_or_else(|_| SystemTime::now()), + }, + Err(_) => { + // as above, now() should be a better default than some constant + // TODO: see if we can improve caching in the case where the fallback is a valid timezone + Source::LocalTime { mtime: SystemTime::now() } + } + }, + } + } +} + +struct Cache { + zone: TimeZone, + source: Source, + last_checked: SystemTime, +} + +#[cfg(target_os = "aix")] +const TZDB_LOCATION: &str = "/usr/share/lib/zoneinfo"; + +#[cfg(not(any(target_os = "android", target_os = "aix")))] +const TZDB_LOCATION: &str = "/usr/share/zoneinfo"; + +fn fallback_timezone() -> Option { + let tz_name = iana_time_zone::get_timezone().ok()?; + #[cfg(not(target_os = "android"))] + let bytes = fs::read(format!("{}/{}", TZDB_LOCATION, tz_name)).ok()?; + #[cfg(target_os = "android")] + let bytes = android_tzdata::find_tz_data(&tz_name).ok()?; + TimeZone::from_tz_data(&bytes).ok() +} + +impl Default for Cache { + fn default() -> Cache { + // default to UTC if no local timezone can be found + let env_tz = env::var("TZ").ok(); + let env_ref = env_tz.as_deref(); + Cache { + last_checked: SystemTime::now(), + source: Source::new(env_ref), + zone: current_zone(env_ref), + } + } +} + +fn current_zone(var: Option<&str>) -> TimeZone { + TimeZone::local(var).ok().or_else(fallback_timezone).unwrap_or_else(TimeZone::utc) +} + +impl Cache { + fn offset(&mut self, d: NaiveDateTime, local: bool) -> MappedLocalTime { + let now = SystemTime::now(); + + match now.duration_since(self.last_checked) { + // If the cache has been around for less than a second then we reuse it + // unconditionally. This is a reasonable tradeoff because the timezone + // generally won't be changing _that_ often, but if the time zone does + // change, it will reflect sufficiently quickly from an application + // user's perspective. + Ok(d) if d.as_secs() < 1 => (), + Ok(_) | Err(_) => { + let env_tz = env::var("TZ").ok(); + let env_ref = env_tz.as_deref(); + let new_source = Source::new(env_ref); + + let out_of_date = match (&self.source, &new_source) { + // change from env to file or file to env, must recreate the zone + (Source::Environment { .. }, Source::LocalTime { .. }) + | (Source::LocalTime { .. }, Source::Environment { .. }) => true, + // stay as file, but mtime has changed + (Source::LocalTime { mtime: old_mtime }, Source::LocalTime { mtime }) + if old_mtime != mtime => + { + true + } + // stay as env, but hash of variable has changed + (Source::Environment { hash: old_hash }, Source::Environment { hash }) + if old_hash != hash => + { + true + } + // cache can be reused + _ => false, + }; + + if out_of_date { + self.zone = current_zone(env_ref); + } + + self.last_checked = now; + self.source = new_source; + } + } + + if !local { + let offset = self + .zone + .find_local_time_type(d.and_utc().timestamp()) + .expect("unable to select local time type") + .offset(); + + return match FixedOffset::east_opt(offset) { + Some(offset) => MappedLocalTime::Single(offset), + None => MappedLocalTime::None, + }; + } + + // we pass through the year as the year of a local point in time must either be valid in that locale, or + // the entire time was skipped in which case we will return MappedLocalTime::None anyway. + self.zone + .find_local_time_type_from_local(d) + .expect("unable to select local time type") + .and_then(|o| FixedOffset::east_opt(o.offset())) + } +} diff --git a/third_party/rust/chrono/src/offset/local/win_bindings.rs b/third_party/rust/chrono/src/offset/local/win_bindings.rs new file mode 100644 index 00000000000..b8e6b6d2a22 --- /dev/null +++ b/third_party/rust/chrono/src/offset/local/win_bindings.rs @@ -0,0 +1,49 @@ +#![allow(non_snake_case, non_upper_case_globals, non_camel_case_types, dead_code, clippy::all)] + +windows_link::link!("kernel32.dll" "system" fn GetTimeZoneInformationForYear(wyear : u16, pdtzi : *const DYNAMIC_TIME_ZONE_INFORMATION, ptzi : *mut TIME_ZONE_INFORMATION) -> BOOL); +windows_link::link!("kernel32.dll" "system" fn SystemTimeToFileTime(lpsystemtime : *const SYSTEMTIME, lpfiletime : *mut FILETIME) -> BOOL); +windows_link::link!("kernel32.dll" "system" fn SystemTimeToTzSpecificLocalTime(lptimezoneinformation : *const TIME_ZONE_INFORMATION, lpuniversaltime : *const SYSTEMTIME, lplocaltime : *mut SYSTEMTIME) -> BOOL); +windows_link::link!("kernel32.dll" "system" fn TzSpecificLocalTimeToSystemTime(lptimezoneinformation : *const TIME_ZONE_INFORMATION, lplocaltime : *const SYSTEMTIME, lpuniversaltime : *mut SYSTEMTIME) -> BOOL); +pub type BOOL = i32; +#[repr(C)] +#[derive(Clone, Copy)] +pub struct DYNAMIC_TIME_ZONE_INFORMATION { + pub Bias: i32, + pub StandardName: [u16; 32], + pub StandardDate: SYSTEMTIME, + pub StandardBias: i32, + pub DaylightName: [u16; 32], + pub DaylightDate: SYSTEMTIME, + pub DaylightBias: i32, + pub TimeZoneKeyName: [u16; 128], + pub DynamicDaylightTimeDisabled: bool, +} +#[repr(C)] +#[derive(Clone, Copy)] +pub struct FILETIME { + pub dwLowDateTime: u32, + pub dwHighDateTime: u32, +} +#[repr(C)] +#[derive(Clone, Copy)] +pub struct SYSTEMTIME { + pub wYear: u16, + pub wMonth: u16, + pub wDayOfWeek: u16, + pub wDay: u16, + pub wHour: u16, + pub wMinute: u16, + pub wSecond: u16, + pub wMilliseconds: u16, +} +#[repr(C)] +#[derive(Clone, Copy)] +pub struct TIME_ZONE_INFORMATION { + pub Bias: i32, + pub StandardName: [u16; 32], + pub StandardDate: SYSTEMTIME, + pub StandardBias: i32, + pub DaylightName: [u16; 32], + pub DaylightDate: SYSTEMTIME, + pub DaylightBias: i32, +} diff --git a/third_party/rust/chrono/src/offset/local/win_bindings.txt b/third_party/rust/chrono/src/offset/local/win_bindings.txt new file mode 100644 index 00000000000..2efa4314fc4 --- /dev/null +++ b/third_party/rust/chrono/src/offset/local/win_bindings.txt @@ -0,0 +1,7 @@ +--out src/offset/local/win_bindings.rs +--flat --sys --no-comment +--filter + GetTimeZoneInformationForYear + SystemTimeToFileTime + SystemTimeToTzSpecificLocalTime + TzSpecificLocalTimeToSystemTime diff --git a/third_party/rust/chrono/src/offset/local/windows.rs b/third_party/rust/chrono/src/offset/local/windows.rs new file mode 100644 index 00000000000..dcab6c44e60 --- /dev/null +++ b/third_party/rust/chrono/src/offset/local/windows.rs @@ -0,0 +1,293 @@ +// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// 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. + +use std::cmp::Ordering; +use std::mem::MaybeUninit; +use std::ptr; + +use super::win_bindings::{GetTimeZoneInformationForYear, SYSTEMTIME, TIME_ZONE_INFORMATION}; + +use crate::offset::local::{Transition, lookup_with_dst_transitions}; +use crate::{Datelike, FixedOffset, MappedLocalTime, NaiveDate, NaiveDateTime, NaiveTime, Weekday}; + +// We don't use `SystemTimeToTzSpecificLocalTime` because it doesn't support the same range of dates +// as Chrono. Also it really isn't that difficult to work out the correct offset from the provided +// DST rules. +// +// This method uses `overflowing_sub_offset` because it is no problem if the transition time in UTC +// falls a couple of hours inside the buffer space around the `NaiveDateTime` range (although it is +// very theoretical to have a transition at midnight around `NaiveDate::(MIN|MAX)`. +pub(super) fn offset_from_utc_datetime(utc: &NaiveDateTime) -> MappedLocalTime { + // Using a `TzInfo` based on the year of an UTC datetime is technically wrong, we should be + // using the rules for the year of the corresponding local time. But this matches what + // `SystemTimeToTzSpecificLocalTime` is documented to do. + let tz_info = match TzInfo::for_year(utc.year()) { + Some(tz_info) => tz_info, + None => return MappedLocalTime::None, + }; + let offset = match (tz_info.std_transition, tz_info.dst_transition) { + (Some(std_transition), Some(dst_transition)) => { + let std_transition_utc = std_transition.overflowing_sub_offset(tz_info.dst_offset); + let dst_transition_utc = dst_transition.overflowing_sub_offset(tz_info.std_offset); + if dst_transition_utc < std_transition_utc { + match utc >= &dst_transition_utc && utc < &std_transition_utc { + true => tz_info.dst_offset, + false => tz_info.std_offset, + } + } else { + match utc >= &std_transition_utc && utc < &dst_transition_utc { + true => tz_info.std_offset, + false => tz_info.dst_offset, + } + } + } + (Some(std_transition), None) => { + let std_transition_utc = std_transition.overflowing_sub_offset(tz_info.dst_offset); + match utc < &std_transition_utc { + true => tz_info.dst_offset, + false => tz_info.std_offset, + } + } + (None, Some(dst_transition)) => { + let dst_transition_utc = dst_transition.overflowing_sub_offset(tz_info.std_offset); + match utc < &dst_transition_utc { + true => tz_info.std_offset, + false => tz_info.dst_offset, + } + } + (None, None) => tz_info.std_offset, + }; + MappedLocalTime::Single(offset) +} + +// We don't use `TzSpecificLocalTimeToSystemTime` because it doesn't let us choose how to handle +// ambiguous cases (during a DST transition). Instead we get the timezone information for the +// current year and compute it ourselves, like we do on Unix. +pub(super) fn offset_from_local_datetime(local: &NaiveDateTime) -> MappedLocalTime { + let tz_info = match TzInfo::for_year(local.year()) { + Some(tz_info) => tz_info, + None => return MappedLocalTime::None, + }; + // Create a sorted slice of transitions and use `lookup_with_dst_transitions`. + match (tz_info.std_transition, tz_info.dst_transition) { + (Some(std_transition), Some(dst_transition)) => { + let std_transition = + Transition::new(std_transition, tz_info.dst_offset, tz_info.std_offset); + let dst_transition = + Transition::new(dst_transition, tz_info.std_offset, tz_info.dst_offset); + let transitions = match std_transition.cmp(&dst_transition) { + Ordering::Less => [std_transition, dst_transition], + Ordering::Greater => [dst_transition, std_transition], + Ordering::Equal => { + // This doesn't make sense. Let's just return the standard offset. + return MappedLocalTime::Single(tz_info.std_offset); + } + }; + lookup_with_dst_transitions(&transitions, *local) + } + (Some(std_transition), None) => { + let transitions = + [Transition::new(std_transition, tz_info.dst_offset, tz_info.std_offset)]; + lookup_with_dst_transitions(&transitions, *local) + } + (None, Some(dst_transition)) => { + let transitions = + [Transition::new(dst_transition, tz_info.std_offset, tz_info.dst_offset)]; + lookup_with_dst_transitions(&transitions, *local) + } + (None, None) => MappedLocalTime::Single(tz_info.std_offset), + } +} + +// The basis for Windows timezone and DST support has been in place since Windows 2000. It does not +// allow for complex rules like the IANA timezone database: +// - A timezone has the same base offset the whole year. +// - There seem to be either zero or two DST transitions (but we support having just one). +// - As of Vista(?) only years from 2004 until a few years into the future are supported. +// - All other years get the base settings, which seem to be that of the current year. +// +// These details don't matter much, we just work with the offsets and transition dates Windows +// returns through `GetTimeZoneInformationForYear` for a particular year. +struct TzInfo { + // Offset from UTC during standard time. + std_offset: FixedOffset, + // Offset from UTC during daylight saving time. + dst_offset: FixedOffset, + // Transition from standard time to daylight saving time, given in local standard time. + std_transition: Option, + // Transition from daylight saving time to standard time, given in local daylight saving time. + dst_transition: Option, +} + +impl TzInfo { + fn for_year(year: i32) -> Option { + // The API limits years to 1601..=30827. + // Working with timezones and daylight saving time this far into the past or future makes + // little sense. But whatever is extrapolated for 1601 or 30827 is what can be extrapolated + // for years beyond. + let ref_year = year.clamp(1601, 30827) as u16; + let tz_info = unsafe { + let mut tz_info = MaybeUninit::::uninit(); + if GetTimeZoneInformationForYear(ref_year, ptr::null_mut(), tz_info.as_mut_ptr()) == 0 { + return None; + } + tz_info.assume_init() + }; + let std_offset = (tz_info.Bias) + .checked_add(tz_info.StandardBias) + .and_then(|o| o.checked_mul(60)) + .and_then(FixedOffset::west_opt)?; + let dst_offset = (tz_info.Bias) + .checked_add(tz_info.DaylightBias) + .and_then(|o| o.checked_mul(60)) + .and_then(FixedOffset::west_opt)?; + Some(TzInfo { + std_offset, + dst_offset, + std_transition: naive_date_time_from_system_time(tz_info.StandardDate, year).ok()?, + dst_transition: naive_date_time_from_system_time(tz_info.DaylightDate, year).ok()?, + }) + } +} + +/// Resolve a `SYSTEMTIME` object to an `Option`. +/// +/// A `SYSTEMTIME` within a `TIME_ZONE_INFORMATION` struct can be zero to indicate there is no +/// transition. +/// If it has year, month and day values it is a concrete date. +/// If the year is missing the `SYSTEMTIME` is a rule, which this method resolves for the provided +/// year. A rule has a month, weekday, and nth weekday of the month as components. +/// +/// Returns `Err` if any of the values is invalid, which should never happen. +fn naive_date_time_from_system_time( + st: SYSTEMTIME, + year: i32, +) -> Result, ()> { + if st.wYear == 0 && st.wMonth == 0 { + return Ok(None); + } + let time = NaiveTime::from_hms_milli_opt( + st.wHour as u32, + st.wMinute as u32, + st.wSecond as u32, + st.wMilliseconds as u32, + ) + .ok_or(())?; + + if st.wYear != 0 { + // We have a concrete date. + let date = + NaiveDate::from_ymd_opt(st.wYear as i32, st.wMonth as u32, st.wDay as u32).ok_or(())?; + return Ok(Some(date.and_time(time))); + } + + // Resolve a rule with month, weekday, and nth weekday of the month to a date in the current + // year. + let weekday = match st.wDayOfWeek { + 0 => Weekday::Sun, + 1 => Weekday::Mon, + 2 => Weekday::Tue, + 3 => Weekday::Wed, + 4 => Weekday::Thu, + 5 => Weekday::Fri, + 6 => Weekday::Sat, + _ => return Err(()), + }; + let nth_day = match st.wDay { + 1..=5 => st.wDay as u8, + _ => return Err(()), + }; + let date = NaiveDate::from_weekday_of_month_opt(year, st.wMonth as u32, weekday, nth_day) + .or_else(|| NaiveDate::from_weekday_of_month_opt(year, st.wMonth as u32, weekday, 4)) + .ok_or(())?; // `st.wMonth` must be invalid + Ok(Some(date.and_time(time))) +} + +#[cfg(test)] +mod tests { + use crate::offset::local::win_bindings::{ + FILETIME, SYSTEMTIME, SystemTimeToFileTime, TzSpecificLocalTimeToSystemTime, + }; + use crate::{DateTime, FixedOffset, Local, NaiveDate, NaiveDateTime, TimeDelta}; + use crate::{Datelike, TimeZone, Timelike}; + use std::mem::MaybeUninit; + use std::ptr; + + #[test] + fn verify_against_tz_specific_local_time_to_system_time() { + // The implementation in Windows itself is the source of truth on how to work with the OS + // timezone information. This test compares for every hour over a period of 125 years our + // implementation to `TzSpecificLocalTimeToSystemTime`. + // + // This uses parts of a previous Windows `Local` implementation in chrono. + fn from_local_time(dt: &NaiveDateTime) -> DateTime { + let st = system_time_from_naive_date_time(dt); + let utc_time = local_to_utc_time(&st); + let utc_secs = system_time_as_unix_seconds(&utc_time); + let local_secs = system_time_as_unix_seconds(&st); + let offset = (local_secs - utc_secs) as i32; + let offset = FixedOffset::east_opt(offset).unwrap(); + DateTime::from_naive_utc_and_offset(*dt - offset, offset) + } + fn system_time_from_naive_date_time(dt: &NaiveDateTime) -> SYSTEMTIME { + SYSTEMTIME { + // Valid values: 1601-30827 + wYear: dt.year() as u16, + // Valid values:1-12 + wMonth: dt.month() as u16, + // Valid values: 0-6, starting Sunday. + // NOTE: enum returns 1-7, starting Monday, so we are + // off here, but this is not currently used in local. + wDayOfWeek: dt.weekday() as u16, + // Valid values: 1-31 + wDay: dt.day() as u16, + // Valid values: 0-23 + wHour: dt.hour() as u16, + // Valid values: 0-59 + wMinute: dt.minute() as u16, + // Valid values: 0-59 + wSecond: dt.second() as u16, + // Valid values: 0-999 + wMilliseconds: 0, + } + } + fn local_to_utc_time(local: &SYSTEMTIME) -> SYSTEMTIME { + let mut sys_time = MaybeUninit::::uninit(); + unsafe { TzSpecificLocalTimeToSystemTime(ptr::null(), local, sys_time.as_mut_ptr()) }; + // SAFETY: TzSpecificLocalTimeToSystemTime must have succeeded at this point, so we can + // assume the value is initialized. + unsafe { sys_time.assume_init() } + } + const HECTONANOSECS_IN_SEC: i64 = 10_000_000; + const HECTONANOSEC_TO_UNIX_EPOCH: i64 = 11_644_473_600 * HECTONANOSECS_IN_SEC; + fn system_time_as_unix_seconds(st: &SYSTEMTIME) -> i64 { + let mut init = MaybeUninit::::uninit(); + unsafe { + SystemTimeToFileTime(st, init.as_mut_ptr()); + } + // SystemTimeToFileTime must have succeeded at this point, so we can assume the value is + // initialized. + let filetime = unsafe { init.assume_init() }; + let bit_shift = + ((filetime.dwHighDateTime as u64) << 32) | (filetime.dwLowDateTime as u64); + (bit_shift as i64 - HECTONANOSEC_TO_UNIX_EPOCH) / HECTONANOSECS_IN_SEC + } + + let mut date = NaiveDate::from_ymd_opt(1975, 1, 1).unwrap().and_hms_opt(0, 30, 0).unwrap(); + + while date.year() < 2078 { + // Windows doesn't handle non-existing dates, it just treats it as valid. + if let Some(our_result) = Local.from_local_datetime(&date).earliest() { + assert_eq!(from_local_time(&date), our_result); + } + date += TimeDelta::try_hours(1).unwrap(); + } + } +} diff --git a/third_party/rust/chrono/src/offset/mod.rs b/third_party/rust/chrono/src/offset/mod.rs index 0da6bfb424b..09f6821dce0 100644 --- a/third_party/rust/chrono/src/offset/mod.rs +++ b/third_party/rust/chrono/src/offset/mod.rs @@ -20,71 +20,144 @@ use core::fmt; -use format::{parse, ParseResult, Parsed, StrftimeItems}; -use naive::{NaiveDate, NaiveDateTime, NaiveTime}; -use Weekday; -use {Date, DateTime}; +use crate::Weekday; +use crate::format::{ParseResult, Parsed, StrftimeItems, parse}; +use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; +#[allow(deprecated)] +use crate::{Date, DateTime}; -/// The conversion result from the local time to the timezone-aware datetime types. +pub(crate) mod fixed; +pub use self::fixed::FixedOffset; + +#[cfg(feature = "clock")] +pub(crate) mod local; +#[cfg(feature = "clock")] +pub use self::local::Local; + +pub(crate) mod utc; +pub use self::utc::Utc; + +/// The result of mapping a local time to a concrete instant in a given time zone. +/// +/// The calculation to go from a local time (wall clock time) to an instant in UTC can end up in +/// three cases: +/// * A single, simple result. +/// * An ambiguous result when the clock is turned backwards during a transition due to for example +/// DST. +/// * No result when the clock is turned forwards during a transition due to for example DST. +/// +/// When the clock is turned backwards it creates a _fold_ in local time, during which the local +/// time is _ambiguous_. When the clock is turned forwards it creates a _gap_ in local time, during +/// which the local time is _missing_, or does not exist. +/// +/// Chrono does not return a default choice or invalid data during time zone transitions, but has +/// the `MappedLocalTime` type to help deal with the result correctly. +/// +/// The type of `T` is usually a [`DateTime`] but may also be only an offset. +pub type MappedLocalTime = LocalResult; #[derive(Clone, PartialEq, Debug, Copy, Eq, Hash)] + +/// Old name of [`MappedLocalTime`]. See that type for more documentation. pub enum LocalResult { - /// Given local time representation is invalid. - /// This can occur when, for example, the positive timezone transition. - None, - /// Given local time representation has a single unique result. + /// The local time maps to a single unique result. Single(T), - /// Given local time representation has multiple results and thus ambiguous. - /// This can occur when, for example, the negative timezone transition. - Ambiguous(T /*min*/, T /*max*/), + + /// The local time is _ambiguous_ because there is a _fold_ in the local time. + /// + /// This variant contains the two possible results, in the order `(earliest, latest)`. + Ambiguous(T, T), + + /// The local time does not exist because there is a _gap_ in the local time. + /// + /// This variant may also be returned if there was an error while resolving the local time, + /// caused by for example missing time zone data files, an error in an OS API, or overflow. + None, } -impl LocalResult { - /// Returns `Some` only when the conversion result is unique, or `None` otherwise. +impl MappedLocalTime { + /// Returns `Some` if the time zone mapping has a single result. + /// + /// # Errors + /// + /// Returns `None` if local time falls in a _fold_ or _gap_ in the local time, or if there was + /// an error. + #[must_use] pub fn single(self) -> Option { match self { - LocalResult::Single(t) => Some(t), + MappedLocalTime::Single(t) => Some(t), _ => None, } } - /// Returns `Some` for the earliest possible conversion result, or `None` if none. + /// Returns the earliest possible result of the time zone mapping. + /// + /// # Errors + /// + /// Returns `None` if local time falls in a _gap_ in the local time, or if there was an error. + #[must_use] pub fn earliest(self) -> Option { match self { - LocalResult::Single(t) | LocalResult::Ambiguous(t, _) => Some(t), + MappedLocalTime::Single(t) | MappedLocalTime::Ambiguous(t, _) => Some(t), _ => None, } } - /// Returns `Some` for the latest possible conversion result, or `None` if none. + /// Returns the latest possible result of the time zone mapping. + /// + /// # Errors + /// + /// Returns `None` if local time falls in a _gap_ in the local time, or if there was an error. + #[must_use] pub fn latest(self) -> Option { match self { - LocalResult::Single(t) | LocalResult::Ambiguous(_, t) => Some(t), + MappedLocalTime::Single(t) | MappedLocalTime::Ambiguous(_, t) => Some(t), _ => None, } } - /// Maps a `LocalResult` into `LocalResult` with given function. - pub fn map U>(self, mut f: F) -> LocalResult { + /// Maps a `MappedLocalTime` into `MappedLocalTime` with given function. + #[must_use] + pub fn map U>(self, mut f: F) -> MappedLocalTime { match self { - LocalResult::None => LocalResult::None, - LocalResult::Single(v) => LocalResult::Single(f(v)), - LocalResult::Ambiguous(min, max) => LocalResult::Ambiguous(f(min), f(max)), + MappedLocalTime::None => MappedLocalTime::None, + MappedLocalTime::Single(v) => MappedLocalTime::Single(f(v)), + MappedLocalTime::Ambiguous(min, max) => MappedLocalTime::Ambiguous(f(min), f(max)), + } + } + + /// Maps a `MappedLocalTime` into `MappedLocalTime` with given function. + /// + /// Returns `MappedLocalTime::None` if the function returns `None`. + #[must_use] + pub(crate) fn and_then Option>(self, mut f: F) -> MappedLocalTime { + match self { + MappedLocalTime::None => MappedLocalTime::None, + MappedLocalTime::Single(v) => match f(v) { + Some(new) => MappedLocalTime::Single(new), + None => MappedLocalTime::None, + }, + MappedLocalTime::Ambiguous(min, max) => match (f(min), f(max)) { + (Some(min), Some(max)) => MappedLocalTime::Ambiguous(min, max), + _ => MappedLocalTime::None, + }, } } } -impl LocalResult> { +#[allow(deprecated)] +impl MappedLocalTime> { /// Makes a new `DateTime` from the current date and given `NaiveTime`. /// The offset in the current date is preserved. /// /// Propagates any error. Ambiguous result would be discarded. #[inline] - pub fn and_time(self, time: NaiveTime) -> LocalResult> { + #[must_use] + pub fn and_time(self, time: NaiveTime) -> MappedLocalTime> { match self { - LocalResult::Single(d) => { - d.and_time(time).map_or(LocalResult::None, LocalResult::Single) + MappedLocalTime::Single(d) => { + d.and_time(time).map_or(MappedLocalTime::None, MappedLocalTime::Single) } - _ => LocalResult::None, + _ => MappedLocalTime::None, } } @@ -93,12 +166,13 @@ impl LocalResult> { /// /// Propagates any error. Ambiguous result would be discarded. #[inline] - pub fn and_hms_opt(self, hour: u32, min: u32, sec: u32) -> LocalResult> { + #[must_use] + pub fn and_hms_opt(self, hour: u32, min: u32, sec: u32) -> MappedLocalTime> { match self { - LocalResult::Single(d) => { - d.and_hms_opt(hour, min, sec).map_or(LocalResult::None, LocalResult::Single) + MappedLocalTime::Single(d) => { + d.and_hms_opt(hour, min, sec).map_or(MappedLocalTime::None, MappedLocalTime::Single) } - _ => LocalResult::None, + _ => MappedLocalTime::None, } } @@ -108,18 +182,19 @@ impl LocalResult> { /// /// Propagates any error. Ambiguous result would be discarded. #[inline] + #[must_use] pub fn and_hms_milli_opt( self, hour: u32, min: u32, sec: u32, milli: u32, - ) -> LocalResult> { + ) -> MappedLocalTime> { match self { - LocalResult::Single(d) => d + MappedLocalTime::Single(d) => d .and_hms_milli_opt(hour, min, sec, milli) - .map_or(LocalResult::None, LocalResult::Single), - _ => LocalResult::None, + .map_or(MappedLocalTime::None, MappedLocalTime::Single), + _ => MappedLocalTime::None, } } @@ -129,18 +204,19 @@ impl LocalResult> { /// /// Propagates any error. Ambiguous result would be discarded. #[inline] + #[must_use] pub fn and_hms_micro_opt( self, hour: u32, min: u32, sec: u32, micro: u32, - ) -> LocalResult> { + ) -> MappedLocalTime> { match self { - LocalResult::Single(d) => d + MappedLocalTime::Single(d) => d .and_hms_micro_opt(hour, min, sec, micro) - .map_or(LocalResult::None, LocalResult::Single), - _ => LocalResult::None, + .map_or(MappedLocalTime::None, MappedLocalTime::Single), + _ => MappedLocalTime::None, } } @@ -150,29 +226,41 @@ impl LocalResult> { /// /// Propagates any error. Ambiguous result would be discarded. #[inline] + #[must_use] pub fn and_hms_nano_opt( self, hour: u32, min: u32, sec: u32, nano: u32, - ) -> LocalResult> { + ) -> MappedLocalTime> { match self { - LocalResult::Single(d) => d + MappedLocalTime::Single(d) => d .and_hms_nano_opt(hour, min, sec, nano) - .map_or(LocalResult::None, LocalResult::Single), - _ => LocalResult::None, + .map_or(MappedLocalTime::None, MappedLocalTime::Single), + _ => MappedLocalTime::None, } } } -impl LocalResult { - /// Returns the single unique conversion result, or panics accordingly. +impl MappedLocalTime { + /// Returns a single unique conversion result or panics. + /// + /// `unwrap()` is best combined with time zone types where the mapping can never fail like + /// [`Utc`] and [`FixedOffset`]. Note that for [`FixedOffset`] there is a rare case where a + /// resulting [`DateTime`] can be out of range. + /// + /// # Panics + /// + /// Panics if the local time falls within a _fold_ or a _gap_ in the local time, and on any + /// error that may have been returned by the type implementing [`TimeZone`]. + #[must_use] + #[track_caller] pub fn unwrap(self) -> T { match self { - LocalResult::None => panic!("No such local time"), - LocalResult::Single(t) => t, - LocalResult::Ambiguous(t1, t2) => { + MappedLocalTime::None => panic!("No such local time"), + MappedLocalTime::Single(t) => t, + MappedLocalTime::Ambiguous(t1, t2) => { panic!("Ambiguous local time, ranging from {:?} to {:?}", t1, t2) } } @@ -187,14 +275,34 @@ pub trait Offset: Sized + Clone + fmt::Debug { /// The time zone. /// -/// The methods here are the primarily constructors for [`Date`](../struct.Date.html) and -/// [`DateTime`](../struct.DateTime.html) types. +/// The methods here are the primary constructors for the [`DateTime`] type. pub trait TimeZone: Sized + Clone { /// An associated offset type. /// This type is used to store the actual offset in date and time types. /// The original `TimeZone` value can be recovered via `TimeZone::from_offset`. type Offset: Offset; + /// Make a new `DateTime` from year, month, day, time components and current time zone. + /// + /// This assumes the proleptic Gregorian calendar, with the year 0 being 1 BCE. + /// + /// Returns `MappedLocalTime::None` on invalid input data. + fn with_ymd_and_hms( + &self, + year: i32, + month: u32, + day: u32, + hour: u32, + min: u32, + sec: u32, + ) -> MappedLocalTime> { + match NaiveDate::from_ymd_opt(year, month, day).and_then(|d| d.and_hms_opt(hour, min, sec)) + { + Some(dt) => self.from_local_datetime(&dt), + None => MappedLocalTime::None, + } + } + /// Makes a new `Date` from year, month, day and the current time zone. /// This assumes the proleptic Gregorian calendar, with the year 0 being 1 BCE. /// @@ -202,14 +310,8 @@ pub trait TimeZone: Sized + Clone { /// but it will propagate to the `DateTime` values constructed via this date. /// /// Panics on the out-of-range date, invalid month and/or day. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{Utc, TimeZone}; - /// - /// assert_eq!(Utc.ymd(2015, 5, 15).to_string(), "2015-05-15UTC"); - /// ~~~~ + #[deprecated(since = "0.4.23", note = "use `with_ymd_and_hms()` instead")] + #[allow(deprecated)] fn ymd(&self, year: i32, month: u32, day: u32) -> Date { self.ymd_opt(year, month, day).unwrap() } @@ -221,19 +323,12 @@ pub trait TimeZone: Sized + Clone { /// but it will propagate to the `DateTime` values constructed via this date. /// /// Returns `None` on the out-of-range date, invalid month and/or day. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{Utc, LocalResult, TimeZone}; - /// - /// assert_eq!(Utc.ymd_opt(2015, 5, 15).unwrap().to_string(), "2015-05-15UTC"); - /// assert_eq!(Utc.ymd_opt(2000, 0, 0), LocalResult::None); - /// ~~~~ - fn ymd_opt(&self, year: i32, month: u32, day: u32) -> LocalResult> { + #[deprecated(since = "0.4.23", note = "use `with_ymd_and_hms()` instead")] + #[allow(deprecated)] + fn ymd_opt(&self, year: i32, month: u32, day: u32) -> MappedLocalTime> { match NaiveDate::from_ymd_opt(year, month, day) { Some(d) => self.from_local_date(&d), - None => LocalResult::None, + None => MappedLocalTime::None, } } @@ -244,14 +339,11 @@ pub trait TimeZone: Sized + Clone { /// but it will propagate to the `DateTime` values constructed via this date. /// /// Panics on the out-of-range date and/or invalid DOY. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{Utc, TimeZone}; - /// - /// assert_eq!(Utc.yo(2015, 135).to_string(), "2015-05-15UTC"); - /// ~~~~ + #[deprecated( + since = "0.4.23", + note = "use `from_local_datetime()` with a `NaiveDateTime` instead" + )] + #[allow(deprecated)] fn yo(&self, year: i32, ordinal: u32) -> Date { self.yo_opt(year, ordinal).unwrap() } @@ -263,10 +355,15 @@ pub trait TimeZone: Sized + Clone { /// but it will propagate to the `DateTime` values constructed via this date. /// /// Returns `None` on the out-of-range date and/or invalid DOY. - fn yo_opt(&self, year: i32, ordinal: u32) -> LocalResult> { + #[deprecated( + since = "0.4.23", + note = "use `from_local_datetime()` with a `NaiveDateTime` instead" + )] + #[allow(deprecated)] + fn yo_opt(&self, year: i32, ordinal: u32) -> MappedLocalTime> { match NaiveDate::from_yo_opt(year, ordinal) { Some(d) => self.from_local_date(&d), - None => LocalResult::None, + None => MappedLocalTime::None, } } @@ -279,14 +376,11 @@ pub trait TimeZone: Sized + Clone { /// but it will propagate to the `DateTime` values constructed via this date. /// /// Panics on the out-of-range date and/or invalid week number. - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{Utc, Weekday, TimeZone}; - /// - /// assert_eq!(Utc.isoywd(2015, 20, Weekday::Fri).to_string(), "2015-05-15UTC"); - /// ~~~~ + #[deprecated( + since = "0.4.23", + note = "use `from_local_datetime()` with a `NaiveDateTime` instead" + )] + #[allow(deprecated)] fn isoywd(&self, year: i32, week: u32, weekday: Weekday) -> Date { self.isoywd_opt(year, week, weekday).unwrap() } @@ -300,10 +394,15 @@ pub trait TimeZone: Sized + Clone { /// but it will propagate to the `DateTime` values constructed via this date. /// /// Returns `None` on the out-of-range date and/or invalid week number. - fn isoywd_opt(&self, year: i32, week: u32, weekday: Weekday) -> LocalResult> { + #[deprecated( + since = "0.4.23", + note = "use `from_local_datetime()` with a `NaiveDateTime` instead" + )] + #[allow(deprecated)] + fn isoywd_opt(&self, year: i32, week: u32, weekday: Weekday) -> MappedLocalTime> { match NaiveDate::from_isoywd_opt(year, week, weekday) { Some(d) => self.from_local_date(&d), - None => LocalResult::None, + None => MappedLocalTime::None, } } @@ -311,16 +410,15 @@ pub trait TimeZone: Sized + Clone { /// since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp") /// and the number of nanoseconds since the last whole non-leap second. /// + /// The nanosecond part can exceed 1,000,000,000 in order to represent a + /// [leap second](crate::NaiveTime#leap-second-handling), but only when `secs % 60 == 59`. + /// (The true "UNIX timestamp" cannot represent a leap second unambiguously.) + /// + /// # Panics + /// /// Panics on the out-of-range number of seconds and/or invalid nanosecond, /// for a non-panicking version see [`timestamp_opt`](#method.timestamp_opt). - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{Utc, TimeZone}; - /// - /// assert_eq!(Utc.timestamp(1431648000, 0).to_string(), "2015-05-15 00:00:00 UTC"); - /// ~~~~ + #[deprecated(since = "0.4.23", note = "use `timestamp_opt()` instead")] fn timestamp(&self, secs: i64, nsecs: u32) -> DateTime { self.timestamp_opt(secs, nsecs).unwrap() } @@ -329,12 +427,26 @@ pub trait TimeZone: Sized + Clone { /// since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp") /// and the number of nanoseconds since the last whole non-leap second. /// - /// Returns `LocalResult::None` on out-of-range number of seconds and/or - /// invalid nanosecond, otherwise always returns `LocalResult::Single`. - fn timestamp_opt(&self, secs: i64, nsecs: u32) -> LocalResult> { - match NaiveDateTime::from_timestamp_opt(secs, nsecs) { - Some(dt) => LocalResult::Single(self.from_utc_datetime(&dt)), - None => LocalResult::None, + /// The nanosecond part can exceed 1,000,000,000 in order to represent a + /// [leap second](crate::NaiveTime#leap-second-handling), but only when `secs % 60 == 59`. + /// (The true "UNIX timestamp" cannot represent a leap second unambiguously.) + /// + /// # Errors + /// + /// Returns `MappedLocalTime::None` on out-of-range number of seconds and/or + /// invalid nanosecond, otherwise always returns `MappedLocalTime::Single`. + /// + /// # Example + /// + /// ``` + /// use chrono::{TimeZone, Utc}; + /// + /// assert_eq!(Utc.timestamp_opt(1431648000, 0).unwrap().to_string(), "2015-05-15 00:00:00 UTC"); + /// ``` + fn timestamp_opt(&self, secs: i64, nsecs: u32) -> MappedLocalTime> { + match DateTime::from_timestamp(secs, nsecs) { + Some(dt) => MappedLocalTime::Single(self.from_utc_datetime(&dt.naive_utc())), + None => MappedLocalTime::None, } } @@ -343,14 +455,7 @@ pub trait TimeZone: Sized + Clone { /// /// Panics on out-of-range number of milliseconds for a non-panicking /// version see [`timestamp_millis_opt`](#method.timestamp_millis_opt). - /// - /// # Example - /// - /// ~~~~ - /// use chrono::{Utc, TimeZone}; - /// - /// assert_eq!(Utc.timestamp_millis(1431648000).timestamp(), 1431648); - /// ~~~~ + #[deprecated(since = "0.4.23", note = "use `timestamp_millis_opt()` instead")] fn timestamp_millis(&self, millis: i64) -> DateTime { self.timestamp_millis_opt(millis).unwrap() } @@ -359,60 +464,78 @@ pub trait TimeZone: Sized + Clone { /// since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp"). /// /// - /// Returns `LocalResult::None` on out-of-range number of milliseconds + /// Returns `MappedLocalTime::None` on out-of-range number of milliseconds /// and/or invalid nanosecond, otherwise always returns - /// `LocalResult::Single`. + /// `MappedLocalTime::Single`. /// /// # Example /// - /// ~~~~ - /// use chrono::{Utc, TimeZone, LocalResult}; + /// ``` + /// use chrono::{MappedLocalTime, TimeZone, Utc}; /// match Utc.timestamp_millis_opt(1431648000) { - /// LocalResult::Single(dt) => assert_eq!(dt.timestamp(), 1431648), + /// MappedLocalTime::Single(dt) => assert_eq!(dt.timestamp(), 1431648), /// _ => panic!("Incorrect timestamp_millis"), /// }; - /// ~~~~ - fn timestamp_millis_opt(&self, millis: i64) -> LocalResult> { - let (mut secs, mut millis) = (millis / 1000, millis % 1000); - if millis < 0 { - secs -= 1; - millis += 1000; + /// ``` + fn timestamp_millis_opt(&self, millis: i64) -> MappedLocalTime> { + match DateTime::from_timestamp_millis(millis) { + Some(dt) => MappedLocalTime::Single(self.from_utc_datetime(&dt.naive_utc())), + None => MappedLocalTime::None, } - self.timestamp_opt(secs, millis as u32 * 1_000_000) } /// Makes a new `DateTime` from the number of non-leap nanoseconds /// since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp"). /// - /// Unlike [`timestamp_millis`](#method.timestamp_millis), this never - /// panics. + /// Unlike [`timestamp_millis_opt`](#method.timestamp_millis_opt), this never fails. /// /// # Example /// - /// ~~~~ - /// use chrono::{Utc, TimeZone}; + /// ``` + /// use chrono::{TimeZone, Utc}; /// /// assert_eq!(Utc.timestamp_nanos(1431648000000000).timestamp(), 1431648); - /// ~~~~ + /// ``` fn timestamp_nanos(&self, nanos: i64) -> DateTime { - let (mut secs, mut nanos) = (nanos / 1_000_000_000, nanos % 1_000_000_000); - if nanos < 0 { - secs -= 1; - nanos += 1_000_000_000; - } - self.timestamp_opt(secs, nanos as u32).unwrap() + self.from_utc_datetime(&DateTime::from_timestamp_nanos(nanos).naive_utc()) } - /// Parses a string with the specified format string and - /// returns a `DateTime` with the current offset. - /// See the [`format::strftime` module](../format/strftime/index.html) - /// on the supported escape sequences. + /// Makes a new `DateTime` from the number of non-leap microseconds + /// since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp"). /// - /// If the format does not include offsets, the current offset is assumed; - /// otherwise the input should have a matching UTC offset. + /// # Example /// - /// See also `DateTime::parse_from_str` which gives a local `DateTime` - /// with parsed `FixedOffset`. + /// ``` + /// use chrono::{TimeZone, Utc}; + /// + /// assert_eq!(Utc.timestamp_micros(1431648000000).unwrap().timestamp(), 1431648); + /// ``` + fn timestamp_micros(&self, micros: i64) -> MappedLocalTime> { + match DateTime::from_timestamp_micros(micros) { + Some(dt) => MappedLocalTime::Single(self.from_utc_datetime(&dt.naive_utc())), + None => MappedLocalTime::None, + } + } + + /// Parses a string with the specified format string and returns a + /// `DateTime` with the current offset. + /// + /// See the [`crate::format::strftime`] module on the + /// supported escape sequences. + /// + /// If the to-be-parsed string includes an offset, it *must* match the + /// offset of the TimeZone, otherwise an error will be returned. + /// + /// See also [`DateTime::parse_from_str`] which gives a [`DateTime`] with + /// parsed [`FixedOffset`]. + /// + /// See also [`NaiveDateTime::parse_from_str`] which gives a [`NaiveDateTime`] without + /// an offset, but can be converted to a [`DateTime`] with [`NaiveDateTime::and_utc`] or + /// [`NaiveDateTime::and_local_timezone`]. + #[deprecated( + since = "0.4.29", + note = "use `DateTime::parse_from_str` or `NaiveDateTime::parse_from_str` with `and_utc()` or `and_local_timezone()` instead" + )] fn datetime_from_str(&self, s: &str, fmt: &str) -> ParseResult> { let mut parsed = Parsed::new(); parse(&mut parsed, s, StrftimeItems::new(fmt))?; @@ -423,13 +546,16 @@ pub trait TimeZone: Sized + Clone { fn from_offset(offset: &Self::Offset) -> Self; /// Creates the offset(s) for given local `NaiveDate` if possible. - fn offset_from_local_date(&self, local: &NaiveDate) -> LocalResult; + fn offset_from_local_date(&self, local: &NaiveDate) -> MappedLocalTime; /// Creates the offset(s) for given local `NaiveDateTime` if possible. - fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult; + fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> MappedLocalTime; /// Converts the local `NaiveDate` to the timezone-aware `Date` if possible. - fn from_local_date(&self, local: &NaiveDate) -> LocalResult> { + #[allow(clippy::wrong_self_convention)] + #[deprecated(since = "0.4.23", note = "use `from_local_datetime()` instead")] + #[allow(deprecated)] + fn from_local_date(&self, local: &NaiveDate) -> MappedLocalTime> { self.offset_from_local_date(local).map(|offset| { // since FixedOffset is within +/- 1 day, the date is never affected Date::from_utc(*local, offset) @@ -437,9 +563,13 @@ pub trait TimeZone: Sized + Clone { } /// Converts the local `NaiveDateTime` to the timezone-aware `DateTime` if possible. - fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult> { - self.offset_from_local_datetime(local) - .map(|offset| DateTime::from_utc(*local - offset.fix(), offset)) + #[allow(clippy::wrong_self_convention)] + fn from_local_datetime(&self, local: &NaiveDateTime) -> MappedLocalTime> { + self.offset_from_local_datetime(local).and_then(|off| { + local + .checked_sub_offset(off.fix()) + .map(|dt| DateTime::from_naive_utc_and_offset(dt, off)) + }) } /// Creates the offset for given UTC `NaiveDate`. This cannot fail. @@ -450,48 +580,68 @@ pub trait TimeZone: Sized + Clone { /// Converts the UTC `NaiveDate` to the local time. /// The UTC is continuous and thus this cannot fail (but can give the duplicate local time). + #[allow(clippy::wrong_self_convention)] + #[deprecated(since = "0.4.23", note = "use `from_utc_datetime()` instead")] + #[allow(deprecated)] fn from_utc_date(&self, utc: &NaiveDate) -> Date { Date::from_utc(*utc, self.offset_from_utc_date(utc)) } /// Converts the UTC `NaiveDateTime` to the local time. /// The UTC is continuous and thus this cannot fail (but can give the duplicate local time). + #[allow(clippy::wrong_self_convention)] fn from_utc_datetime(&self, utc: &NaiveDateTime) -> DateTime { - DateTime::from_utc(*utc, self.offset_from_utc_datetime(utc)) + DateTime::from_naive_utc_and_offset(*utc, self.offset_from_utc_datetime(utc)) } } -mod fixed; -#[cfg(feature = "clock")] -mod local; -mod utc; - -pub use self::fixed::FixedOffset; -#[cfg(feature = "clock")] -pub use self::local::Local; -pub use self::utc::Utc; - #[cfg(test)] mod tests { use super::*; + #[test] + fn test_fixed_offset_min_max_dates() { + for offset_hour in -23..=23 { + dbg!(offset_hour); + let offset = FixedOffset::east_opt(offset_hour * 60 * 60).unwrap(); + + let local_max = offset.from_utc_datetime(&NaiveDateTime::MAX); + assert_eq!(local_max.naive_utc(), NaiveDateTime::MAX); + let local_min = offset.from_utc_datetime(&NaiveDateTime::MIN); + assert_eq!(local_min.naive_utc(), NaiveDateTime::MIN); + + let local_max = offset.from_local_datetime(&NaiveDateTime::MAX); + if offset_hour >= 0 { + assert_eq!(local_max.unwrap().naive_local(), NaiveDateTime::MAX); + } else { + assert_eq!(local_max, MappedLocalTime::None); + } + let local_min = offset.from_local_datetime(&NaiveDateTime::MIN); + if offset_hour <= 0 { + assert_eq!(local_min.unwrap().naive_local(), NaiveDateTime::MIN); + } else { + assert_eq!(local_min, MappedLocalTime::None); + } + } + } + #[test] fn test_negative_millis() { - let dt = Utc.timestamp_millis(-1000); + let dt = Utc.timestamp_millis_opt(-1000).unwrap(); assert_eq!(dt.to_string(), "1969-12-31 23:59:59 UTC"); - let dt = Utc.timestamp_millis(-7000); + let dt = Utc.timestamp_millis_opt(-7000).unwrap(); assert_eq!(dt.to_string(), "1969-12-31 23:59:53 UTC"); - let dt = Utc.timestamp_millis(-7001); + let dt = Utc.timestamp_millis_opt(-7001).unwrap(); assert_eq!(dt.to_string(), "1969-12-31 23:59:52.999 UTC"); - let dt = Utc.timestamp_millis(-7003); + let dt = Utc.timestamp_millis_opt(-7003).unwrap(); assert_eq!(dt.to_string(), "1969-12-31 23:59:52.997 UTC"); - let dt = Utc.timestamp_millis(-999); + let dt = Utc.timestamp_millis_opt(-999).unwrap(); assert_eq!(dt.to_string(), "1969-12-31 23:59:59.001 UTC"); - let dt = Utc.timestamp_millis(-1); + let dt = Utc.timestamp_millis_opt(-1).unwrap(); assert_eq!(dt.to_string(), "1969-12-31 23:59:59.999 UTC"); - let dt = Utc.timestamp_millis(-60000); + let dt = Utc.timestamp_millis_opt(-60000).unwrap(); assert_eq!(dt.to_string(), "1969-12-31 23:59:00 UTC"); - let dt = Utc.timestamp_millis(-3600000); + let dt = Utc.timestamp_millis_opt(-3600000).unwrap(); assert_eq!(dt.to_string(), "1969-12-31 23:00:00 UTC"); for (millis, expected) in &[ @@ -500,7 +650,7 @@ mod tests { (-7003, "1969-12-31 23:59:52.997 UTC"), ] { match Utc.timestamp_millis_opt(*millis) { - LocalResult::Single(dt) => { + MappedLocalTime::Single(dt) => { assert_eq!(dt.to_string(), *expected); } e => panic!("Got {:?} instead of an okay answer", e), @@ -524,8 +674,22 @@ mod tests { #[test] fn test_nanos_never_panics() { - Utc.timestamp_nanos(i64::max_value()); + Utc.timestamp_nanos(i64::MAX); Utc.timestamp_nanos(i64::default()); - Utc.timestamp_nanos(i64::min_value()); + Utc.timestamp_nanos(i64::MIN); + } + + #[test] + fn test_negative_micros() { + let dt = Utc.timestamp_micros(-1_000_000).unwrap(); + assert_eq!(dt.to_string(), "1969-12-31 23:59:59 UTC"); + let dt = Utc.timestamp_micros(-999_999).unwrap(); + assert_eq!(dt.to_string(), "1969-12-31 23:59:59.000001 UTC"); + let dt = Utc.timestamp_micros(-1).unwrap(); + assert_eq!(dt.to_string(), "1969-12-31 23:59:59.999999 UTC"); + let dt = Utc.timestamp_micros(-60_000_000).unwrap(); + assert_eq!(dt.to_string(), "1969-12-31 23:59:00 UTC"); + let dt = Utc.timestamp_micros(-3_600_000_000).unwrap(); + assert_eq!(dt.to_string(), "1969-12-31 23:00:00 UTC"); } } diff --git a/third_party/rust/chrono/src/offset/utc.rs b/third_party/rust/chrono/src/offset/utc.rs index aec6667b0de..5ae26ed86da 100644 --- a/third_party/rust/chrono/src/offset/utc.rs +++ b/third_party/rust/chrono/src/offset/utc.rs @@ -4,16 +4,24 @@ //! The UTC (Coordinated Universal Time) time zone. use core::fmt; - -use super::{FixedOffset, LocalResult, Offset, TimeZone}; -use naive::{NaiveDate, NaiveDateTime}; #[cfg(all( - feature = "clock", - not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")) + feature = "now", + not(all( + target_arch = "wasm32", + feature = "wasmbind", + not(any(target_os = "emscripten", target_os = "wasi")) + )) ))] use std::time::{SystemTime, UNIX_EPOCH}; -#[cfg(feature = "clock")] -use {Date, DateTime}; + +#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] +use rkyv::{Archive, Deserialize, Serialize}; + +use super::{FixedOffset, MappedLocalTime, Offset, TimeZone}; +use crate::naive::{NaiveDate, NaiveDateTime}; +#[cfg(feature = "now")] +#[allow(deprecated)] +use crate::{Date, DateTime}; /// The UTC time zone. This is the most efficient time zone when you don't need the local time. /// It is also used as an offset (which is also a dummy type). @@ -24,35 +32,79 @@ use {Date, DateTime}; /// /// # Example /// -/// ~~~~ -/// use chrono::{DateTime, TimeZone, NaiveDateTime, Utc}; +/// ``` +/// use chrono::{DateTime, TimeZone, Utc}; /// -/// let dt = DateTime::::from_utc(NaiveDateTime::from_timestamp(61, 0), Utc); +/// let dt = DateTime::from_timestamp(61, 0).unwrap(); /// -/// assert_eq!(Utc.timestamp(61, 0), dt); -/// assert_eq!(Utc.ymd(1970, 1, 1).and_hms(0, 1, 1), dt); -/// ~~~~ -#[derive(Copy, Clone, PartialEq, Eq)] +/// assert_eq!(Utc.timestamp_opt(61, 0).unwrap(), dt); +/// assert_eq!(Utc.with_ymd_and_hms(1970, 1, 1, 0, 1, 1).unwrap(), dt); +/// ``` +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +#[cfg_attr( + any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), + derive(Archive, Deserialize, Serialize), + archive(compare(PartialEq)), + archive_attr(derive(Clone, Copy, PartialEq, Eq, Debug, Hash)) +)] +#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] +#[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(arbitrary::Arbitrary))] pub struct Utc; -#[cfg(feature = "clock")] +#[cfg(feature = "now")] impl Utc { /// Returns a `Date` which corresponds to the current date. + #[deprecated( + since = "0.4.23", + note = "use `Utc::now()` instead, potentially with `.date_naive()`" + )] + #[allow(deprecated)] + #[must_use] pub fn today() -> Date { Utc::now().date() } - /// Returns a `DateTime` which corresponds to the current date. - #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind")))] + /// Returns a `DateTime` which corresponds to the current date and time in UTC. + /// + /// See also the similar [`Local::now()`] which returns `DateTime`, i.e. the local date + /// and time including offset from UTC. + /// + /// [`Local::now()`]: crate::Local::now + /// + /// # Example + /// + /// ``` + /// # #![allow(unused_variables)] + /// # use chrono::{FixedOffset, Utc}; + /// // Current time in UTC + /// let now_utc = Utc::now(); + /// + /// // Current date in UTC + /// let today_utc = now_utc.date_naive(); + /// + /// // Current time in some timezone (let's use +05:00) + /// let offset = FixedOffset::east_opt(5 * 60 * 60).unwrap(); + /// let now_with_offset = Utc::now().with_timezone(&offset); + /// ``` + #[cfg(not(all( + target_arch = "wasm32", + feature = "wasmbind", + not(any(target_os = "emscripten", target_os = "wasi")) + )))] + #[must_use] pub fn now() -> DateTime { let now = SystemTime::now().duration_since(UNIX_EPOCH).expect("system time before Unix epoch"); - let naive = NaiveDateTime::from_timestamp(now.as_secs() as i64, now.subsec_nanos() as u32); - DateTime::from_utc(naive, Utc) + DateTime::from_timestamp(now.as_secs() as i64, now.subsec_nanos()).unwrap() } - /// Returns a `DateTime` which corresponds to the current date. - #[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), feature = "wasmbind"))] + /// Returns a `DateTime` which corresponds to the current date and time. + #[cfg(all( + target_arch = "wasm32", + feature = "wasmbind", + not(any(target_os = "emscripten", target_os = "wasi")) + ))] + #[must_use] pub fn now() -> DateTime { let now = js_sys::Date::new_0(); DateTime::::from(now) @@ -66,11 +118,11 @@ impl TimeZone for Utc { Utc } - fn offset_from_local_date(&self, _local: &NaiveDate) -> LocalResult { - LocalResult::Single(Utc) + fn offset_from_local_date(&self, _local: &NaiveDate) -> MappedLocalTime { + MappedLocalTime::Single(Utc) } - fn offset_from_local_datetime(&self, _local: &NaiveDateTime) -> LocalResult { - LocalResult::Single(Utc) + fn offset_from_local_datetime(&self, _local: &NaiveDateTime) -> MappedLocalTime { + MappedLocalTime::Single(Utc) } fn offset_from_utc_date(&self, _utc: &NaiveDate) -> Utc { @@ -83,7 +135,7 @@ impl TimeZone for Utc { impl Offset for Utc { fn fix(&self) -> FixedOffset { - FixedOffset::east(0) + FixedOffset::east_opt(0).unwrap() } } diff --git a/third_party/rust/chrono/src/oldtime.rs b/third_party/rust/chrono/src/oldtime.rs deleted file mode 100644 index 8656769c50e..00000000000 --- a/third_party/rust/chrono/src/oldtime.rs +++ /dev/null @@ -1,684 +0,0 @@ -// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// 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. - -//! Temporal quantification - -use core::ops::{Add, Div, Mul, Neg, Sub}; -use core::time::Duration as StdDuration; -use core::{fmt, i64}; -#[cfg(any(feature = "std", test))] -use std::error::Error; - -/// The number of nanoseconds in a microsecond. -const NANOS_PER_MICRO: i32 = 1000; -/// The number of nanoseconds in a millisecond. -const NANOS_PER_MILLI: i32 = 1000_000; -/// The number of nanoseconds in seconds. -const NANOS_PER_SEC: i32 = 1_000_000_000; -/// The number of microseconds per second. -const MICROS_PER_SEC: i64 = 1000_000; -/// The number of milliseconds per second. -const MILLIS_PER_SEC: i64 = 1000; -/// The number of seconds in a minute. -const SECS_PER_MINUTE: i64 = 60; -/// The number of seconds in an hour. -const SECS_PER_HOUR: i64 = 3600; -/// The number of (non-leap) seconds in days. -const SECS_PER_DAY: i64 = 86400; -/// The number of (non-leap) seconds in a week. -const SECS_PER_WEEK: i64 = 604800; - -macro_rules! try_opt { - ($e:expr) => { - match $e { - Some(v) => v, - None => return None, - } - }; -} - -/// ISO 8601 time duration with nanosecond precision. -/// This also allows for the negative duration; see individual methods for details. -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] -pub struct Duration { - secs: i64, - nanos: i32, // Always 0 <= nanos < NANOS_PER_SEC -} - -/// The minimum possible `Duration`: `i64::MIN` milliseconds. -pub const MIN: Duration = Duration { - secs: i64::MIN / MILLIS_PER_SEC - 1, - nanos: NANOS_PER_SEC + (i64::MIN % MILLIS_PER_SEC) as i32 * NANOS_PER_MILLI, -}; - -/// The maximum possible `Duration`: `i64::MAX` milliseconds. -pub const MAX: Duration = Duration { - secs: i64::MAX / MILLIS_PER_SEC, - nanos: (i64::MAX % MILLIS_PER_SEC) as i32 * NANOS_PER_MILLI, -}; - -impl Duration { - /// Makes a new `Duration` with given number of weeks. - /// Equivalent to `Duration::seconds(weeks * 7 * 24 * 60 * 60)` with overflow checks. - /// Panics when the duration is out of bounds. - #[inline] - pub fn weeks(weeks: i64) -> Duration { - let secs = weeks.checked_mul(SECS_PER_WEEK).expect("Duration::weeks out of bounds"); - Duration::seconds(secs) - } - - /// Makes a new `Duration` with given number of days. - /// Equivalent to `Duration::seconds(days * 24 * 60 * 60)` with overflow checks. - /// Panics when the duration is out of bounds. - #[inline] - pub fn days(days: i64) -> Duration { - let secs = days.checked_mul(SECS_PER_DAY).expect("Duration::days out of bounds"); - Duration::seconds(secs) - } - - /// Makes a new `Duration` with given number of hours. - /// Equivalent to `Duration::seconds(hours * 60 * 60)` with overflow checks. - /// Panics when the duration is out of bounds. - #[inline] - pub fn hours(hours: i64) -> Duration { - let secs = hours.checked_mul(SECS_PER_HOUR).expect("Duration::hours ouf of bounds"); - Duration::seconds(secs) - } - - /// Makes a new `Duration` with given number of minutes. - /// Equivalent to `Duration::seconds(minutes * 60)` with overflow checks. - /// Panics when the duration is out of bounds. - #[inline] - pub fn minutes(minutes: i64) -> Duration { - let secs = minutes.checked_mul(SECS_PER_MINUTE).expect("Duration::minutes out of bounds"); - Duration::seconds(secs) - } - - /// Makes a new `Duration` with given number of seconds. - /// Panics when the duration is more than `i64::MAX` seconds - /// or less than `i64::MIN` seconds. - #[inline] - pub fn seconds(seconds: i64) -> Duration { - let d = Duration { secs: seconds, nanos: 0 }; - if d < MIN || d > MAX { - panic!("Duration::seconds out of bounds"); - } - d - } - - /// Makes a new `Duration` with given number of milliseconds. - #[inline] - pub fn milliseconds(milliseconds: i64) -> Duration { - let (secs, millis) = div_mod_floor_64(milliseconds, MILLIS_PER_SEC); - let nanos = millis as i32 * NANOS_PER_MILLI; - Duration { secs: secs, nanos: nanos } - } - - /// Makes a new `Duration` with given number of microseconds. - #[inline] - pub fn microseconds(microseconds: i64) -> Duration { - let (secs, micros) = div_mod_floor_64(microseconds, MICROS_PER_SEC); - let nanos = micros as i32 * NANOS_PER_MICRO; - Duration { secs: secs, nanos: nanos } - } - - /// Makes a new `Duration` with given number of nanoseconds. - #[inline] - pub fn nanoseconds(nanos: i64) -> Duration { - let (secs, nanos) = div_mod_floor_64(nanos, NANOS_PER_SEC as i64); - Duration { secs: secs, nanos: nanos as i32 } - } - - /// Returns the total number of whole weeks in the duration. - #[inline] - pub fn num_weeks(&self) -> i64 { - self.num_days() / 7 - } - - /// Returns the total number of whole days in the duration. - pub fn num_days(&self) -> i64 { - self.num_seconds() / SECS_PER_DAY - } - - /// Returns the total number of whole hours in the duration. - #[inline] - pub fn num_hours(&self) -> i64 { - self.num_seconds() / SECS_PER_HOUR - } - - /// Returns the total number of whole minutes in the duration. - #[inline] - pub fn num_minutes(&self) -> i64 { - self.num_seconds() / SECS_PER_MINUTE - } - - /// Returns the total number of whole seconds in the duration. - pub fn num_seconds(&self) -> i64 { - // If secs is negative, nanos should be subtracted from the duration. - if self.secs < 0 && self.nanos > 0 { - self.secs + 1 - } else { - self.secs - } - } - - /// Returns the number of nanoseconds such that - /// `nanos_mod_sec() + num_seconds() * NANOS_PER_SEC` is the total number of - /// nanoseconds in the duration. - fn nanos_mod_sec(&self) -> i32 { - if self.secs < 0 && self.nanos > 0 { - self.nanos - NANOS_PER_SEC - } else { - self.nanos - } - } - - /// Returns the total number of whole milliseconds in the duration, - pub fn num_milliseconds(&self) -> i64 { - // A proper Duration will not overflow, because MIN and MAX are defined - // such that the range is exactly i64 milliseconds. - let secs_part = self.num_seconds() * MILLIS_PER_SEC; - let nanos_part = self.nanos_mod_sec() / NANOS_PER_MILLI; - secs_part + nanos_part as i64 - } - - /// Returns the total number of whole microseconds in the duration, - /// or `None` on overflow (exceeding 2^63 microseconds in either direction). - pub fn num_microseconds(&self) -> Option { - let secs_part = try_opt!(self.num_seconds().checked_mul(MICROS_PER_SEC)); - let nanos_part = self.nanos_mod_sec() / NANOS_PER_MICRO; - secs_part.checked_add(nanos_part as i64) - } - - /// Returns the total number of whole nanoseconds in the duration, - /// or `None` on overflow (exceeding 2^63 nanoseconds in either direction). - pub fn num_nanoseconds(&self) -> Option { - let secs_part = try_opt!(self.num_seconds().checked_mul(NANOS_PER_SEC as i64)); - let nanos_part = self.nanos_mod_sec(); - secs_part.checked_add(nanos_part as i64) - } - - /// Add two durations, returning `None` if overflow occurred. - pub fn checked_add(&self, rhs: &Duration) -> Option { - let mut secs = try_opt!(self.secs.checked_add(rhs.secs)); - let mut nanos = self.nanos + rhs.nanos; - if nanos >= NANOS_PER_SEC { - nanos -= NANOS_PER_SEC; - secs = try_opt!(secs.checked_add(1)); - } - let d = Duration { secs: secs, nanos: nanos }; - // Even if d is within the bounds of i64 seconds, - // it might still overflow i64 milliseconds. - if d < MIN || d > MAX { - None - } else { - Some(d) - } - } - - /// Subtract two durations, returning `None` if overflow occurred. - pub fn checked_sub(&self, rhs: &Duration) -> Option { - let mut secs = try_opt!(self.secs.checked_sub(rhs.secs)); - let mut nanos = self.nanos - rhs.nanos; - if nanos < 0 { - nanos += NANOS_PER_SEC; - secs = try_opt!(secs.checked_sub(1)); - } - let d = Duration { secs: secs, nanos: nanos }; - // Even if d is within the bounds of i64 seconds, - // it might still overflow i64 milliseconds. - if d < MIN || d > MAX { - None - } else { - Some(d) - } - } - - /// Returns the duration as an absolute (non-negative) value. - #[inline] - pub fn abs(&self) -> Duration { - Duration { secs: self.secs.abs(), nanos: self.nanos } - } - - /// The minimum possible `Duration`: `i64::MIN` milliseconds. - #[inline] - pub fn min_value() -> Duration { - MIN - } - - /// The maximum possible `Duration`: `i64::MAX` milliseconds. - #[inline] - pub fn max_value() -> Duration { - MAX - } - - /// A duration where the stored seconds and nanoseconds are equal to zero. - #[inline] - pub fn zero() -> Duration { - Duration { secs: 0, nanos: 0 } - } - - /// Returns `true` if the duration equals `Duration::zero()`. - #[inline] - pub fn is_zero(&self) -> bool { - self.secs == 0 && self.nanos == 0 - } - - /// Creates a `time::Duration` object from `std::time::Duration` - /// - /// This function errors when original duration is larger than the maximum - /// value supported for this type. - pub fn from_std(duration: StdDuration) -> Result { - // We need to check secs as u64 before coercing to i64 - if duration.as_secs() > MAX.secs as u64 { - return Err(OutOfRangeError(())); - } - let d = Duration { secs: duration.as_secs() as i64, nanos: duration.subsec_nanos() as i32 }; - if d > MAX { - return Err(OutOfRangeError(())); - } - Ok(d) - } - - /// Creates a `std::time::Duration` object from `time::Duration` - /// - /// This function errors when duration is less than zero. As standard - /// library implementation is limited to non-negative values. - pub fn to_std(&self) -> Result { - if self.secs < 0 { - return Err(OutOfRangeError(())); - } - Ok(StdDuration::new(self.secs as u64, self.nanos as u32)) - } -} - -impl Neg for Duration { - type Output = Duration; - - #[inline] - fn neg(self) -> Duration { - if self.nanos == 0 { - Duration { secs: -self.secs, nanos: 0 } - } else { - Duration { secs: -self.secs - 1, nanos: NANOS_PER_SEC - self.nanos } - } - } -} - -impl Add for Duration { - type Output = Duration; - - fn add(self, rhs: Duration) -> Duration { - let mut secs = self.secs + rhs.secs; - let mut nanos = self.nanos + rhs.nanos; - if nanos >= NANOS_PER_SEC { - nanos -= NANOS_PER_SEC; - secs += 1; - } - Duration { secs: secs, nanos: nanos } - } -} - -impl Sub for Duration { - type Output = Duration; - - fn sub(self, rhs: Duration) -> Duration { - let mut secs = self.secs - rhs.secs; - let mut nanos = self.nanos - rhs.nanos; - if nanos < 0 { - nanos += NANOS_PER_SEC; - secs -= 1; - } - Duration { secs: secs, nanos: nanos } - } -} - -impl Mul for Duration { - type Output = Duration; - - fn mul(self, rhs: i32) -> Duration { - // Multiply nanoseconds as i64, because it cannot overflow that way. - let total_nanos = self.nanos as i64 * rhs as i64; - let (extra_secs, nanos) = div_mod_floor_64(total_nanos, NANOS_PER_SEC as i64); - let secs = self.secs * rhs as i64 + extra_secs; - Duration { secs: secs, nanos: nanos as i32 } - } -} - -impl Div for Duration { - type Output = Duration; - - fn div(self, rhs: i32) -> Duration { - let mut secs = self.secs / rhs as i64; - let carry = self.secs - secs * rhs as i64; - let extra_nanos = carry * NANOS_PER_SEC as i64 / rhs as i64; - let mut nanos = self.nanos / rhs + extra_nanos as i32; - if nanos >= NANOS_PER_SEC { - nanos -= NANOS_PER_SEC; - secs += 1; - } - if nanos < 0 { - nanos += NANOS_PER_SEC; - secs -= 1; - } - Duration { secs: secs, nanos: nanos } - } -} - -impl fmt::Display for Duration { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - // technically speaking, negative duration is not valid ISO 8601, - // but we need to print it anyway. - let (abs, sign) = if self.secs < 0 { (-*self, "-") } else { (*self, "") }; - - let days = abs.secs / SECS_PER_DAY; - let secs = abs.secs - days * SECS_PER_DAY; - let hasdate = days != 0; - let hastime = (secs != 0 || abs.nanos != 0) || !hasdate; - - write!(f, "{}P", sign)?; - - if hasdate { - write!(f, "{}D", days)?; - } - if hastime { - if abs.nanos == 0 { - write!(f, "T{}S", secs)?; - } else if abs.nanos % NANOS_PER_MILLI == 0 { - write!(f, "T{}.{:03}S", secs, abs.nanos / NANOS_PER_MILLI)?; - } else if abs.nanos % NANOS_PER_MICRO == 0 { - write!(f, "T{}.{:06}S", secs, abs.nanos / NANOS_PER_MICRO)?; - } else { - write!(f, "T{}.{:09}S", secs, abs.nanos)?; - } - } - Ok(()) - } -} - -/// Represents error when converting `Duration` to/from a standard library -/// implementation -/// -/// The `std::time::Duration` supports a range from zero to `u64::MAX` -/// *seconds*, while this module supports signed range of up to -/// `i64::MAX` of *milliseconds*. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct OutOfRangeError(()); - -impl fmt::Display for OutOfRangeError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Source duration value is out of range for the target type") - } -} - -#[cfg(any(feature = "std", test))] -impl Error for OutOfRangeError { - #[allow(deprecated)] - fn description(&self) -> &str { - "out of range error" - } -} - -// Copied from libnum -#[inline] -fn div_mod_floor_64(this: i64, other: i64) -> (i64, i64) { - (div_floor_64(this, other), mod_floor_64(this, other)) -} - -#[inline] -fn div_floor_64(this: i64, other: i64) -> i64 { - match div_rem_64(this, other) { - (d, r) if (r > 0 && other < 0) || (r < 0 && other > 0) => d - 1, - (d, _) => d, - } -} - -#[inline] -fn mod_floor_64(this: i64, other: i64) -> i64 { - match this % other { - r if (r > 0 && other < 0) || (r < 0 && other > 0) => r + other, - r => r, - } -} - -#[inline] -fn div_rem_64(this: i64, other: i64) -> (i64, i64) { - (this / other, this % other) -} - -#[cfg(test)] -mod tests { - use super::{Duration, OutOfRangeError, MAX, MIN}; - use std::time::Duration as StdDuration; - use std::{i32, i64}; - - #[test] - fn test_duration() { - assert!(Duration::seconds(1) != Duration::zero()); - assert_eq!(Duration::seconds(1) + Duration::seconds(2), Duration::seconds(3)); - assert_eq!( - Duration::seconds(86399) + Duration::seconds(4), - Duration::days(1) + Duration::seconds(3) - ); - assert_eq!(Duration::days(10) - Duration::seconds(1000), Duration::seconds(863000)); - assert_eq!(Duration::days(10) - Duration::seconds(1000000), Duration::seconds(-136000)); - assert_eq!( - Duration::days(2) + Duration::seconds(86399) + Duration::nanoseconds(1234567890), - Duration::days(3) + Duration::nanoseconds(234567890) - ); - assert_eq!(-Duration::days(3), Duration::days(-3)); - assert_eq!( - -(Duration::days(3) + Duration::seconds(70)), - Duration::days(-4) + Duration::seconds(86400 - 70) - ); - } - - #[test] - fn test_duration_num_days() { - assert_eq!(Duration::zero().num_days(), 0); - assert_eq!(Duration::days(1).num_days(), 1); - assert_eq!(Duration::days(-1).num_days(), -1); - assert_eq!(Duration::seconds(86399).num_days(), 0); - assert_eq!(Duration::seconds(86401).num_days(), 1); - assert_eq!(Duration::seconds(-86399).num_days(), 0); - assert_eq!(Duration::seconds(-86401).num_days(), -1); - assert_eq!(Duration::days(i32::MAX as i64).num_days(), i32::MAX as i64); - assert_eq!(Duration::days(i32::MIN as i64).num_days(), i32::MIN as i64); - } - - #[test] - fn test_duration_num_seconds() { - assert_eq!(Duration::zero().num_seconds(), 0); - assert_eq!(Duration::seconds(1).num_seconds(), 1); - assert_eq!(Duration::seconds(-1).num_seconds(), -1); - assert_eq!(Duration::milliseconds(999).num_seconds(), 0); - assert_eq!(Duration::milliseconds(1001).num_seconds(), 1); - assert_eq!(Duration::milliseconds(-999).num_seconds(), 0); - assert_eq!(Duration::milliseconds(-1001).num_seconds(), -1); - } - - #[test] - fn test_duration_num_milliseconds() { - assert_eq!(Duration::zero().num_milliseconds(), 0); - assert_eq!(Duration::milliseconds(1).num_milliseconds(), 1); - assert_eq!(Duration::milliseconds(-1).num_milliseconds(), -1); - assert_eq!(Duration::microseconds(999).num_milliseconds(), 0); - assert_eq!(Duration::microseconds(1001).num_milliseconds(), 1); - assert_eq!(Duration::microseconds(-999).num_milliseconds(), 0); - assert_eq!(Duration::microseconds(-1001).num_milliseconds(), -1); - assert_eq!(Duration::milliseconds(i64::MAX).num_milliseconds(), i64::MAX); - assert_eq!(Duration::milliseconds(i64::MIN).num_milliseconds(), i64::MIN); - assert_eq!(MAX.num_milliseconds(), i64::MAX); - assert_eq!(MIN.num_milliseconds(), i64::MIN); - } - - #[test] - fn test_duration_num_microseconds() { - assert_eq!(Duration::zero().num_microseconds(), Some(0)); - assert_eq!(Duration::microseconds(1).num_microseconds(), Some(1)); - assert_eq!(Duration::microseconds(-1).num_microseconds(), Some(-1)); - assert_eq!(Duration::nanoseconds(999).num_microseconds(), Some(0)); - assert_eq!(Duration::nanoseconds(1001).num_microseconds(), Some(1)); - assert_eq!(Duration::nanoseconds(-999).num_microseconds(), Some(0)); - assert_eq!(Duration::nanoseconds(-1001).num_microseconds(), Some(-1)); - assert_eq!(Duration::microseconds(i64::MAX).num_microseconds(), Some(i64::MAX)); - assert_eq!(Duration::microseconds(i64::MIN).num_microseconds(), Some(i64::MIN)); - assert_eq!(MAX.num_microseconds(), None); - assert_eq!(MIN.num_microseconds(), None); - - // overflow checks - const MICROS_PER_DAY: i64 = 86400_000_000; - assert_eq!( - Duration::days(i64::MAX / MICROS_PER_DAY).num_microseconds(), - Some(i64::MAX / MICROS_PER_DAY * MICROS_PER_DAY) - ); - assert_eq!( - Duration::days(i64::MIN / MICROS_PER_DAY).num_microseconds(), - Some(i64::MIN / MICROS_PER_DAY * MICROS_PER_DAY) - ); - assert_eq!(Duration::days(i64::MAX / MICROS_PER_DAY + 1).num_microseconds(), None); - assert_eq!(Duration::days(i64::MIN / MICROS_PER_DAY - 1).num_microseconds(), None); - } - - #[test] - fn test_duration_num_nanoseconds() { - assert_eq!(Duration::zero().num_nanoseconds(), Some(0)); - assert_eq!(Duration::nanoseconds(1).num_nanoseconds(), Some(1)); - assert_eq!(Duration::nanoseconds(-1).num_nanoseconds(), Some(-1)); - assert_eq!(Duration::nanoseconds(i64::MAX).num_nanoseconds(), Some(i64::MAX)); - assert_eq!(Duration::nanoseconds(i64::MIN).num_nanoseconds(), Some(i64::MIN)); - assert_eq!(MAX.num_nanoseconds(), None); - assert_eq!(MIN.num_nanoseconds(), None); - - // overflow checks - const NANOS_PER_DAY: i64 = 86400_000_000_000; - assert_eq!( - Duration::days(i64::MAX / NANOS_PER_DAY).num_nanoseconds(), - Some(i64::MAX / NANOS_PER_DAY * NANOS_PER_DAY) - ); - assert_eq!( - Duration::days(i64::MIN / NANOS_PER_DAY).num_nanoseconds(), - Some(i64::MIN / NANOS_PER_DAY * NANOS_PER_DAY) - ); - assert_eq!(Duration::days(i64::MAX / NANOS_PER_DAY + 1).num_nanoseconds(), None); - assert_eq!(Duration::days(i64::MIN / NANOS_PER_DAY - 1).num_nanoseconds(), None); - } - - #[test] - fn test_duration_checked_ops() { - assert_eq!( - Duration::milliseconds(i64::MAX - 1).checked_add(&Duration::microseconds(999)), - Some(Duration::milliseconds(i64::MAX - 2) + Duration::microseconds(1999)) - ); - assert!(Duration::milliseconds(i64::MAX) - .checked_add(&Duration::microseconds(1000)) - .is_none()); - - assert_eq!( - Duration::milliseconds(i64::MIN).checked_sub(&Duration::milliseconds(0)), - Some(Duration::milliseconds(i64::MIN)) - ); - assert!(Duration::milliseconds(i64::MIN).checked_sub(&Duration::milliseconds(1)).is_none()); - } - - #[test] - fn test_duration_mul() { - assert_eq!(Duration::zero() * i32::MAX, Duration::zero()); - assert_eq!(Duration::zero() * i32::MIN, Duration::zero()); - assert_eq!(Duration::nanoseconds(1) * 0, Duration::zero()); - assert_eq!(Duration::nanoseconds(1) * 1, Duration::nanoseconds(1)); - assert_eq!(Duration::nanoseconds(1) * 1_000_000_000, Duration::seconds(1)); - assert_eq!(Duration::nanoseconds(1) * -1_000_000_000, -Duration::seconds(1)); - assert_eq!(-Duration::nanoseconds(1) * 1_000_000_000, -Duration::seconds(1)); - assert_eq!( - Duration::nanoseconds(30) * 333_333_333, - Duration::seconds(10) - Duration::nanoseconds(10) - ); - assert_eq!( - (Duration::nanoseconds(1) + Duration::seconds(1) + Duration::days(1)) * 3, - Duration::nanoseconds(3) + Duration::seconds(3) + Duration::days(3) - ); - assert_eq!(Duration::milliseconds(1500) * -2, Duration::seconds(-3)); - assert_eq!(Duration::milliseconds(-1500) * 2, Duration::seconds(-3)); - } - - #[test] - fn test_duration_div() { - assert_eq!(Duration::zero() / i32::MAX, Duration::zero()); - assert_eq!(Duration::zero() / i32::MIN, Duration::zero()); - assert_eq!(Duration::nanoseconds(123_456_789) / 1, Duration::nanoseconds(123_456_789)); - assert_eq!(Duration::nanoseconds(123_456_789) / -1, -Duration::nanoseconds(123_456_789)); - assert_eq!(-Duration::nanoseconds(123_456_789) / -1, Duration::nanoseconds(123_456_789)); - assert_eq!(-Duration::nanoseconds(123_456_789) / 1, -Duration::nanoseconds(123_456_789)); - assert_eq!(Duration::seconds(1) / 3, Duration::nanoseconds(333_333_333)); - assert_eq!(Duration::seconds(4) / 3, Duration::nanoseconds(1_333_333_333)); - assert_eq!(Duration::seconds(-1) / 2, Duration::milliseconds(-500)); - assert_eq!(Duration::seconds(1) / -2, Duration::milliseconds(-500)); - assert_eq!(Duration::seconds(-1) / -2, Duration::milliseconds(500)); - assert_eq!(Duration::seconds(-4) / 3, Duration::nanoseconds(-1_333_333_333)); - assert_eq!(Duration::seconds(-4) / -3, Duration::nanoseconds(1_333_333_333)); - } - - #[test] - fn test_duration_fmt() { - assert_eq!(Duration::zero().to_string(), "PT0S"); - assert_eq!(Duration::days(42).to_string(), "P42D"); - assert_eq!(Duration::days(-42).to_string(), "-P42D"); - assert_eq!(Duration::seconds(42).to_string(), "PT42S"); - assert_eq!(Duration::milliseconds(42).to_string(), "PT0.042S"); - assert_eq!(Duration::microseconds(42).to_string(), "PT0.000042S"); - assert_eq!(Duration::nanoseconds(42).to_string(), "PT0.000000042S"); - assert_eq!((Duration::days(7) + Duration::milliseconds(6543)).to_string(), "P7DT6.543S"); - assert_eq!(Duration::seconds(-86401).to_string(), "-P1DT1S"); - assert_eq!(Duration::nanoseconds(-1).to_string(), "-PT0.000000001S"); - - // the format specifier should have no effect on `Duration` - assert_eq!( - format!("{:30}", Duration::days(1) + Duration::milliseconds(2345)), - "P1DT2.345S" - ); - } - - #[test] - fn test_to_std() { - assert_eq!(Duration::seconds(1).to_std(), Ok(StdDuration::new(1, 0))); - assert_eq!(Duration::seconds(86401).to_std(), Ok(StdDuration::new(86401, 0))); - assert_eq!(Duration::milliseconds(123).to_std(), Ok(StdDuration::new(0, 123000000))); - assert_eq!(Duration::milliseconds(123765).to_std(), Ok(StdDuration::new(123, 765000000))); - assert_eq!(Duration::nanoseconds(777).to_std(), Ok(StdDuration::new(0, 777))); - assert_eq!(MAX.to_std(), Ok(StdDuration::new(9223372036854775, 807000000))); - assert_eq!(Duration::seconds(-1).to_std(), Err(OutOfRangeError(()))); - assert_eq!(Duration::milliseconds(-1).to_std(), Err(OutOfRangeError(()))); - } - - #[test] - fn test_from_std() { - assert_eq!(Ok(Duration::seconds(1)), Duration::from_std(StdDuration::new(1, 0))); - assert_eq!(Ok(Duration::seconds(86401)), Duration::from_std(StdDuration::new(86401, 0))); - assert_eq!( - Ok(Duration::milliseconds(123)), - Duration::from_std(StdDuration::new(0, 123000000)) - ); - assert_eq!( - Ok(Duration::milliseconds(123765)), - Duration::from_std(StdDuration::new(123, 765000000)) - ); - assert_eq!(Ok(Duration::nanoseconds(777)), Duration::from_std(StdDuration::new(0, 777))); - assert_eq!(Ok(MAX), Duration::from_std(StdDuration::new(9223372036854775, 807000000))); - assert_eq!( - Duration::from_std(StdDuration::new(9223372036854776, 0)), - Err(OutOfRangeError(())) - ); - assert_eq!( - Duration::from_std(StdDuration::new(9223372036854775, 807000001)), - Err(OutOfRangeError(())) - ); - } -} diff --git a/third_party/rust/chrono/src/round.rs b/third_party/rust/chrono/src/round.rs index 92d7c3a504d..f5ca2d06346 100644 --- a/third_party/rust/chrono/src/round.rs +++ b/third_party/rust/chrono/src/round.rs @@ -1,16 +1,12 @@ // This is a part of Chrono. // See README.md and LICENSE.txt for details. +//! Functionality for rounding or truncating a `DateTime` by a `TimeDelta`. + +use crate::{DateTime, NaiveDateTime, TimeDelta, TimeZone, Timelike}; use core::cmp::Ordering; use core::fmt; -use core::marker::Sized; use core::ops::{Add, Sub}; -use datetime::DateTime; -use oldtime::Duration; -#[cfg(any(feature = "std", test))] -use std; -use TimeZone; -use Timelike; /// Extension trait for subsecond rounding or truncation to a maximum number /// of digits. Rounding can be used to decrease the error variance when @@ -25,8 +21,12 @@ pub trait SubsecRound { /// /// # Example /// ``` rust - /// # use chrono::{DateTime, SubsecRound, Timelike, TimeZone, Utc}; - /// let dt = Utc.ymd(2018, 1, 11).and_hms_milli(12, 0, 0, 154); + /// # use chrono::{SubsecRound, Timelike, NaiveDate}; + /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11) + /// .unwrap() + /// .and_hms_milli_opt(12, 0, 0, 154) + /// .unwrap() + /// .and_utc(); /// assert_eq!(dt.round_subsecs(2).nanosecond(), 150_000_000); /// assert_eq!(dt.round_subsecs(1).nanosecond(), 200_000_000); /// ``` @@ -37,8 +37,12 @@ pub trait SubsecRound { /// /// # Example /// ``` rust - /// # use chrono::{DateTime, SubsecRound, Timelike, TimeZone, Utc}; - /// let dt = Utc.ymd(2018, 1, 11).and_hms_milli(12, 0, 0, 154); + /// # use chrono::{SubsecRound, Timelike, NaiveDate}; + /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11) + /// .unwrap() + /// .and_hms_milli_opt(12, 0, 0, 154) + /// .unwrap() + /// .and_utc(); /// assert_eq!(dt.trunc_subsecs(2).nanosecond(), 150_000_000); /// assert_eq!(dt.trunc_subsecs(1).nanosecond(), 100_000_000); /// ``` @@ -47,7 +51,7 @@ pub trait SubsecRound { impl SubsecRound for T where - T: Timelike + Add + Sub, + T: Timelike + Add + Sub, { fn round_subsecs(self, digits: u16) -> T { let span = span_for_digits(digits); @@ -55,9 +59,9 @@ where if delta_down > 0 { let delta_up = span - delta_down; if delta_up <= delta_down { - self + Duration::nanoseconds(delta_up.into()) + self + TimeDelta::nanoseconds(delta_up.into()) } else { - self - Duration::nanoseconds(delta_down.into()) + self - TimeDelta::nanoseconds(delta_down.into()) } } else { self // unchanged @@ -68,7 +72,7 @@ where let span = span_for_digits(digits); let delta_down = self.nanosecond() % span; if delta_down > 0 { - self - Duration::nanoseconds(delta_down.into()) + self - TimeDelta::nanoseconds(delta_down.into()) } else { self // unchanged } @@ -76,7 +80,7 @@ where } // Return the maximum span in nanoseconds for the target number of digits. -fn span_for_digits(digits: u16) -> u32 { +const fn span_for_digits(digits: u16) -> u32 { // fast lookup form of: 10^(9-min(9,digits)) match digits { 0 => 1_000_000_000, @@ -92,139 +96,229 @@ fn span_for_digits(digits: u16) -> u32 { } } -/// Extension trait for rounding or truncating a DateTime by a Duration. +/// Extension trait for rounding or truncating a DateTime by a TimeDelta. /// /// # Limitations -/// Both rounding and truncating are done via [`Duration::num_nanoseconds`] and -/// [`DateTime::timestamp_nanos`]. This means that they will fail if either the -/// `Duration` or the `DateTime` are too big to represented as nanoseconds. They -/// will also fail if the `Duration` is bigger than the timestamp. +/// Both rounding and truncating are done via [`TimeDelta::num_nanoseconds`] and +/// [`DateTime::timestamp_nanos_opt`]. This means that they will fail if either the +/// `TimeDelta` or the `DateTime` are too big to represented as nanoseconds. They +/// will also fail if the `TimeDelta` is bigger than the timestamp, negative or zero. pub trait DurationRound: Sized { /// Error that can occur in rounding or truncating - #[cfg(any(feature = "std", test))] + #[cfg(feature = "std")] type Err: std::error::Error; /// Error that can occur in rounding or truncating - #[cfg(not(any(feature = "std", test)))] + #[cfg(not(feature = "std"))] type Err: fmt::Debug + fmt::Display; - /// Return a copy rounded by Duration. + /// Return a copy rounded by TimeDelta. /// /// # Example /// ``` rust - /// # use chrono::{DateTime, DurationRound, Duration, TimeZone, Utc}; - /// let dt = Utc.ymd(2018, 1, 11).and_hms_milli(12, 0, 0, 154); + /// # use chrono::{DurationRound, TimeDelta, NaiveDate}; + /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11) + /// .unwrap() + /// .and_hms_milli_opt(12, 0, 0, 154) + /// .unwrap() + /// .and_utc(); /// assert_eq!( - /// dt.duration_round(Duration::milliseconds(10)).unwrap().to_string(), + /// dt.duration_round(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(), /// "2018-01-11 12:00:00.150 UTC" /// ); /// assert_eq!( - /// dt.duration_round(Duration::days(1)).unwrap().to_string(), + /// dt.duration_round(TimeDelta::try_days(1).unwrap()).unwrap().to_string(), /// "2018-01-12 00:00:00 UTC" /// ); /// ``` - fn duration_round(self, duration: Duration) -> Result; + fn duration_round(self, duration: TimeDelta) -> Result; - /// Return a copy truncated by Duration. + /// Return a copy truncated by TimeDelta. /// /// # Example /// ``` rust - /// # use chrono::{DateTime, DurationRound, Duration, TimeZone, Utc}; - /// let dt = Utc.ymd(2018, 1, 11).and_hms_milli(12, 0, 0, 154); + /// # use chrono::{DurationRound, TimeDelta, NaiveDate}; + /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11) + /// .unwrap() + /// .and_hms_milli_opt(12, 0, 0, 154) + /// .unwrap() + /// .and_utc(); /// assert_eq!( - /// dt.duration_trunc(Duration::milliseconds(10)).unwrap().to_string(), + /// dt.duration_trunc(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(), /// "2018-01-11 12:00:00.150 UTC" /// ); /// assert_eq!( - /// dt.duration_trunc(Duration::days(1)).unwrap().to_string(), + /// dt.duration_trunc(TimeDelta::try_days(1).unwrap()).unwrap().to_string(), /// "2018-01-11 00:00:00 UTC" /// ); /// ``` - fn duration_trunc(self, duration: Duration) -> Result; -} + fn duration_trunc(self, duration: TimeDelta) -> Result; -/// The maximum number of seconds a DateTime can be to be represented as nanoseconds -const MAX_SECONDS_TIMESTAMP_FOR_NANOS: i64 = 9_223_372_036; + /// Return a copy rounded **up** by TimeDelta. + /// + /// # Example + /// ``` rust + /// # use chrono::{DurationRound, TimeDelta, NaiveDate}; + /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11) + /// .unwrap() + /// .and_hms_milli_opt(12, 0, 0, 154) + /// .unwrap() + /// .and_utc(); + /// assert_eq!( + /// dt.duration_round_up(TimeDelta::milliseconds(10)).unwrap().to_string(), + /// "2018-01-11 12:00:00.160 UTC" + /// ); + /// assert_eq!( + /// dt.duration_round_up(TimeDelta::hours(1)).unwrap().to_string(), + /// "2018-01-11 13:00:00 UTC" + /// ); + /// + /// assert_eq!( + /// dt.duration_round_up(TimeDelta::days(1)).unwrap().to_string(), + /// "2018-01-12 00:00:00 UTC" + /// ); + /// ``` + fn duration_round_up(self, duration: TimeDelta) -> Result; +} impl DurationRound for DateTime { type Err = RoundingError; - fn duration_round(self, duration: Duration) -> Result { - if let Some(span) = duration.num_nanoseconds() { - if self.timestamp().abs() > MAX_SECONDS_TIMESTAMP_FOR_NANOS { - return Err(RoundingError::TimestampExceedsLimit); - } - let stamp = self.timestamp_nanos(); - if span > stamp.abs() { - return Err(RoundingError::DurationExceedsTimestamp); - } - let delta_down = stamp % span; - if delta_down == 0 { - Ok(self) - } else { - let (delta_up, delta_down) = if delta_down < 0 { - (delta_down.abs(), span - delta_down.abs()) - } else { - (span - delta_down, delta_down) - }; - if delta_up <= delta_down { - Ok(self + Duration::nanoseconds(delta_up)) - } else { - Ok(self - Duration::nanoseconds(delta_down)) - } - } - } else { - Err(RoundingError::DurationExceedsLimit) - } + fn duration_round(self, duration: TimeDelta) -> Result { + duration_round(self.naive_local(), self, duration) } - fn duration_trunc(self, duration: Duration) -> Result { - if let Some(span) = duration.num_nanoseconds() { - if self.timestamp().abs() > MAX_SECONDS_TIMESTAMP_FOR_NANOS { - return Err(RoundingError::TimestampExceedsLimit); - } - let stamp = self.timestamp_nanos(); - if span > stamp.abs() { - return Err(RoundingError::DurationExceedsTimestamp); - } - let delta_down = stamp % span; - match delta_down.cmp(&0) { - Ordering::Equal => Ok(self), - Ordering::Greater => Ok(self - Duration::nanoseconds(delta_down)), - Ordering::Less => Ok(self - Duration::nanoseconds(span - delta_down.abs())), - } - } else { - Err(RoundingError::DurationExceedsLimit) - } + fn duration_trunc(self, duration: TimeDelta) -> Result { + duration_trunc(self.naive_local(), self, duration) + } + + fn duration_round_up(self, duration: TimeDelta) -> Result { + duration_round_up(self.naive_local(), self, duration) } } -/// An error from rounding by `Duration` +impl DurationRound for NaiveDateTime { + type Err = RoundingError; + + fn duration_round(self, duration: TimeDelta) -> Result { + duration_round(self, self, duration) + } + + fn duration_trunc(self, duration: TimeDelta) -> Result { + duration_trunc(self, self, duration) + } + + fn duration_round_up(self, duration: TimeDelta) -> Result { + duration_round_up(self, self, duration) + } +} + +fn duration_round( + naive: NaiveDateTime, + original: T, + duration: TimeDelta, +) -> Result +where + T: Timelike + Add + Sub, +{ + if let Some(span) = duration.num_nanoseconds() { + if span <= 0 { + return Err(RoundingError::DurationExceedsLimit); + } + let stamp = + naive.and_utc().timestamp_nanos_opt().ok_or(RoundingError::TimestampExceedsLimit)?; + let delta_down = stamp % span; + if delta_down == 0 { + Ok(original) + } else { + let (delta_up, delta_down) = if delta_down < 0 { + (delta_down.abs(), span - delta_down.abs()) + } else { + (span - delta_down, delta_down) + }; + if delta_up <= delta_down { + Ok(original + TimeDelta::nanoseconds(delta_up)) + } else { + Ok(original - TimeDelta::nanoseconds(delta_down)) + } + } + } else { + Err(RoundingError::DurationExceedsLimit) + } +} + +fn duration_trunc( + naive: NaiveDateTime, + original: T, + duration: TimeDelta, +) -> Result +where + T: Timelike + Add + Sub, +{ + if let Some(span) = duration.num_nanoseconds() { + if span <= 0 { + return Err(RoundingError::DurationExceedsLimit); + } + let stamp = + naive.and_utc().timestamp_nanos_opt().ok_or(RoundingError::TimestampExceedsLimit)?; + let delta_down = stamp % span; + match delta_down.cmp(&0) { + Ordering::Equal => Ok(original), + Ordering::Greater => Ok(original - TimeDelta::nanoseconds(delta_down)), + Ordering::Less => Ok(original - TimeDelta::nanoseconds(span - delta_down.abs())), + } + } else { + Err(RoundingError::DurationExceedsLimit) + } +} + +fn duration_round_up( + naive: NaiveDateTime, + original: T, + duration: TimeDelta, +) -> Result +where + T: Timelike + Add + Sub, +{ + if let Some(span) = duration.num_nanoseconds() { + if span <= 0 { + return Err(RoundingError::DurationExceedsLimit); + } + let stamp = + naive.and_utc().timestamp_nanos_opt().ok_or(RoundingError::TimestampExceedsLimit)?; + let delta_down = stamp % span; + match delta_down.cmp(&0) { + Ordering::Equal => Ok(original), + Ordering::Greater => Ok(original + TimeDelta::nanoseconds(span - delta_down)), + Ordering::Less => Ok(original + TimeDelta::nanoseconds(delta_down.abs())), + } + } else { + Err(RoundingError::DurationExceedsLimit) + } +} + +/// An error from rounding by `TimeDelta` /// /// See: [`DurationRound`] #[derive(Debug, Clone, PartialEq, Eq, Copy)] pub enum RoundingError { - /// Error when the Duration exceeds the Duration from or until the Unix epoch. + /// Error when the TimeDelta exceeds the TimeDelta from or until the Unix epoch. /// - /// ``` rust - /// # use chrono::{DateTime, DurationRound, Duration, RoundingError, TimeZone, Utc}; - /// let dt = Utc.ymd(1970, 12, 12).and_hms(0, 0, 0); - /// - /// assert_eq!( - /// dt.duration_round(Duration::days(365)), - /// Err(RoundingError::DurationExceedsTimestamp), - /// ); - /// ``` + /// Note: this error is not produced anymore. DurationExceedsTimestamp, - /// Error when `Duration.num_nanoseconds` exceeds the limit. + /// Error when `TimeDelta.num_nanoseconds` exceeds the limit. /// /// ``` rust - /// # use chrono::{DateTime, DurationRound, Duration, RoundingError, TimeZone, Utc}; - /// let dt = Utc.ymd(2260, 12, 31).and_hms_nano(23, 59, 59, 1_75_500_000); + /// # use chrono::{DurationRound, TimeDelta, RoundingError, NaiveDate}; + /// let dt = NaiveDate::from_ymd_opt(2260, 12, 31) + /// .unwrap() + /// .and_hms_nano_opt(23, 59, 59, 1_75_500_000) + /// .unwrap() + /// .and_utc(); /// /// assert_eq!( - /// dt.duration_round(Duration::days(300 * 365)), + /// dt.duration_round(TimeDelta::try_days(300 * 365).unwrap()), /// Err(RoundingError::DurationExceedsLimit) /// ); /// ``` @@ -233,10 +327,13 @@ pub enum RoundingError { /// Error when `DateTime.timestamp_nanos` exceeds the limit. /// /// ``` rust - /// # use chrono::{DateTime, DurationRound, Duration, RoundingError, TimeZone, Utc}; - /// let dt = Utc.ymd(2300, 12, 12).and_hms(0, 0, 0); + /// # use chrono::{DurationRound, TimeDelta, RoundingError, TimeZone, Utc}; + /// let dt = Utc.with_ymd_and_hms(2300, 12, 12, 0, 0, 0).unwrap(); /// - /// assert_eq!(dt.duration_round(Duration::days(1)), Err(RoundingError::TimestampExceedsLimit),); + /// assert_eq!( + /// dt.duration_round(TimeDelta::try_days(1).unwrap()), + /// Err(RoundingError::TimestampExceedsLimit) + /// ); /// ``` TimestampExceedsLimit, } @@ -257,7 +354,7 @@ impl fmt::Display for RoundingError { } } -#[cfg(any(feature = "std", test))] +#[cfg(feature = "std")] impl std::error::Error for RoundingError { #[allow(deprecated)] fn description(&self) -> &str { @@ -267,30 +364,45 @@ impl std::error::Error for RoundingError { #[cfg(test)] mod tests { - use super::{Duration, DurationRound, SubsecRound}; - use offset::{FixedOffset, TimeZone, Utc}; - use Timelike; + use super::{DurationRound, RoundingError, SubsecRound, TimeDelta}; + use crate::Timelike; + use crate::offset::{FixedOffset, TimeZone, Utc}; + use crate::{DateTime, NaiveDate}; #[test] fn test_round_subsecs() { - let pst = FixedOffset::east(8 * 60 * 60); - let dt = pst.ymd(2018, 1, 11).and_hms_nano(10, 5, 13, 084_660_684); + let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap(); + let dt = pst + .from_local_datetime( + &NaiveDate::from_ymd_opt(2018, 1, 11) + .unwrap() + .and_hms_nano_opt(10, 5, 13, 84_660_684) + .unwrap(), + ) + .unwrap(); assert_eq!(dt.round_subsecs(10), dt); assert_eq!(dt.round_subsecs(9), dt); - assert_eq!(dt.round_subsecs(8).nanosecond(), 084_660_680); - assert_eq!(dt.round_subsecs(7).nanosecond(), 084_660_700); - assert_eq!(dt.round_subsecs(6).nanosecond(), 084_661_000); - assert_eq!(dt.round_subsecs(5).nanosecond(), 084_660_000); - assert_eq!(dt.round_subsecs(4).nanosecond(), 084_700_000); - assert_eq!(dt.round_subsecs(3).nanosecond(), 085_000_000); - assert_eq!(dt.round_subsecs(2).nanosecond(), 080_000_000); + assert_eq!(dt.round_subsecs(8).nanosecond(), 84_660_680); + assert_eq!(dt.round_subsecs(7).nanosecond(), 84_660_700); + assert_eq!(dt.round_subsecs(6).nanosecond(), 84_661_000); + assert_eq!(dt.round_subsecs(5).nanosecond(), 84_660_000); + assert_eq!(dt.round_subsecs(4).nanosecond(), 84_700_000); + assert_eq!(dt.round_subsecs(3).nanosecond(), 85_000_000); + assert_eq!(dt.round_subsecs(2).nanosecond(), 80_000_000); assert_eq!(dt.round_subsecs(1).nanosecond(), 100_000_000); assert_eq!(dt.round_subsecs(0).nanosecond(), 0); assert_eq!(dt.round_subsecs(0).second(), 13); - let dt = Utc.ymd(2018, 1, 11).and_hms_nano(10, 5, 27, 750_500_000); + let dt = Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2018, 1, 11) + .unwrap() + .and_hms_nano_opt(10, 5, 27, 750_500_000) + .unwrap(), + ) + .unwrap(); assert_eq!(dt.round_subsecs(9), dt); assert_eq!(dt.round_subsecs(4), dt); assert_eq!(dt.round_subsecs(3).nanosecond(), 751_000_000); @@ -303,7 +415,14 @@ mod tests { #[test] fn test_round_leap_nanos() { - let dt = Utc.ymd(2016, 12, 31).and_hms_nano(23, 59, 59, 1_750_500_000); + let dt = Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2016, 12, 31) + .unwrap() + .and_hms_nano_opt(23, 59, 59, 1_750_500_000) + .unwrap(), + ) + .unwrap(); assert_eq!(dt.round_subsecs(9), dt); assert_eq!(dt.round_subsecs(4), dt); assert_eq!(dt.round_subsecs(2).nanosecond(), 1_750_000_000); @@ -316,24 +435,38 @@ mod tests { #[test] fn test_trunc_subsecs() { - let pst = FixedOffset::east(8 * 60 * 60); - let dt = pst.ymd(2018, 1, 11).and_hms_nano(10, 5, 13, 084_660_684); + let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap(); + let dt = pst + .from_local_datetime( + &NaiveDate::from_ymd_opt(2018, 1, 11) + .unwrap() + .and_hms_nano_opt(10, 5, 13, 84_660_684) + .unwrap(), + ) + .unwrap(); assert_eq!(dt.trunc_subsecs(10), dt); assert_eq!(dt.trunc_subsecs(9), dt); - assert_eq!(dt.trunc_subsecs(8).nanosecond(), 084_660_680); - assert_eq!(dt.trunc_subsecs(7).nanosecond(), 084_660_600); - assert_eq!(dt.trunc_subsecs(6).nanosecond(), 084_660_000); - assert_eq!(dt.trunc_subsecs(5).nanosecond(), 084_660_000); - assert_eq!(dt.trunc_subsecs(4).nanosecond(), 084_600_000); - assert_eq!(dt.trunc_subsecs(3).nanosecond(), 084_000_000); - assert_eq!(dt.trunc_subsecs(2).nanosecond(), 080_000_000); + assert_eq!(dt.trunc_subsecs(8).nanosecond(), 84_660_680); + assert_eq!(dt.trunc_subsecs(7).nanosecond(), 84_660_600); + assert_eq!(dt.trunc_subsecs(6).nanosecond(), 84_660_000); + assert_eq!(dt.trunc_subsecs(5).nanosecond(), 84_660_000); + assert_eq!(dt.trunc_subsecs(4).nanosecond(), 84_600_000); + assert_eq!(dt.trunc_subsecs(3).nanosecond(), 84_000_000); + assert_eq!(dt.trunc_subsecs(2).nanosecond(), 80_000_000); assert_eq!(dt.trunc_subsecs(1).nanosecond(), 0); assert_eq!(dt.trunc_subsecs(0).nanosecond(), 0); assert_eq!(dt.trunc_subsecs(0).second(), 13); - let dt = pst.ymd(2018, 1, 11).and_hms_nano(10, 5, 27, 750_500_000); + let dt = pst + .from_local_datetime( + &NaiveDate::from_ymd_opt(2018, 1, 11) + .unwrap() + .and_hms_nano_opt(10, 5, 27, 750_500_000) + .unwrap(), + ) + .unwrap(); assert_eq!(dt.trunc_subsecs(9), dt); assert_eq!(dt.trunc_subsecs(4), dt); assert_eq!(dt.trunc_subsecs(3).nanosecond(), 750_000_000); @@ -346,7 +479,14 @@ mod tests { #[test] fn test_trunc_leap_nanos() { - let dt = Utc.ymd(2016, 12, 31).and_hms_nano(23, 59, 59, 1_750_500_000); + let dt = Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2016, 12, 31) + .unwrap() + .and_hms_nano_opt(23, 59, 59, 1_750_500_000) + .unwrap(), + ) + .unwrap(); assert_eq!(dt.trunc_subsecs(9), dt); assert_eq!(dt.trunc_subsecs(4), dt); assert_eq!(dt.trunc_subsecs(2).nanosecond(), 1_750_000_000); @@ -359,98 +499,584 @@ mod tests { #[test] fn test_duration_round() { - let dt = Utc.ymd(2016, 12, 31).and_hms_nano(23, 59, 59, 175_500_000); + let dt = Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2016, 12, 31) + .unwrap() + .and_hms_nano_opt(23, 59, 59, 175_500_000) + .unwrap(), + ) + .unwrap(); assert_eq!( - dt.duration_round(Duration::milliseconds(10)).unwrap().to_string(), + dt.duration_round(TimeDelta::new(-1, 0).unwrap()), + Err(RoundingError::DurationExceedsLimit) + ); + assert_eq!(dt.duration_round(TimeDelta::zero()), Err(RoundingError::DurationExceedsLimit)); + + assert_eq!( + dt.duration_round(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(), "2016-12-31 23:59:59.180 UTC" ); // round up - let dt = Utc.ymd(2012, 12, 12).and_hms_milli(18, 22, 30, 0); + let dt = Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2012, 12, 12) + .unwrap() + .and_hms_milli_opt(18, 22, 30, 0) + .unwrap(), + ) + .unwrap(); assert_eq!( - dt.duration_round(Duration::minutes(5)).unwrap().to_string(), + dt.duration_round(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(), "2012-12-12 18:25:00 UTC" ); // round down - let dt = Utc.ymd(2012, 12, 12).and_hms_milli(18, 22, 29, 999); + let dt = Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2012, 12, 12) + .unwrap() + .and_hms_milli_opt(18, 22, 29, 999) + .unwrap(), + ) + .unwrap(); assert_eq!( - dt.duration_round(Duration::minutes(5)).unwrap().to_string(), + dt.duration_round(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(), "2012-12-12 18:20:00 UTC" ); assert_eq!( - dt.duration_round(Duration::minutes(10)).unwrap().to_string(), + dt.duration_round(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(), "2012-12-12 18:20:00 UTC" ); assert_eq!( - dt.duration_round(Duration::minutes(30)).unwrap().to_string(), + dt.duration_round(TimeDelta::try_minutes(30).unwrap()).unwrap().to_string(), "2012-12-12 18:30:00 UTC" ); assert_eq!( - dt.duration_round(Duration::hours(1)).unwrap().to_string(), + dt.duration_round(TimeDelta::try_hours(1).unwrap()).unwrap().to_string(), "2012-12-12 18:00:00 UTC" ); assert_eq!( - dt.duration_round(Duration::days(1)).unwrap().to_string(), + dt.duration_round(TimeDelta::try_days(1).unwrap()).unwrap().to_string(), "2012-12-13 00:00:00 UTC" ); + + // timezone east + let dt = + FixedOffset::east_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap(); + assert_eq!( + dt.duration_round(TimeDelta::try_days(1).unwrap()).unwrap().to_string(), + "2020-10-28 00:00:00 +01:00" + ); + assert_eq!( + dt.duration_round(TimeDelta::try_weeks(1).unwrap()).unwrap().to_string(), + "2020-10-29 00:00:00 +01:00" + ); + + // timezone west + let dt = + FixedOffset::west_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap(); + assert_eq!( + dt.duration_round(TimeDelta::try_days(1).unwrap()).unwrap().to_string(), + "2020-10-28 00:00:00 -01:00" + ); + assert_eq!( + dt.duration_round(TimeDelta::try_weeks(1).unwrap()).unwrap().to_string(), + "2020-10-29 00:00:00 -01:00" + ); + } + + #[test] + fn test_duration_round_naive() { + let dt = Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2016, 12, 31) + .unwrap() + .and_hms_nano_opt(23, 59, 59, 175_500_000) + .unwrap(), + ) + .unwrap() + .naive_utc(); + + assert_eq!( + dt.duration_round(TimeDelta::new(-1, 0).unwrap()), + Err(RoundingError::DurationExceedsLimit) + ); + assert_eq!(dt.duration_round(TimeDelta::zero()), Err(RoundingError::DurationExceedsLimit)); + + assert_eq!( + dt.duration_round(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(), + "2016-12-31 23:59:59.180" + ); + + // round up + let dt = Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2012, 12, 12) + .unwrap() + .and_hms_milli_opt(18, 22, 30, 0) + .unwrap(), + ) + .unwrap() + .naive_utc(); + assert_eq!( + dt.duration_round(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(), + "2012-12-12 18:25:00" + ); + // round down + let dt = Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2012, 12, 12) + .unwrap() + .and_hms_milli_opt(18, 22, 29, 999) + .unwrap(), + ) + .unwrap() + .naive_utc(); + assert_eq!( + dt.duration_round(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(), + "2012-12-12 18:20:00" + ); + + assert_eq!( + dt.duration_round(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(), + "2012-12-12 18:20:00" + ); + assert_eq!( + dt.duration_round(TimeDelta::try_minutes(30).unwrap()).unwrap().to_string(), + "2012-12-12 18:30:00" + ); + assert_eq!( + dt.duration_round(TimeDelta::try_hours(1).unwrap()).unwrap().to_string(), + "2012-12-12 18:00:00" + ); + assert_eq!( + dt.duration_round(TimeDelta::try_days(1).unwrap()).unwrap().to_string(), + "2012-12-13 00:00:00" + ); } #[test] fn test_duration_round_pre_epoch() { - let dt = Utc.ymd(1969, 12, 12).and_hms(12, 12, 12); + let dt = Utc.with_ymd_and_hms(1969, 12, 12, 12, 12, 12).unwrap(); assert_eq!( - dt.duration_round(Duration::minutes(10)).unwrap().to_string(), + dt.duration_round(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(), "1969-12-12 12:10:00 UTC" ); } #[test] fn test_duration_trunc() { - let dt = Utc.ymd(2016, 12, 31).and_hms_nano(23, 59, 59, 1_75_500_000); + let dt = Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2016, 12, 31) + .unwrap() + .and_hms_nano_opt(23, 59, 59, 175_500_000) + .unwrap(), + ) + .unwrap(); assert_eq!( - dt.duration_trunc(Duration::milliseconds(10)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::new(-1, 0).unwrap()), + Err(RoundingError::DurationExceedsLimit) + ); + assert_eq!(dt.duration_trunc(TimeDelta::zero()), Err(RoundingError::DurationExceedsLimit)); + + assert_eq!( + dt.duration_trunc(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(), "2016-12-31 23:59:59.170 UTC" ); // would round up - let dt = Utc.ymd(2012, 12, 12).and_hms_milli(18, 22, 30, 0); + let dt = Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2012, 12, 12) + .unwrap() + .and_hms_milli_opt(18, 22, 30, 0) + .unwrap(), + ) + .unwrap(); assert_eq!( - dt.duration_trunc(Duration::minutes(5)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(), "2012-12-12 18:20:00 UTC" ); // would round down - let dt = Utc.ymd(2012, 12, 12).and_hms_milli(18, 22, 29, 999); + let dt = Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2012, 12, 12) + .unwrap() + .and_hms_milli_opt(18, 22, 29, 999) + .unwrap(), + ) + .unwrap(); assert_eq!( - dt.duration_trunc(Duration::minutes(5)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(), "2012-12-12 18:20:00 UTC" ); assert_eq!( - dt.duration_trunc(Duration::minutes(10)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(), "2012-12-12 18:20:00 UTC" ); assert_eq!( - dt.duration_trunc(Duration::minutes(30)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::try_minutes(30).unwrap()).unwrap().to_string(), "2012-12-12 18:00:00 UTC" ); assert_eq!( - dt.duration_trunc(Duration::hours(1)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::try_hours(1).unwrap()).unwrap().to_string(), "2012-12-12 18:00:00 UTC" ); assert_eq!( - dt.duration_trunc(Duration::days(1)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::try_days(1).unwrap()).unwrap().to_string(), "2012-12-12 00:00:00 UTC" ); + + // timezone east + let dt = + FixedOffset::east_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap(); + assert_eq!( + dt.duration_trunc(TimeDelta::try_days(1).unwrap()).unwrap().to_string(), + "2020-10-27 00:00:00 +01:00" + ); + assert_eq!( + dt.duration_trunc(TimeDelta::try_weeks(1).unwrap()).unwrap().to_string(), + "2020-10-22 00:00:00 +01:00" + ); + + // timezone west + let dt = + FixedOffset::west_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap(); + assert_eq!( + dt.duration_trunc(TimeDelta::try_days(1).unwrap()).unwrap().to_string(), + "2020-10-27 00:00:00 -01:00" + ); + assert_eq!( + dt.duration_trunc(TimeDelta::try_weeks(1).unwrap()).unwrap().to_string(), + "2020-10-22 00:00:00 -01:00" + ); + } + + #[test] + fn test_duration_trunc_naive() { + let dt = Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2016, 12, 31) + .unwrap() + .and_hms_nano_opt(23, 59, 59, 175_500_000) + .unwrap(), + ) + .unwrap() + .naive_utc(); + + assert_eq!( + dt.duration_trunc(TimeDelta::new(-1, 0).unwrap()), + Err(RoundingError::DurationExceedsLimit) + ); + assert_eq!(dt.duration_trunc(TimeDelta::zero()), Err(RoundingError::DurationExceedsLimit)); + + assert_eq!( + dt.duration_trunc(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(), + "2016-12-31 23:59:59.170" + ); + + // would round up + let dt = Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2012, 12, 12) + .unwrap() + .and_hms_milli_opt(18, 22, 30, 0) + .unwrap(), + ) + .unwrap() + .naive_utc(); + assert_eq!( + dt.duration_trunc(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(), + "2012-12-12 18:20:00" + ); + // would round down + let dt = Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2012, 12, 12) + .unwrap() + .and_hms_milli_opt(18, 22, 29, 999) + .unwrap(), + ) + .unwrap() + .naive_utc(); + assert_eq!( + dt.duration_trunc(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(), + "2012-12-12 18:20:00" + ); + assert_eq!( + dt.duration_trunc(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(), + "2012-12-12 18:20:00" + ); + assert_eq!( + dt.duration_trunc(TimeDelta::try_minutes(30).unwrap()).unwrap().to_string(), + "2012-12-12 18:00:00" + ); + assert_eq!( + dt.duration_trunc(TimeDelta::try_hours(1).unwrap()).unwrap().to_string(), + "2012-12-12 18:00:00" + ); + assert_eq!( + dt.duration_trunc(TimeDelta::try_days(1).unwrap()).unwrap().to_string(), + "2012-12-12 00:00:00" + ); } #[test] fn test_duration_trunc_pre_epoch() { - let dt = Utc.ymd(1969, 12, 12).and_hms(12, 12, 12); + let dt = Utc.with_ymd_and_hms(1969, 12, 12, 12, 12, 12).unwrap(); assert_eq!( - dt.duration_trunc(Duration::minutes(10)).unwrap().to_string(), + dt.duration_trunc(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(), "1969-12-12 12:10:00 UTC" ); } + + #[test] + fn issue1010() { + let dt = DateTime::from_timestamp(-4_227_854_320, 678_774_288).unwrap(); + let span = TimeDelta::microseconds(-7_019_067_213_869_040); + assert_eq!(dt.duration_trunc(span), Err(RoundingError::DurationExceedsLimit)); + + let dt = DateTime::from_timestamp(320_041_586, 920_103_021).unwrap(); + let span = TimeDelta::nanoseconds(-8_923_838_508_697_114_584); + assert_eq!(dt.duration_round(span), Err(RoundingError::DurationExceedsLimit)); + + let dt = DateTime::from_timestamp(-2_621_440, 0).unwrap(); + let span = TimeDelta::nanoseconds(-9_223_372_036_854_771_421); + assert_eq!(dt.duration_round(span), Err(RoundingError::DurationExceedsLimit)); + } + + #[test] + fn test_duration_trunc_close_to_epoch() { + let span = TimeDelta::try_minutes(15).unwrap(); + + let dt = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_opt(0, 0, 15).unwrap(); + assert_eq!(dt.duration_trunc(span).unwrap().to_string(), "1970-01-01 00:00:00"); + + let dt = NaiveDate::from_ymd_opt(1969, 12, 31).unwrap().and_hms_opt(23, 59, 45).unwrap(); + assert_eq!(dt.duration_trunc(span).unwrap().to_string(), "1969-12-31 23:45:00"); + } + + #[test] + fn test_duration_round_close_to_epoch() { + let span = TimeDelta::try_minutes(15).unwrap(); + + let dt = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_opt(0, 0, 15).unwrap(); + assert_eq!(dt.duration_round(span).unwrap().to_string(), "1970-01-01 00:00:00"); + + let dt = NaiveDate::from_ymd_opt(1969, 12, 31).unwrap().and_hms_opt(23, 59, 45).unwrap(); + assert_eq!(dt.duration_round(span).unwrap().to_string(), "1970-01-01 00:00:00"); + } + + #[test] + fn test_duration_round_close_to_min_max() { + let span = TimeDelta::nanoseconds(i64::MAX); + + let dt = DateTime::from_timestamp_nanos(i64::MIN / 2 - 1); + assert_eq!( + dt.duration_round(span).unwrap().to_string(), + "1677-09-21 00:12:43.145224193 UTC" + ); + + let dt = DateTime::from_timestamp_nanos(i64::MIN / 2 + 1); + assert_eq!(dt.duration_round(span).unwrap().to_string(), "1970-01-01 00:00:00 UTC"); + + let dt = DateTime::from_timestamp_nanos(i64::MAX / 2 + 1); + assert_eq!( + dt.duration_round(span).unwrap().to_string(), + "2262-04-11 23:47:16.854775807 UTC" + ); + + let dt = DateTime::from_timestamp_nanos(i64::MAX / 2 - 1); + assert_eq!(dt.duration_round(span).unwrap().to_string(), "1970-01-01 00:00:00 UTC"); + } + + #[test] + fn test_duration_round_up() { + let dt = NaiveDate::from_ymd_opt(2016, 12, 31) + .unwrap() + .and_hms_nano_opt(23, 59, 59, 175_500_000) + .unwrap() + .and_utc(); + + assert_eq!( + dt.duration_round_up(TimeDelta::new(-1, 0).unwrap()), + Err(RoundingError::DurationExceedsLimit) + ); + + assert_eq!( + dt.duration_round_up(TimeDelta::zero()), + Err(RoundingError::DurationExceedsLimit) + ); + + assert_eq!(dt.duration_round_up(TimeDelta::MAX), Err(RoundingError::DurationExceedsLimit)); + + assert_eq!( + dt.duration_round_up(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(), + "2016-12-31 23:59:59.180 UTC" + ); + + // round up + let dt = NaiveDate::from_ymd_opt(2012, 12, 12) + .unwrap() + .and_hms_milli_opt(18, 22, 30, 0) + .unwrap() + .and_utc(); + + assert_eq!( + dt.duration_round_up(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(), + "2012-12-12 18:25:00 UTC" + ); + + assert_eq!( + dt.duration_round_up(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(), + "2012-12-12 18:30:00 UTC" + ); + assert_eq!( + dt.duration_round_up(TimeDelta::try_minutes(30).unwrap()).unwrap().to_string(), + "2012-12-12 18:30:00 UTC" + ); + assert_eq!( + dt.duration_round_up(TimeDelta::try_hours(1).unwrap()).unwrap().to_string(), + "2012-12-12 19:00:00 UTC" + ); + assert_eq!( + dt.duration_round_up(TimeDelta::try_days(1).unwrap()).unwrap().to_string(), + "2012-12-13 00:00:00 UTC" + ); + + // timezone east + let dt = + FixedOffset::east_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap(); + assert_eq!( + dt.duration_round_up(TimeDelta::try_days(1).unwrap()).unwrap().to_string(), + "2020-10-28 00:00:00 +01:00" + ); + assert_eq!( + dt.duration_round_up(TimeDelta::try_weeks(1).unwrap()).unwrap().to_string(), + "2020-10-29 00:00:00 +01:00" + ); + + // timezone west + let dt = + FixedOffset::west_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap(); + assert_eq!( + dt.duration_round_up(TimeDelta::try_days(1).unwrap()).unwrap().to_string(), + "2020-10-28 00:00:00 -01:00" + ); + assert_eq!( + dt.duration_round_up(TimeDelta::try_weeks(1).unwrap()).unwrap().to_string(), + "2020-10-29 00:00:00 -01:00" + ); + } + + #[test] + fn test_duration_round_up_naive() { + let dt = NaiveDate::from_ymd_opt(2016, 12, 31) + .unwrap() + .and_hms_nano_opt(23, 59, 59, 175_500_000) + .unwrap(); + + assert_eq!( + dt.duration_round_up(TimeDelta::new(-1, 0).unwrap()), + Err(RoundingError::DurationExceedsLimit) + ); + assert_eq!( + dt.duration_round_up(TimeDelta::zero()), + Err(RoundingError::DurationExceedsLimit) + ); + + assert_eq!(dt.duration_round_up(TimeDelta::MAX), Err(RoundingError::DurationExceedsLimit)); + + assert_eq!( + dt.duration_round_up(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(), + "2016-12-31 23:59:59.180" + ); + + let dt = Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2012, 12, 12) + .unwrap() + .and_hms_milli_opt(18, 22, 30, 0) + .unwrap(), + ) + .unwrap() + .naive_utc(); + assert_eq!( + dt.duration_round_up(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(), + "2012-12-12 18:25:00" + ); + assert_eq!( + dt.duration_round_up(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(), + "2012-12-12 18:30:00" + ); + assert_eq!( + dt.duration_round_up(TimeDelta::try_minutes(30).unwrap()).unwrap().to_string(), + "2012-12-12 18:30:00" + ); + assert_eq!( + dt.duration_round_up(TimeDelta::try_hours(1).unwrap()).unwrap().to_string(), + "2012-12-12 19:00:00" + ); + assert_eq!( + dt.duration_round_up(TimeDelta::try_days(1).unwrap()).unwrap().to_string(), + "2012-12-13 00:00:00" + ); + } + + #[test] + fn test_duration_round_up_pre_epoch() { + let dt = Utc.with_ymd_and_hms(1969, 12, 12, 12, 12, 12).unwrap(); + assert_eq!( + dt.duration_round_up(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(), + "1969-12-12 12:20:00 UTC" + ); + + let time_delta = TimeDelta::minutes(30); + assert_eq!( + DateTime::UNIX_EPOCH.duration_round_up(time_delta).unwrap().to_string(), + "1970-01-01 00:00:00 UTC" + ) + } + + #[test] + fn test_duration_round_up_close_to_min_max() { + let mut dt = NaiveDate::from_ymd_opt(2012, 12, 12) + .unwrap() + .and_hms_milli_opt(18, 22, 30, 0) + .unwrap() + .and_utc(); + + let span = TimeDelta::nanoseconds(i64::MAX); + + assert_eq!( + dt.duration_round_up(span).unwrap().to_string(), + DateTime::from_timestamp_nanos(i64::MAX).to_string() + ); + + dt = DateTime::UNIX_EPOCH + TimeDelta::nanoseconds(1); + assert_eq!(dt.duration_round_up(span).unwrap(), DateTime::from_timestamp_nanos(i64::MAX)); + + let dt = DateTime::from_timestamp_nanos(1); + assert_eq!( + dt.duration_round_up(span).unwrap().to_string(), + "2262-04-11 23:47:16.854775807 UTC" + ); + + let dt = DateTime::from_timestamp_nanos(-1); + assert_eq!(dt.duration_round_up(span).unwrap(), DateTime::UNIX_EPOCH); + + // Rounds to 1677-09-21 00:12:43.145224193 UTC if at i64::MIN. + // because i64::MIN is 1677-09-21 00:12:43.145224192 UTC. + // + // v + // We add 2 to get to 1677-09-21 00:12:43.145224194 UTC + // this issue is because abs(i64::MIN) == i64::MAX + 1 + let dt = DateTime::from_timestamp_nanos(i64::MIN + 2); + assert_eq!(dt.duration_round_up(span).unwrap(), DateTime::UNIX_EPOCH); + } } diff --git a/third_party/rust/chrono/src/sys.rs b/third_party/rust/chrono/src/sys.rs deleted file mode 100644 index 2e46b7e8eb6..00000000000 --- a/third_party/rust/chrono/src/sys.rs +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// 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. - -//! Platform wrappers for converting UTC times to and from the local time zone. -//! -//! This code was rescued from v0.1 of the time crate, which is no longer -//! maintained. It has been substantially stripped down to the bare minimum -//! required by chrono. - -use std::time::{SystemTime, UNIX_EPOCH}; - -#[cfg(any(target_arch = "wasm32", target_env = "sgx"))] -#[path = "sys/stub.rs"] -mod inner; - -#[cfg(unix)] -#[path = "sys/unix.rs"] -mod inner; - -#[cfg(windows)] -#[path = "sys/windows.rs"] -mod inner; - -/// A record specifying a time value in seconds and nanoseconds, where -/// nanoseconds represent the offset from the given second. -/// -/// For example a timespec of 1.2 seconds after the beginning of the epoch would -/// be represented as {sec: 1, nsec: 200000000}. -pub struct Timespec { - pub sec: i64, - pub nsec: i32, -} - -impl Timespec { - /// Constructs a timespec representing the current time in UTC. - pub fn now() -> Timespec { - let st = - SystemTime::now().duration_since(UNIX_EPOCH).expect("system time before Unix epoch"); - Timespec { sec: st.as_secs() as i64, nsec: st.subsec_nanos() as i32 } - } - - /// Converts this timespec into the system's local time. - pub fn local(self) -> Tm { - let mut tm = Tm { - tm_sec: 0, - tm_min: 0, - tm_hour: 0, - tm_mday: 0, - tm_mon: 0, - tm_year: 0, - tm_wday: 0, - tm_yday: 0, - tm_isdst: 0, - tm_utcoff: 0, - tm_nsec: 0, - }; - inner::time_to_local_tm(self.sec, &mut tm); - tm.tm_nsec = self.nsec; - tm - } -} - -/// Holds a calendar date and time broken down into its components (year, month, -/// day, and so on), also called a broken-down time value. -// FIXME: use c_int instead of i32? -#[cfg(feature = "clock")] -#[repr(C)] -pub struct Tm { - /// Seconds after the minute - [0, 60] - pub tm_sec: i32, - - /// Minutes after the hour - [0, 59] - pub tm_min: i32, - - /// Hours after midnight - [0, 23] - pub tm_hour: i32, - - /// Day of the month - [1, 31] - pub tm_mday: i32, - - /// Months since January - [0, 11] - pub tm_mon: i32, - - /// Years since 1900 - pub tm_year: i32, - - /// Days since Sunday - [0, 6]. 0 = Sunday, 1 = Monday, ..., 6 = Saturday. - pub tm_wday: i32, - - /// Days since January 1 - [0, 365] - pub tm_yday: i32, - - /// Daylight Saving Time flag. - /// - /// This value is positive if Daylight Saving Time is in effect, zero if - /// Daylight Saving Time is not in effect, and negative if this information - /// is not available. - pub tm_isdst: i32, - - /// Identifies the time zone that was used to compute this broken-down time - /// value, including any adjustment for Daylight Saving Time. This is the - /// number of seconds east of UTC. For example, for U.S. Pacific Daylight - /// Time, the value is `-7*60*60 = -25200`. - pub tm_utcoff: i32, - - /// Nanoseconds after the second - [0, 109 - 1] - pub tm_nsec: i32, -} - -impl Tm { - /// Convert time to the seconds from January 1, 1970 - pub fn to_timespec(&self) -> Timespec { - let sec = match self.tm_utcoff { - 0 => inner::utc_tm_to_time(self), - _ => inner::local_tm_to_time(self), - }; - Timespec { sec: sec, nsec: self.tm_nsec } - } -} diff --git a/third_party/rust/chrono/src/sys/stub.rs b/third_party/rust/chrono/src/sys/stub.rs deleted file mode 100644 index 9172a852237..00000000000 --- a/third_party/rust/chrono/src/sys/stub.rs +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// 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. - -use super::Tm; - -fn time_to_tm(ts: i64, tm: &mut Tm) { - let leapyear = |year| -> bool { year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) }; - - static YTAB: [[i64; 12]; 2] = [ - [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], - [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], - ]; - - let mut year = 1970; - - let dayclock = ts % 86400; - let mut dayno = ts / 86400; - - tm.tm_sec = (dayclock % 60) as i32; - tm.tm_min = ((dayclock % 3600) / 60) as i32; - tm.tm_hour = (dayclock / 3600) as i32; - tm.tm_wday = ((dayno + 4) % 7) as i32; - loop { - let yearsize = if leapyear(year) { 366 } else { 365 }; - if dayno >= yearsize { - dayno -= yearsize; - year += 1; - } else { - break; - } - } - tm.tm_year = (year - 1900) as i32; - tm.tm_yday = dayno as i32; - let mut mon = 0; - while dayno >= YTAB[if leapyear(year) { 1 } else { 0 }][mon] { - dayno -= YTAB[if leapyear(year) { 1 } else { 0 }][mon]; - mon += 1; - } - tm.tm_mon = mon as i32; - tm.tm_mday = dayno as i32 + 1; - tm.tm_isdst = 0; -} - -fn tm_to_time(tm: &Tm) -> i64 { - let mut y = tm.tm_year as i64 + 1900; - let mut m = tm.tm_mon as i64 + 1; - if m <= 2 { - y -= 1; - m += 12; - } - let d = tm.tm_mday as i64; - let h = tm.tm_hour as i64; - let mi = tm.tm_min as i64; - let s = tm.tm_sec as i64; - (365 * y + y / 4 - y / 100 + y / 400 + 3 * (m + 1) / 5 + 30 * m + d - 719561) * 86400 - + 3600 * h - + 60 * mi - + s -} - -pub fn time_to_local_tm(sec: i64, tm: &mut Tm) { - // FIXME: Add timezone logic - time_to_tm(sec, tm); -} - -pub fn utc_tm_to_time(tm: &Tm) -> i64 { - tm_to_time(tm) -} - -pub fn local_tm_to_time(tm: &Tm) -> i64 { - // FIXME: Add timezone logic - tm_to_time(tm) -} diff --git a/third_party/rust/chrono/src/sys/unix.rs b/third_party/rust/chrono/src/sys/unix.rs deleted file mode 100644 index 2f845e74550..00000000000 --- a/third_party/rust/chrono/src/sys/unix.rs +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// 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. - -use super::Tm; -use libc::{self, time_t}; -use std::io; -use std::mem; - -#[cfg(any(target_os = "solaris", target_os = "illumos"))] -extern "C" { - static timezone: time_t; - static altzone: time_t; -} - -#[cfg(any(target_os = "solaris", target_os = "illumos"))] -fn tzset() { - extern "C" { - fn tzset(); - } - unsafe { tzset() } -} - -fn rust_tm_to_tm(rust_tm: &Tm, tm: &mut libc::tm) { - tm.tm_sec = rust_tm.tm_sec; - tm.tm_min = rust_tm.tm_min; - tm.tm_hour = rust_tm.tm_hour; - tm.tm_mday = rust_tm.tm_mday; - tm.tm_mon = rust_tm.tm_mon; - tm.tm_year = rust_tm.tm_year; - tm.tm_wday = rust_tm.tm_wday; - tm.tm_yday = rust_tm.tm_yday; - tm.tm_isdst = rust_tm.tm_isdst; -} - -fn tm_to_rust_tm(tm: &libc::tm, utcoff: i32, rust_tm: &mut Tm) { - rust_tm.tm_sec = tm.tm_sec; - rust_tm.tm_min = tm.tm_min; - rust_tm.tm_hour = tm.tm_hour; - rust_tm.tm_mday = tm.tm_mday; - rust_tm.tm_mon = tm.tm_mon; - rust_tm.tm_year = tm.tm_year; - rust_tm.tm_wday = tm.tm_wday; - rust_tm.tm_yday = tm.tm_yday; - rust_tm.tm_isdst = tm.tm_isdst; - rust_tm.tm_utcoff = utcoff; -} - -#[cfg(any(target_os = "nacl", target_os = "solaris", target_os = "illumos"))] -unsafe fn timegm(tm: *mut libc::tm) -> time_t { - use std::env::{remove_var, set_var, var_os}; - extern "C" { - fn tzset(); - } - - let ret; - - let current_tz = var_os("TZ"); - set_var("TZ", "UTC"); - tzset(); - - ret = libc::mktime(tm); - - if let Some(tz) = current_tz { - set_var("TZ", tz); - } else { - remove_var("TZ"); - } - tzset(); - - ret -} - -pub fn time_to_local_tm(sec: i64, tm: &mut Tm) { - unsafe { - let sec = sec as time_t; - let mut out = mem::zeroed(); - if libc::localtime_r(&sec, &mut out).is_null() { - panic!("localtime_r failed: {}", io::Error::last_os_error()); - } - #[cfg(any(target_os = "solaris", target_os = "illumos"))] - let gmtoff = { - tzset(); - // < 0 means we don't know; assume we're not in DST. - if out.tm_isdst == 0 { - // timezone is seconds west of UTC, tm_gmtoff is seconds east - -timezone - } else if out.tm_isdst > 0 { - -altzone - } else { - -timezone - } - }; - #[cfg(not(any(target_os = "solaris", target_os = "illumos")))] - let gmtoff = out.tm_gmtoff; - tm_to_rust_tm(&out, gmtoff as i32, tm); - } -} - -pub fn utc_tm_to_time(rust_tm: &Tm) -> i64 { - #[cfg(not(any( - all(target_os = "android", target_pointer_width = "32"), - target_os = "nacl", - target_os = "solaris", - target_os = "illumos" - )))] - use libc::timegm; - #[cfg(all(target_os = "android", target_pointer_width = "32"))] - use libc::timegm64 as timegm; - - let mut tm = unsafe { mem::zeroed() }; - rust_tm_to_tm(rust_tm, &mut tm); - unsafe { timegm(&mut tm) as i64 } -} - -pub fn local_tm_to_time(rust_tm: &Tm) -> i64 { - let mut tm = unsafe { mem::zeroed() }; - rust_tm_to_tm(rust_tm, &mut tm); - unsafe { libc::mktime(&mut tm) as i64 } -} diff --git a/third_party/rust/chrono/src/sys/windows.rs b/third_party/rust/chrono/src/sys/windows.rs deleted file mode 100644 index 3f90338e411..00000000000 --- a/third_party/rust/chrono/src/sys/windows.rs +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// 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. - -use super::Tm; -use std::io; -use std::mem; - -use winapi::shared::minwindef::*; -use winapi::um::minwinbase::SYSTEMTIME; -use winapi::um::timezoneapi::*; - -const HECTONANOSECS_IN_SEC: i64 = 10_000_000; -const HECTONANOSEC_TO_UNIX_EPOCH: i64 = 11_644_473_600 * HECTONANOSECS_IN_SEC; - -fn time_to_file_time(sec: i64) -> FILETIME { - let t = ((sec * HECTONANOSECS_IN_SEC) + HECTONANOSEC_TO_UNIX_EPOCH) as u64; - FILETIME { dwLowDateTime: t as DWORD, dwHighDateTime: (t >> 32) as DWORD } -} - -fn file_time_as_u64(ft: &FILETIME) -> u64 { - ((ft.dwHighDateTime as u64) << 32) | (ft.dwLowDateTime as u64) -} - -fn file_time_to_unix_seconds(ft: &FILETIME) -> i64 { - let t = file_time_as_u64(ft) as i64; - ((t - HECTONANOSEC_TO_UNIX_EPOCH) / HECTONANOSECS_IN_SEC) as i64 -} - -fn system_time_to_file_time(sys: &SYSTEMTIME) -> FILETIME { - unsafe { - let mut ft = mem::zeroed(); - SystemTimeToFileTime(sys, &mut ft); - ft - } -} - -fn tm_to_system_time(tm: &Tm) -> SYSTEMTIME { - let mut sys: SYSTEMTIME = unsafe { mem::zeroed() }; - sys.wSecond = tm.tm_sec as WORD; - sys.wMinute = tm.tm_min as WORD; - sys.wHour = tm.tm_hour as WORD; - sys.wDay = tm.tm_mday as WORD; - sys.wDayOfWeek = tm.tm_wday as WORD; - sys.wMonth = (tm.tm_mon + 1) as WORD; - sys.wYear = (tm.tm_year + 1900) as WORD; - sys -} - -fn system_time_to_tm(sys: &SYSTEMTIME, tm: &mut Tm) { - tm.tm_sec = sys.wSecond as i32; - tm.tm_min = sys.wMinute as i32; - tm.tm_hour = sys.wHour as i32; - tm.tm_mday = sys.wDay as i32; - tm.tm_wday = sys.wDayOfWeek as i32; - tm.tm_mon = (sys.wMonth - 1) as i32; - tm.tm_year = (sys.wYear - 1900) as i32; - tm.tm_yday = yday(tm.tm_year, tm.tm_mon + 1, tm.tm_mday); - - fn yday(year: i32, month: i32, day: i32) -> i32 { - let leap = if month > 2 { - if year % 4 == 0 { - 1 - } else { - 2 - } - } else { - 0 - }; - let july = if month > 7 { 1 } else { 0 }; - - (month - 1) * 30 + month / 2 + (day - 1) - leap + july - } -} - -macro_rules! call { - ($name:ident($($arg:expr),*)) => { - if $name($($arg),*) == 0 { - panic!(concat!(stringify!($name), " failed with: {}"), - io::Error::last_os_error()); - } - } -} - -pub fn time_to_local_tm(sec: i64, tm: &mut Tm) { - let ft = time_to_file_time(sec); - unsafe { - let mut utc = mem::zeroed(); - let mut local = mem::zeroed(); - call!(FileTimeToSystemTime(&ft, &mut utc)); - call!(SystemTimeToTzSpecificLocalTime(0 as *const _, &mut utc, &mut local)); - system_time_to_tm(&local, tm); - - let local = system_time_to_file_time(&local); - let local_sec = file_time_to_unix_seconds(&local); - - let mut tz = mem::zeroed(); - GetTimeZoneInformation(&mut tz); - - // SystemTimeToTzSpecificLocalTime already applied the biases so - // check if it non standard - tm.tm_utcoff = (local_sec - sec) as i32; - tm.tm_isdst = if tm.tm_utcoff == -60 * (tz.Bias + tz.StandardBias) { 0 } else { 1 }; - } -} - -pub fn utc_tm_to_time(tm: &Tm) -> i64 { - unsafe { - let mut ft = mem::zeroed(); - let sys_time = tm_to_system_time(tm); - call!(SystemTimeToFileTime(&sys_time, &mut ft)); - file_time_to_unix_seconds(&ft) - } -} - -pub fn local_tm_to_time(tm: &Tm) -> i64 { - unsafe { - let mut ft = mem::zeroed(); - let mut utc = mem::zeroed(); - let mut sys_time = tm_to_system_time(tm); - call!(TzSpecificLocalTimeToSystemTime(0 as *mut _, &mut sys_time, &mut utc)); - call!(SystemTimeToFileTime(&utc, &mut ft)); - file_time_to_unix_seconds(&ft) - } -} diff --git a/third_party/rust/chrono/src/time_delta.rs b/third_party/rust/chrono/src/time_delta.rs new file mode 100644 index 00000000000..d891d8b6b2c --- /dev/null +++ b/third_party/rust/chrono/src/time_delta.rs @@ -0,0 +1,1354 @@ +// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// 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. + +//! Temporal quantification + +use core::fmt; +use core::ops::{Add, AddAssign, Div, Mul, Neg, Sub, SubAssign}; +use core::time::Duration; +#[cfg(feature = "std")] +use std::error::Error; + +use crate::{expect, try_opt}; + +#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] +use rkyv::{Archive, Deserialize, Serialize}; + +/// The number of nanoseconds in a microsecond. +const NANOS_PER_MICRO: i32 = 1000; +/// The number of nanoseconds in a millisecond. +const NANOS_PER_MILLI: i32 = 1_000_000; +/// The number of nanoseconds in seconds. +pub(crate) const NANOS_PER_SEC: i32 = 1_000_000_000; +/// The number of microseconds per second. +const MICROS_PER_SEC: i64 = 1_000_000; +/// The number of milliseconds per second. +const MILLIS_PER_SEC: i64 = 1000; +/// The number of seconds in a minute. +const SECS_PER_MINUTE: i64 = 60; +/// The number of seconds in an hour. +const SECS_PER_HOUR: i64 = 3600; +/// The number of (non-leap) seconds in days. +const SECS_PER_DAY: i64 = 86_400; +/// The number of (non-leap) seconds in a week. +const SECS_PER_WEEK: i64 = 604_800; + +/// Time duration with nanosecond precision. +/// +/// This also allows for negative durations; see individual methods for details. +/// +/// A `TimeDelta` is represented internally as a complement of seconds and +/// nanoseconds. The range is restricted to that of `i64` milliseconds, with the +/// minimum value notably being set to `-i64::MAX` rather than allowing the full +/// range of `i64::MIN`. This is to allow easy flipping of sign, so that for +/// instance `abs()` can be called without any checks. +#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] +#[cfg_attr( + any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), + derive(Archive, Deserialize, Serialize), + archive(compare(PartialEq, PartialOrd)), + archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) +)] +#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] +pub struct TimeDelta { + secs: i64, + nanos: i32, // Always 0 <= nanos < NANOS_PER_SEC +} + +/// The minimum possible `TimeDelta`: `-i64::MAX` milliseconds. +pub(crate) const MIN: TimeDelta = TimeDelta { + secs: -i64::MAX / MILLIS_PER_SEC - 1, + nanos: NANOS_PER_SEC + (-i64::MAX % MILLIS_PER_SEC) as i32 * NANOS_PER_MILLI, +}; + +/// The maximum possible `TimeDelta`: `i64::MAX` milliseconds. +pub(crate) const MAX: TimeDelta = TimeDelta { + secs: i64::MAX / MILLIS_PER_SEC, + nanos: (i64::MAX % MILLIS_PER_SEC) as i32 * NANOS_PER_MILLI, +}; + +impl TimeDelta { + /// Makes a new `TimeDelta` with given number of seconds and nanoseconds. + /// + /// # Errors + /// + /// Returns `None` when the duration is out of bounds, or if `nanos` ≥ 1,000,000,000. + pub const fn new(secs: i64, nanos: u32) -> Option { + if secs < MIN.secs + || secs > MAX.secs + || nanos >= 1_000_000_000 + || (secs == MAX.secs && nanos > MAX.nanos as u32) + || (secs == MIN.secs && nanos < MIN.nanos as u32) + { + return None; + } + Some(TimeDelta { secs, nanos: nanos as i32 }) + } + + /// Makes a new `TimeDelta` with the given number of weeks. + /// + /// Equivalent to `TimeDelta::seconds(weeks * 7 * 24 * 60 * 60)` with + /// overflow checks. + /// + /// # Panics + /// + /// Panics when the duration is out of bounds. + #[inline] + #[must_use] + pub const fn weeks(weeks: i64) -> TimeDelta { + expect(TimeDelta::try_weeks(weeks), "TimeDelta::weeks out of bounds") + } + + /// Makes a new `TimeDelta` with the given number of weeks. + /// + /// Equivalent to `TimeDelta::try_seconds(weeks * 7 * 24 * 60 * 60)` with + /// overflow checks. + /// + /// # Errors + /// + /// Returns `None` when the `TimeDelta` would be out of bounds. + #[inline] + pub const fn try_weeks(weeks: i64) -> Option { + TimeDelta::try_seconds(try_opt!(weeks.checked_mul(SECS_PER_WEEK))) + } + + /// Makes a new `TimeDelta` with the given number of days. + /// + /// Equivalent to `TimeDelta::seconds(days * 24 * 60 * 60)` with overflow + /// checks. + /// + /// # Panics + /// + /// Panics when the `TimeDelta` would be out of bounds. + #[inline] + #[must_use] + pub const fn days(days: i64) -> TimeDelta { + expect(TimeDelta::try_days(days), "TimeDelta::days out of bounds") + } + + /// Makes a new `TimeDelta` with the given number of days. + /// + /// Equivalent to `TimeDelta::try_seconds(days * 24 * 60 * 60)` with overflow + /// checks. + /// + /// # Errors + /// + /// Returns `None` when the `TimeDelta` would be out of bounds. + #[inline] + pub const fn try_days(days: i64) -> Option { + TimeDelta::try_seconds(try_opt!(days.checked_mul(SECS_PER_DAY))) + } + + /// Makes a new `TimeDelta` with the given number of hours. + /// + /// Equivalent to `TimeDelta::seconds(hours * 60 * 60)` with overflow checks. + /// + /// # Panics + /// + /// Panics when the `TimeDelta` would be out of bounds. + #[inline] + #[must_use] + pub const fn hours(hours: i64) -> TimeDelta { + expect(TimeDelta::try_hours(hours), "TimeDelta::hours out of bounds") + } + + /// Makes a new `TimeDelta` with the given number of hours. + /// + /// Equivalent to `TimeDelta::try_seconds(hours * 60 * 60)` with overflow checks. + /// + /// # Errors + /// + /// Returns `None` when the `TimeDelta` would be out of bounds. + #[inline] + pub const fn try_hours(hours: i64) -> Option { + TimeDelta::try_seconds(try_opt!(hours.checked_mul(SECS_PER_HOUR))) + } + + /// Makes a new `TimeDelta` with the given number of minutes. + /// + /// Equivalent to `TimeDelta::seconds(minutes * 60)` with overflow checks. + /// + /// # Panics + /// + /// Panics when the `TimeDelta` would be out of bounds. + #[inline] + #[must_use] + pub const fn minutes(minutes: i64) -> TimeDelta { + expect(TimeDelta::try_minutes(minutes), "TimeDelta::minutes out of bounds") + } + + /// Makes a new `TimeDelta` with the given number of minutes. + /// + /// Equivalent to `TimeDelta::try_seconds(minutes * 60)` with overflow checks. + /// + /// # Errors + /// + /// Returns `None` when the `TimeDelta` would be out of bounds. + #[inline] + pub const fn try_minutes(minutes: i64) -> Option { + TimeDelta::try_seconds(try_opt!(minutes.checked_mul(SECS_PER_MINUTE))) + } + + /// Makes a new `TimeDelta` with the given number of seconds. + /// + /// # Panics + /// + /// Panics when `seconds` is more than `i64::MAX / 1_000` or less than `-i64::MAX / 1_000` + /// (in this context, this is the same as `i64::MIN / 1_000` due to rounding). + #[inline] + #[must_use] + pub const fn seconds(seconds: i64) -> TimeDelta { + expect(TimeDelta::try_seconds(seconds), "TimeDelta::seconds out of bounds") + } + + /// Makes a new `TimeDelta` with the given number of seconds. + /// + /// # Errors + /// + /// Returns `None` when `seconds` is more than `i64::MAX / 1_000` or less than + /// `-i64::MAX / 1_000` (in this context, this is the same as `i64::MIN / 1_000` due to + /// rounding). + #[inline] + pub const fn try_seconds(seconds: i64) -> Option { + TimeDelta::new(seconds, 0) + } + + /// Makes a new `TimeDelta` with the given number of milliseconds. + /// + /// # Panics + /// + /// Panics when the `TimeDelta` would be out of bounds, i.e. when `milliseconds` is more than + /// `i64::MAX` or less than `-i64::MAX`. Notably, this is not the same as `i64::MIN`. + #[inline] + pub const fn milliseconds(milliseconds: i64) -> TimeDelta { + expect(TimeDelta::try_milliseconds(milliseconds), "TimeDelta::milliseconds out of bounds") + } + + /// Makes a new `TimeDelta` with the given number of milliseconds. + /// + /// # Errors + /// + /// Returns `None` the `TimeDelta` would be out of bounds, i.e. when `milliseconds` is more + /// than `i64::MAX` or less than `-i64::MAX`. Notably, this is not the same as `i64::MIN`. + #[inline] + pub const fn try_milliseconds(milliseconds: i64) -> Option { + // We don't need to compare against MAX, as this function accepts an + // i64, and MAX is aligned to i64::MAX milliseconds. + if milliseconds < -i64::MAX { + return None; + } + let (secs, millis) = div_mod_floor_64(milliseconds, MILLIS_PER_SEC); + let d = TimeDelta { secs, nanos: millis as i32 * NANOS_PER_MILLI }; + Some(d) + } + + /// Makes a new `TimeDelta` with the given number of microseconds. + /// + /// The number of microseconds acceptable by this constructor is less than + /// the total number that can actually be stored in a `TimeDelta`, so it is + /// not possible to specify a value that would be out of bounds. This + /// function is therefore infallible. + #[inline] + pub const fn microseconds(microseconds: i64) -> TimeDelta { + let (secs, micros) = div_mod_floor_64(microseconds, MICROS_PER_SEC); + let nanos = micros as i32 * NANOS_PER_MICRO; + TimeDelta { secs, nanos } + } + + /// Makes a new `TimeDelta` with the given number of nanoseconds. + /// + /// The number of nanoseconds acceptable by this constructor is less than + /// the total number that can actually be stored in a `TimeDelta`, so it is + /// not possible to specify a value that would be out of bounds. This + /// function is therefore infallible. + #[inline] + pub const fn nanoseconds(nanos: i64) -> TimeDelta { + let (secs, nanos) = div_mod_floor_64(nanos, NANOS_PER_SEC as i64); + TimeDelta { secs, nanos: nanos as i32 } + } + + /// Returns the total number of whole weeks in the `TimeDelta`. + #[inline] + pub const fn num_weeks(&self) -> i64 { + self.num_days() / 7 + } + + /// Returns the total number of whole days in the `TimeDelta`. + #[inline] + pub const fn num_days(&self) -> i64 { + self.num_seconds() / SECS_PER_DAY + } + + /// Returns the total number of whole hours in the `TimeDelta`. + #[inline] + pub const fn num_hours(&self) -> i64 { + self.num_seconds() / SECS_PER_HOUR + } + + /// Returns the total number of whole minutes in the `TimeDelta`. + #[inline] + pub const fn num_minutes(&self) -> i64 { + self.num_seconds() / SECS_PER_MINUTE + } + + /// Returns the total number of whole seconds in the `TimeDelta`. + pub const fn num_seconds(&self) -> i64 { + // If secs is negative, nanos should be subtracted from the duration. + if self.secs < 0 && self.nanos > 0 { self.secs + 1 } else { self.secs } + } + + /// Returns the number of nanoseconds such that + /// `subsec_nanos() + num_seconds() * NANOS_PER_SEC` is the total number of + /// nanoseconds in the `TimeDelta`. + pub const fn subsec_nanos(&self) -> i32 { + if self.secs < 0 && self.nanos > 0 { self.nanos - NANOS_PER_SEC } else { self.nanos } + } + + /// Returns the total number of whole milliseconds in the `TimeDelta`. + pub const fn num_milliseconds(&self) -> i64 { + // A proper TimeDelta will not overflow, because MIN and MAX are defined such + // that the range is within the bounds of an i64, from -i64::MAX through to + // +i64::MAX inclusive. Notably, i64::MIN is excluded from this range. + let secs_part = self.num_seconds() * MILLIS_PER_SEC; + let nanos_part = self.subsec_nanos() / NANOS_PER_MILLI; + secs_part + nanos_part as i64 + } + + /// Returns the total number of whole microseconds in the `TimeDelta`, + /// or `None` on overflow (exceeding 2^63 microseconds in either direction). + pub const fn num_microseconds(&self) -> Option { + let secs_part = try_opt!(self.num_seconds().checked_mul(MICROS_PER_SEC)); + let nanos_part = self.subsec_nanos() / NANOS_PER_MICRO; + secs_part.checked_add(nanos_part as i64) + } + + /// Returns the total number of whole nanoseconds in the `TimeDelta`, + /// or `None` on overflow (exceeding 2^63 nanoseconds in either direction). + pub const fn num_nanoseconds(&self) -> Option { + let secs_part = try_opt!(self.num_seconds().checked_mul(NANOS_PER_SEC as i64)); + let nanos_part = self.subsec_nanos(); + secs_part.checked_add(nanos_part as i64) + } + + /// Add two `TimeDelta`s, returning `None` if overflow occurred. + #[must_use] + pub const fn checked_add(&self, rhs: &TimeDelta) -> Option { + // No overflow checks here because we stay comfortably within the range of an `i64`. + // Range checks happen in `TimeDelta::new`. + let mut secs = self.secs + rhs.secs; + let mut nanos = self.nanos + rhs.nanos; + if nanos >= NANOS_PER_SEC { + nanos -= NANOS_PER_SEC; + secs += 1; + } + TimeDelta::new(secs, nanos as u32) + } + + /// Subtract two `TimeDelta`s, returning `None` if overflow occurred. + #[must_use] + pub const fn checked_sub(&self, rhs: &TimeDelta) -> Option { + // No overflow checks here because we stay comfortably within the range of an `i64`. + // Range checks happen in `TimeDelta::new`. + let mut secs = self.secs - rhs.secs; + let mut nanos = self.nanos - rhs.nanos; + if nanos < 0 { + nanos += NANOS_PER_SEC; + secs -= 1; + } + TimeDelta::new(secs, nanos as u32) + } + + /// Multiply a `TimeDelta` with a i32, returning `None` if overflow occurred. + #[must_use] + pub const fn checked_mul(&self, rhs: i32) -> Option { + // Multiply nanoseconds as i64, because it cannot overflow that way. + let total_nanos = self.nanos as i64 * rhs as i64; + let (extra_secs, nanos) = div_mod_floor_64(total_nanos, NANOS_PER_SEC as i64); + // Multiply seconds as i128 to prevent overflow + let secs: i128 = self.secs as i128 * rhs as i128 + extra_secs as i128; + if secs <= i64::MIN as i128 || secs >= i64::MAX as i128 { + return None; + }; + Some(TimeDelta { secs: secs as i64, nanos: nanos as i32 }) + } + + /// Divide a `TimeDelta` with a i32, returning `None` if dividing by 0. + #[must_use] + pub const fn checked_div(&self, rhs: i32) -> Option { + if rhs == 0 { + return None; + } + let secs = self.secs / rhs as i64; + let carry = self.secs % rhs as i64; + let extra_nanos = carry * NANOS_PER_SEC as i64 / rhs as i64; + let nanos = self.nanos / rhs + extra_nanos as i32; + + let (secs, nanos) = match nanos { + i32::MIN..=-1 => (secs - 1, nanos + NANOS_PER_SEC), + NANOS_PER_SEC..=i32::MAX => (secs + 1, nanos - NANOS_PER_SEC), + _ => (secs, nanos), + }; + + Some(TimeDelta { secs, nanos }) + } + + /// Returns the `TimeDelta` as an absolute (non-negative) value. + #[inline] + pub const fn abs(&self) -> TimeDelta { + if self.secs < 0 && self.nanos != 0 { + TimeDelta { secs: (self.secs + 1).abs(), nanos: NANOS_PER_SEC - self.nanos } + } else { + TimeDelta { secs: self.secs.abs(), nanos: self.nanos } + } + } + + /// The minimum possible `TimeDelta`: `-i64::MAX` milliseconds. + #[deprecated(since = "0.4.39", note = "Use `TimeDelta::MIN` instead")] + #[inline] + pub const fn min_value() -> TimeDelta { + MIN + } + + /// The maximum possible `TimeDelta`: `i64::MAX` milliseconds. + #[deprecated(since = "0.4.39", note = "Use `TimeDelta::MAX` instead")] + #[inline] + pub const fn max_value() -> TimeDelta { + MAX + } + + /// A `TimeDelta` where the stored seconds and nanoseconds are equal to zero. + #[inline] + pub const fn zero() -> TimeDelta { + TimeDelta { secs: 0, nanos: 0 } + } + + /// Returns `true` if the `TimeDelta` equals `TimeDelta::zero()`. + #[inline] + pub const fn is_zero(&self) -> bool { + self.secs == 0 && self.nanos == 0 + } + + /// Creates a `TimeDelta` object from `std::time::Duration` + /// + /// This function errors when original duration is larger than the maximum + /// value supported for this type. + pub const fn from_std(duration: Duration) -> Result { + // We need to check secs as u64 before coercing to i64 + if duration.as_secs() > MAX.secs as u64 { + return Err(OutOfRangeError(())); + } + match TimeDelta::new(duration.as_secs() as i64, duration.subsec_nanos()) { + Some(d) => Ok(d), + None => Err(OutOfRangeError(())), + } + } + + /// Creates a `std::time::Duration` object from a `TimeDelta`. + /// + /// This function errors when duration is less than zero. As standard + /// library implementation is limited to non-negative values. + pub const fn to_std(&self) -> Result { + if self.secs < 0 { + return Err(OutOfRangeError(())); + } + Ok(Duration::new(self.secs as u64, self.nanos as u32)) + } + + /// This duplicates `Neg::neg` because trait methods can't be const yet. + pub(crate) const fn neg(self) -> TimeDelta { + let (secs_diff, nanos) = match self.nanos { + 0 => (0, 0), + nanos => (1, NANOS_PER_SEC - nanos), + }; + TimeDelta { secs: -self.secs - secs_diff, nanos } + } + + /// The minimum possible `TimeDelta`: `-i64::MAX` milliseconds. + pub const MIN: Self = MIN; + + /// The maximum possible `TimeDelta`: `i64::MAX` milliseconds. + pub const MAX: Self = MAX; +} + +impl Neg for TimeDelta { + type Output = TimeDelta; + + #[inline] + fn neg(self) -> TimeDelta { + let (secs_diff, nanos) = match self.nanos { + 0 => (0, 0), + nanos => (1, NANOS_PER_SEC - nanos), + }; + TimeDelta { secs: -self.secs - secs_diff, nanos } + } +} + +impl Add for TimeDelta { + type Output = TimeDelta; + + fn add(self, rhs: TimeDelta) -> TimeDelta { + self.checked_add(&rhs).expect("`TimeDelta + TimeDelta` overflowed") + } +} + +impl Sub for TimeDelta { + type Output = TimeDelta; + + fn sub(self, rhs: TimeDelta) -> TimeDelta { + self.checked_sub(&rhs).expect("`TimeDelta - TimeDelta` overflowed") + } +} + +impl AddAssign for TimeDelta { + fn add_assign(&mut self, rhs: TimeDelta) { + let new = self.checked_add(&rhs).expect("`TimeDelta + TimeDelta` overflowed"); + *self = new; + } +} + +impl SubAssign for TimeDelta { + fn sub_assign(&mut self, rhs: TimeDelta) { + let new = self.checked_sub(&rhs).expect("`TimeDelta - TimeDelta` overflowed"); + *self = new; + } +} + +impl Mul for TimeDelta { + type Output = TimeDelta; + + fn mul(self, rhs: i32) -> TimeDelta { + self.checked_mul(rhs).expect("`TimeDelta * i32` overflowed") + } +} + +impl Div for TimeDelta { + type Output = TimeDelta; + + fn div(self, rhs: i32) -> TimeDelta { + self.checked_div(rhs).expect("`i32` is zero") + } +} + +impl<'a> core::iter::Sum<&'a TimeDelta> for TimeDelta { + fn sum>(iter: I) -> TimeDelta { + iter.fold(TimeDelta::zero(), |acc, x| acc + *x) + } +} + +impl core::iter::Sum for TimeDelta { + fn sum>(iter: I) -> TimeDelta { + iter.fold(TimeDelta::zero(), |acc, x| acc + x) + } +} + +impl fmt::Display for TimeDelta { + /// Format a `TimeDelta` using the [ISO 8601] format + /// + /// [ISO 8601]: https://en.wikipedia.org/wiki/ISO_8601#Durations + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // technically speaking, negative duration is not valid ISO 8601, + // but we need to print it anyway. + let (abs, sign) = if self.secs < 0 { (-*self, "-") } else { (*self, "") }; + + write!(f, "{}P", sign)?; + // Plenty of ways to encode an empty string. `P0D` is short and not too strange. + if abs.secs == 0 && abs.nanos == 0 { + return f.write_str("0D"); + } + + f.write_fmt(format_args!("T{}", abs.secs))?; + + if abs.nanos > 0 { + // Count the number of significant digits, while removing all trailing zero's. + let mut figures = 9usize; + let mut fraction_digits = abs.nanos; + loop { + let div = fraction_digits / 10; + let last_digit = fraction_digits % 10; + if last_digit != 0 { + break; + } + fraction_digits = div; + figures -= 1; + } + f.write_fmt(format_args!(".{:01$}", fraction_digits, figures))?; + } + f.write_str("S")?; + Ok(()) + } +} + +/// Represents error when converting `TimeDelta` to/from a standard library +/// implementation +/// +/// The `std::time::Duration` supports a range from zero to `u64::MAX` +/// *seconds*, while this module supports signed range of up to +/// `i64::MAX` of *milliseconds*. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct OutOfRangeError(()); + +impl fmt::Display for OutOfRangeError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Source duration value is out of range for the target type") + } +} + +#[cfg(feature = "std")] +impl Error for OutOfRangeError { + #[allow(deprecated)] + fn description(&self) -> &str { + "out of range error" + } +} + +#[inline] +const fn div_mod_floor_64(this: i64, other: i64) -> (i64, i64) { + (this.div_euclid(other), this.rem_euclid(other)) +} + +#[cfg(all(feature = "arbitrary", feature = "std"))] +impl arbitrary::Arbitrary<'_> for TimeDelta { + fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result { + const MIN_SECS: i64 = -i64::MAX / MILLIS_PER_SEC - 1; + const MAX_SECS: i64 = i64::MAX / MILLIS_PER_SEC; + + let secs: i64 = u.int_in_range(MIN_SECS..=MAX_SECS)?; + let nanos: i32 = u.int_in_range(0..=(NANOS_PER_SEC - 1))?; + let duration = TimeDelta { secs, nanos }; + + if duration < MIN || duration > MAX { + Err(arbitrary::Error::IncorrectFormat) + } else { + Ok(duration) + } + } +} + +#[cfg(feature = "serde")] +mod serde { + use super::TimeDelta; + use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error}; + + impl Serialize for TimeDelta { + fn serialize(&self, serializer: S) -> Result { + <(i64, i32) as Serialize>::serialize(&(self.secs, self.nanos), serializer) + } + } + + impl<'de> Deserialize<'de> for TimeDelta { + fn deserialize>(deserializer: D) -> Result { + let (secs, nanos) = <(i64, i32) as Deserialize>::deserialize(deserializer)?; + TimeDelta::new(secs, nanos as u32).ok_or(Error::custom("TimeDelta out of bounds")) + } + } + + #[cfg(test)] + mod tests { + use super::{super::MAX, TimeDelta}; + + #[test] + fn test_serde() { + let duration = TimeDelta::new(123, 456).unwrap(); + assert_eq!( + serde_json::from_value::(serde_json::to_value(duration).unwrap()) + .unwrap(), + duration + ); + } + + #[test] + #[should_panic(expected = "TimeDelta out of bounds")] + fn test_serde_oob_panic() { + let _ = + serde_json::from_value::(serde_json::json!([MAX.secs + 1, 0])).unwrap(); + } + } +} + +#[cfg(test)] +mod tests { + use super::OutOfRangeError; + use super::{MAX, MIN, TimeDelta}; + use crate::expect; + use core::time::Duration; + + #[test] + fn test_duration() { + let days = |d| TimeDelta::try_days(d).unwrap(); + let seconds = |s| TimeDelta::try_seconds(s).unwrap(); + + assert!(seconds(1) != TimeDelta::zero()); + assert_eq!(seconds(1) + seconds(2), seconds(3)); + assert_eq!(seconds(86_399) + seconds(4), days(1) + seconds(3)); + assert_eq!(days(10) - seconds(1000), seconds(863_000)); + assert_eq!(days(10) - seconds(1_000_000), seconds(-136_000)); + assert_eq!( + days(2) + seconds(86_399) + TimeDelta::nanoseconds(1_234_567_890), + days(3) + TimeDelta::nanoseconds(234_567_890) + ); + assert_eq!(-days(3), days(-3)); + assert_eq!(-(days(3) + seconds(70)), days(-4) + seconds(86_400 - 70)); + + let mut d = TimeDelta::default(); + d += TimeDelta::try_minutes(1).unwrap(); + d -= seconds(30); + assert_eq!(d, seconds(30)); + } + + #[test] + fn test_duration_num_days() { + assert_eq!(TimeDelta::zero().num_days(), 0); + assert_eq!(TimeDelta::try_days(1).unwrap().num_days(), 1); + assert_eq!(TimeDelta::try_days(-1).unwrap().num_days(), -1); + assert_eq!(TimeDelta::try_seconds(86_399).unwrap().num_days(), 0); + assert_eq!(TimeDelta::try_seconds(86_401).unwrap().num_days(), 1); + assert_eq!(TimeDelta::try_seconds(-86_399).unwrap().num_days(), 0); + assert_eq!(TimeDelta::try_seconds(-86_401).unwrap().num_days(), -1); + assert_eq!(TimeDelta::try_days(i32::MAX as i64).unwrap().num_days(), i32::MAX as i64); + assert_eq!(TimeDelta::try_days(i32::MIN as i64).unwrap().num_days(), i32::MIN as i64); + } + + #[test] + fn test_duration_num_seconds() { + assert_eq!(TimeDelta::zero().num_seconds(), 0); + assert_eq!(TimeDelta::try_seconds(1).unwrap().num_seconds(), 1); + assert_eq!(TimeDelta::try_seconds(-1).unwrap().num_seconds(), -1); + assert_eq!(TimeDelta::try_milliseconds(999).unwrap().num_seconds(), 0); + assert_eq!(TimeDelta::try_milliseconds(1001).unwrap().num_seconds(), 1); + assert_eq!(TimeDelta::try_milliseconds(-999).unwrap().num_seconds(), 0); + assert_eq!(TimeDelta::try_milliseconds(-1001).unwrap().num_seconds(), -1); + } + + #[test] + fn test_duration_seconds_max_allowed() { + let duration = TimeDelta::try_seconds(i64::MAX / 1_000).unwrap(); + assert_eq!(duration.num_seconds(), i64::MAX / 1_000); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + i64::MAX as i128 / 1_000 * 1_000_000_000 + ); + } + + #[test] + fn test_duration_seconds_max_overflow() { + assert!(TimeDelta::try_seconds(i64::MAX / 1_000 + 1).is_none()); + } + + #[test] + #[should_panic(expected = "TimeDelta::seconds out of bounds")] + fn test_duration_seconds_max_overflow_panic() { + let _ = TimeDelta::seconds(i64::MAX / 1_000 + 1); + } + + #[test] + fn test_duration_seconds_min_allowed() { + let duration = TimeDelta::try_seconds(i64::MIN / 1_000).unwrap(); // Same as -i64::MAX / 1_000 due to rounding + assert_eq!(duration.num_seconds(), i64::MIN / 1_000); // Same as -i64::MAX / 1_000 due to rounding + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + -i64::MAX as i128 / 1_000 * 1_000_000_000 + ); + } + + #[test] + fn test_duration_seconds_min_underflow() { + assert!(TimeDelta::try_seconds(-i64::MAX / 1_000 - 1).is_none()); + } + + #[test] + #[should_panic(expected = "TimeDelta::seconds out of bounds")] + fn test_duration_seconds_min_underflow_panic() { + let _ = TimeDelta::seconds(-i64::MAX / 1_000 - 1); + } + + #[test] + fn test_duration_num_milliseconds() { + assert_eq!(TimeDelta::zero().num_milliseconds(), 0); + assert_eq!(TimeDelta::try_milliseconds(1).unwrap().num_milliseconds(), 1); + assert_eq!(TimeDelta::try_milliseconds(-1).unwrap().num_milliseconds(), -1); + assert_eq!(TimeDelta::microseconds(999).num_milliseconds(), 0); + assert_eq!(TimeDelta::microseconds(1001).num_milliseconds(), 1); + assert_eq!(TimeDelta::microseconds(-999).num_milliseconds(), 0); + assert_eq!(TimeDelta::microseconds(-1001).num_milliseconds(), -1); + } + + #[test] + fn test_duration_milliseconds_max_allowed() { + // The maximum number of milliseconds acceptable through the constructor is + // equal to the number that can be stored in a TimeDelta. + let duration = TimeDelta::try_milliseconds(i64::MAX).unwrap(); + assert_eq!(duration.num_milliseconds(), i64::MAX); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + i64::MAX as i128 * 1_000_000 + ); + } + + #[test] + fn test_duration_milliseconds_max_overflow() { + // Here we ensure that trying to add one millisecond to the maximum storable + // value will fail. + assert!( + TimeDelta::try_milliseconds(i64::MAX) + .unwrap() + .checked_add(&TimeDelta::try_milliseconds(1).unwrap()) + .is_none() + ); + } + + #[test] + fn test_duration_milliseconds_min_allowed() { + // The minimum number of milliseconds acceptable through the constructor is + // not equal to the number that can be stored in a TimeDelta - there is a + // difference of one (i64::MIN vs -i64::MAX). + let duration = TimeDelta::try_milliseconds(-i64::MAX).unwrap(); + assert_eq!(duration.num_milliseconds(), -i64::MAX); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + -i64::MAX as i128 * 1_000_000 + ); + } + + #[test] + fn test_duration_milliseconds_min_underflow() { + // Here we ensure that trying to subtract one millisecond from the minimum + // storable value will fail. + assert!( + TimeDelta::try_milliseconds(-i64::MAX) + .unwrap() + .checked_sub(&TimeDelta::try_milliseconds(1).unwrap()) + .is_none() + ); + } + + #[test] + #[should_panic(expected = "TimeDelta::milliseconds out of bounds")] + fn test_duration_milliseconds_min_underflow_panic() { + // Here we ensure that trying to create a value one millisecond below the + // minimum storable value will fail. This test is necessary because the + // storable range is -i64::MAX, but the constructor type of i64 will allow + // i64::MIN, which is one value below. + let _ = TimeDelta::milliseconds(i64::MIN); // Same as -i64::MAX - 1 + } + + #[test] + fn test_duration_num_microseconds() { + assert_eq!(TimeDelta::zero().num_microseconds(), Some(0)); + assert_eq!(TimeDelta::microseconds(1).num_microseconds(), Some(1)); + assert_eq!(TimeDelta::microseconds(-1).num_microseconds(), Some(-1)); + assert_eq!(TimeDelta::nanoseconds(999).num_microseconds(), Some(0)); + assert_eq!(TimeDelta::nanoseconds(1001).num_microseconds(), Some(1)); + assert_eq!(TimeDelta::nanoseconds(-999).num_microseconds(), Some(0)); + assert_eq!(TimeDelta::nanoseconds(-1001).num_microseconds(), Some(-1)); + + // overflow checks + const MICROS_PER_DAY: i64 = 86_400_000_000; + assert_eq!( + TimeDelta::try_days(i64::MAX / MICROS_PER_DAY).unwrap().num_microseconds(), + Some(i64::MAX / MICROS_PER_DAY * MICROS_PER_DAY) + ); + assert_eq!( + TimeDelta::try_days(-i64::MAX / MICROS_PER_DAY).unwrap().num_microseconds(), + Some(-i64::MAX / MICROS_PER_DAY * MICROS_PER_DAY) + ); + assert_eq!( + TimeDelta::try_days(i64::MAX / MICROS_PER_DAY + 1).unwrap().num_microseconds(), + None + ); + assert_eq!( + TimeDelta::try_days(-i64::MAX / MICROS_PER_DAY - 1).unwrap().num_microseconds(), + None + ); + } + #[test] + fn test_duration_microseconds_max_allowed() { + // The number of microseconds acceptable through the constructor is far + // fewer than the number that can actually be stored in a TimeDelta, so this + // is not a particular insightful test. + let duration = TimeDelta::microseconds(i64::MAX); + assert_eq!(duration.num_microseconds(), Some(i64::MAX)); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + i64::MAX as i128 * 1_000 + ); + // Here we create a TimeDelta with the maximum possible number of + // microseconds by creating a TimeDelta with the maximum number of + // milliseconds and then checking that the number of microseconds matches + // the storage limit. + let duration = TimeDelta::try_milliseconds(i64::MAX).unwrap(); + assert!(duration.num_microseconds().is_none()); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + i64::MAX as i128 * 1_000_000 + ); + } + #[test] + fn test_duration_microseconds_max_overflow() { + // This test establishes that a TimeDelta can store more microseconds than + // are representable through the return of duration.num_microseconds(). + let duration = TimeDelta::microseconds(i64::MAX) + TimeDelta::microseconds(1); + assert!(duration.num_microseconds().is_none()); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + (i64::MAX as i128 + 1) * 1_000 + ); + // Here we ensure that trying to add one microsecond to the maximum storable + // value will fail. + assert!( + TimeDelta::try_milliseconds(i64::MAX) + .unwrap() + .checked_add(&TimeDelta::microseconds(1)) + .is_none() + ); + } + #[test] + fn test_duration_microseconds_min_allowed() { + // The number of microseconds acceptable through the constructor is far + // fewer than the number that can actually be stored in a TimeDelta, so this + // is not a particular insightful test. + let duration = TimeDelta::microseconds(i64::MIN); + assert_eq!(duration.num_microseconds(), Some(i64::MIN)); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + i64::MIN as i128 * 1_000 + ); + // Here we create a TimeDelta with the minimum possible number of + // microseconds by creating a TimeDelta with the minimum number of + // milliseconds and then checking that the number of microseconds matches + // the storage limit. + let duration = TimeDelta::try_milliseconds(-i64::MAX).unwrap(); + assert!(duration.num_microseconds().is_none()); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + -i64::MAX as i128 * 1_000_000 + ); + } + #[test] + fn test_duration_microseconds_min_underflow() { + // This test establishes that a TimeDelta can store more microseconds than + // are representable through the return of duration.num_microseconds(). + let duration = TimeDelta::microseconds(i64::MIN) - TimeDelta::microseconds(1); + assert!(duration.num_microseconds().is_none()); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + (i64::MIN as i128 - 1) * 1_000 + ); + // Here we ensure that trying to subtract one microsecond from the minimum + // storable value will fail. + assert!( + TimeDelta::try_milliseconds(-i64::MAX) + .unwrap() + .checked_sub(&TimeDelta::microseconds(1)) + .is_none() + ); + } + + #[test] + fn test_duration_num_nanoseconds() { + assert_eq!(TimeDelta::zero().num_nanoseconds(), Some(0)); + assert_eq!(TimeDelta::nanoseconds(1).num_nanoseconds(), Some(1)); + assert_eq!(TimeDelta::nanoseconds(-1).num_nanoseconds(), Some(-1)); + + // overflow checks + const NANOS_PER_DAY: i64 = 86_400_000_000_000; + assert_eq!( + TimeDelta::try_days(i64::MAX / NANOS_PER_DAY).unwrap().num_nanoseconds(), + Some(i64::MAX / NANOS_PER_DAY * NANOS_PER_DAY) + ); + assert_eq!( + TimeDelta::try_days(-i64::MAX / NANOS_PER_DAY).unwrap().num_nanoseconds(), + Some(-i64::MAX / NANOS_PER_DAY * NANOS_PER_DAY) + ); + assert_eq!( + TimeDelta::try_days(i64::MAX / NANOS_PER_DAY + 1).unwrap().num_nanoseconds(), + None + ); + assert_eq!( + TimeDelta::try_days(-i64::MAX / NANOS_PER_DAY - 1).unwrap().num_nanoseconds(), + None + ); + } + #[test] + fn test_duration_nanoseconds_max_allowed() { + // The number of nanoseconds acceptable through the constructor is far fewer + // than the number that can actually be stored in a TimeDelta, so this is not + // a particular insightful test. + let duration = TimeDelta::nanoseconds(i64::MAX); + assert_eq!(duration.num_nanoseconds(), Some(i64::MAX)); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + i64::MAX as i128 + ); + // Here we create a TimeDelta with the maximum possible number of nanoseconds + // by creating a TimeDelta with the maximum number of milliseconds and then + // checking that the number of nanoseconds matches the storage limit. + let duration = TimeDelta::try_milliseconds(i64::MAX).unwrap(); + assert!(duration.num_nanoseconds().is_none()); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + i64::MAX as i128 * 1_000_000 + ); + } + + #[test] + fn test_duration_nanoseconds_max_overflow() { + // This test establishes that a TimeDelta can store more nanoseconds than are + // representable through the return of duration.num_nanoseconds(). + let duration = TimeDelta::nanoseconds(i64::MAX) + TimeDelta::nanoseconds(1); + assert!(duration.num_nanoseconds().is_none()); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + i64::MAX as i128 + 1 + ); + // Here we ensure that trying to add one nanosecond to the maximum storable + // value will fail. + assert!( + TimeDelta::try_milliseconds(i64::MAX) + .unwrap() + .checked_add(&TimeDelta::nanoseconds(1)) + .is_none() + ); + } + + #[test] + fn test_duration_nanoseconds_min_allowed() { + // The number of nanoseconds acceptable through the constructor is far fewer + // than the number that can actually be stored in a TimeDelta, so this is not + // a particular insightful test. + let duration = TimeDelta::nanoseconds(i64::MIN); + assert_eq!(duration.num_nanoseconds(), Some(i64::MIN)); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + i64::MIN as i128 + ); + // Here we create a TimeDelta with the minimum possible number of nanoseconds + // by creating a TimeDelta with the minimum number of milliseconds and then + // checking that the number of nanoseconds matches the storage limit. + let duration = TimeDelta::try_milliseconds(-i64::MAX).unwrap(); + assert!(duration.num_nanoseconds().is_none()); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + -i64::MAX as i128 * 1_000_000 + ); + } + + #[test] + fn test_duration_nanoseconds_min_underflow() { + // This test establishes that a TimeDelta can store more nanoseconds than are + // representable through the return of duration.num_nanoseconds(). + let duration = TimeDelta::nanoseconds(i64::MIN) - TimeDelta::nanoseconds(1); + assert!(duration.num_nanoseconds().is_none()); + assert_eq!( + duration.secs as i128 * 1_000_000_000 + duration.nanos as i128, + i64::MIN as i128 - 1 + ); + // Here we ensure that trying to subtract one nanosecond from the minimum + // storable value will fail. + assert!( + TimeDelta::try_milliseconds(-i64::MAX) + .unwrap() + .checked_sub(&TimeDelta::nanoseconds(1)) + .is_none() + ); + } + + #[test] + fn test_max() { + assert_eq!( + MAX.secs as i128 * 1_000_000_000 + MAX.nanos as i128, + i64::MAX as i128 * 1_000_000 + ); + assert_eq!(MAX, TimeDelta::try_milliseconds(i64::MAX).unwrap()); + assert_eq!(MAX.num_milliseconds(), i64::MAX); + assert_eq!(MAX.num_microseconds(), None); + assert_eq!(MAX.num_nanoseconds(), None); + } + + #[test] + fn test_min() { + assert_eq!( + MIN.secs as i128 * 1_000_000_000 + MIN.nanos as i128, + -i64::MAX as i128 * 1_000_000 + ); + assert_eq!(MIN, TimeDelta::try_milliseconds(-i64::MAX).unwrap()); + assert_eq!(MIN.num_milliseconds(), -i64::MAX); + assert_eq!(MIN.num_microseconds(), None); + assert_eq!(MIN.num_nanoseconds(), None); + } + + #[test] + fn test_duration_ord() { + let milliseconds = |ms| TimeDelta::try_milliseconds(ms).unwrap(); + + assert!(milliseconds(1) < milliseconds(2)); + assert!(milliseconds(2) > milliseconds(1)); + assert!(milliseconds(-1) > milliseconds(-2)); + assert!(milliseconds(-2) < milliseconds(-1)); + assert!(milliseconds(-1) < milliseconds(1)); + assert!(milliseconds(1) > milliseconds(-1)); + assert!(milliseconds(0) < milliseconds(1)); + assert!(milliseconds(0) > milliseconds(-1)); + assert!(milliseconds(1_001) < milliseconds(1_002)); + assert!(milliseconds(-1_001) > milliseconds(-1_002)); + assert!(TimeDelta::nanoseconds(1_234_567_890) < TimeDelta::nanoseconds(1_234_567_891)); + assert!(TimeDelta::nanoseconds(-1_234_567_890) > TimeDelta::nanoseconds(-1_234_567_891)); + assert!(milliseconds(i64::MAX) > milliseconds(i64::MAX - 1)); + assert!(milliseconds(-i64::MAX) < milliseconds(-i64::MAX + 1)); + } + + #[test] + fn test_duration_checked_ops() { + let milliseconds = |ms| TimeDelta::try_milliseconds(ms).unwrap(); + let seconds = |s| TimeDelta::try_seconds(s).unwrap(); + + assert_eq!( + milliseconds(i64::MAX).checked_add(&milliseconds(0)), + Some(milliseconds(i64::MAX)) + ); + assert_eq!( + milliseconds(i64::MAX - 1).checked_add(&TimeDelta::microseconds(999)), + Some(milliseconds(i64::MAX - 2) + TimeDelta::microseconds(1999)) + ); + assert!(milliseconds(i64::MAX).checked_add(&TimeDelta::microseconds(1000)).is_none()); + assert!(milliseconds(i64::MAX).checked_add(&TimeDelta::nanoseconds(1)).is_none()); + + assert_eq!( + milliseconds(-i64::MAX).checked_sub(&milliseconds(0)), + Some(milliseconds(-i64::MAX)) + ); + assert_eq!( + milliseconds(-i64::MAX + 1).checked_sub(&TimeDelta::microseconds(999)), + Some(milliseconds(-i64::MAX + 2) - TimeDelta::microseconds(1999)) + ); + assert!(milliseconds(-i64::MAX).checked_sub(&milliseconds(1)).is_none()); + assert!(milliseconds(-i64::MAX).checked_sub(&TimeDelta::nanoseconds(1)).is_none()); + + assert!(seconds(i64::MAX / 1000).checked_mul(2000).is_none()); + assert!(seconds(i64::MIN / 1000).checked_mul(2000).is_none()); + assert!(seconds(1).checked_div(0).is_none()); + } + + #[test] + fn test_duration_abs() { + let milliseconds = |ms| TimeDelta::try_milliseconds(ms).unwrap(); + + assert_eq!(milliseconds(1300).abs(), milliseconds(1300)); + assert_eq!(milliseconds(1000).abs(), milliseconds(1000)); + assert_eq!(milliseconds(300).abs(), milliseconds(300)); + assert_eq!(milliseconds(0).abs(), milliseconds(0)); + assert_eq!(milliseconds(-300).abs(), milliseconds(300)); + assert_eq!(milliseconds(-700).abs(), milliseconds(700)); + assert_eq!(milliseconds(-1000).abs(), milliseconds(1000)); + assert_eq!(milliseconds(-1300).abs(), milliseconds(1300)); + assert_eq!(milliseconds(-1700).abs(), milliseconds(1700)); + assert_eq!(milliseconds(-i64::MAX).abs(), milliseconds(i64::MAX)); + } + + #[test] + #[allow(clippy::erasing_op)] + fn test_duration_mul() { + assert_eq!(TimeDelta::zero() * i32::MAX, TimeDelta::zero()); + assert_eq!(TimeDelta::zero() * i32::MIN, TimeDelta::zero()); + assert_eq!(TimeDelta::nanoseconds(1) * 0, TimeDelta::zero()); + assert_eq!(TimeDelta::nanoseconds(1) * 1, TimeDelta::nanoseconds(1)); + assert_eq!(TimeDelta::nanoseconds(1) * 1_000_000_000, TimeDelta::try_seconds(1).unwrap()); + assert_eq!(TimeDelta::nanoseconds(1) * -1_000_000_000, -TimeDelta::try_seconds(1).unwrap()); + assert_eq!(-TimeDelta::nanoseconds(1) * 1_000_000_000, -TimeDelta::try_seconds(1).unwrap()); + assert_eq!( + TimeDelta::nanoseconds(30) * 333_333_333, + TimeDelta::try_seconds(10).unwrap() - TimeDelta::nanoseconds(10) + ); + assert_eq!( + (TimeDelta::nanoseconds(1) + + TimeDelta::try_seconds(1).unwrap() + + TimeDelta::try_days(1).unwrap()) + * 3, + TimeDelta::nanoseconds(3) + + TimeDelta::try_seconds(3).unwrap() + + TimeDelta::try_days(3).unwrap() + ); + assert_eq!( + TimeDelta::try_milliseconds(1500).unwrap() * -2, + TimeDelta::try_seconds(-3).unwrap() + ); + assert_eq!( + TimeDelta::try_milliseconds(-1500).unwrap() * 2, + TimeDelta::try_seconds(-3).unwrap() + ); + } + + #[test] + fn test_duration_div() { + assert_eq!(TimeDelta::zero() / i32::MAX, TimeDelta::zero()); + assert_eq!(TimeDelta::zero() / i32::MIN, TimeDelta::zero()); + assert_eq!(TimeDelta::nanoseconds(123_456_789) / 1, TimeDelta::nanoseconds(123_456_789)); + assert_eq!(TimeDelta::nanoseconds(123_456_789) / -1, -TimeDelta::nanoseconds(123_456_789)); + assert_eq!(-TimeDelta::nanoseconds(123_456_789) / -1, TimeDelta::nanoseconds(123_456_789)); + assert_eq!(-TimeDelta::nanoseconds(123_456_789) / 1, -TimeDelta::nanoseconds(123_456_789)); + assert_eq!(TimeDelta::try_seconds(1).unwrap() / 3, TimeDelta::nanoseconds(333_333_333)); + assert_eq!(TimeDelta::try_seconds(4).unwrap() / 3, TimeDelta::nanoseconds(1_333_333_333)); + assert_eq!( + TimeDelta::try_seconds(-1).unwrap() / 2, + TimeDelta::try_milliseconds(-500).unwrap() + ); + assert_eq!( + TimeDelta::try_seconds(1).unwrap() / -2, + TimeDelta::try_milliseconds(-500).unwrap() + ); + assert_eq!( + TimeDelta::try_seconds(-1).unwrap() / -2, + TimeDelta::try_milliseconds(500).unwrap() + ); + assert_eq!(TimeDelta::try_seconds(-4).unwrap() / 3, TimeDelta::nanoseconds(-1_333_333_333)); + assert_eq!(TimeDelta::try_seconds(-4).unwrap() / -3, TimeDelta::nanoseconds(1_333_333_333)); + } + + #[test] + fn test_duration_sum() { + let duration_list_1 = [TimeDelta::zero(), TimeDelta::try_seconds(1).unwrap()]; + let sum_1: TimeDelta = duration_list_1.iter().sum(); + assert_eq!(sum_1, TimeDelta::try_seconds(1).unwrap()); + + let duration_list_2 = [ + TimeDelta::zero(), + TimeDelta::try_seconds(1).unwrap(), + TimeDelta::try_seconds(6).unwrap(), + TimeDelta::try_seconds(10).unwrap(), + ]; + let sum_2: TimeDelta = duration_list_2.iter().sum(); + assert_eq!(sum_2, TimeDelta::try_seconds(17).unwrap()); + + let duration_arr = [ + TimeDelta::zero(), + TimeDelta::try_seconds(1).unwrap(), + TimeDelta::try_seconds(6).unwrap(), + TimeDelta::try_seconds(10).unwrap(), + ]; + let sum_3: TimeDelta = duration_arr.into_iter().sum(); + assert_eq!(sum_3, TimeDelta::try_seconds(17).unwrap()); + } + + #[test] + fn test_duration_fmt() { + assert_eq!(TimeDelta::zero().to_string(), "P0D"); + assert_eq!(TimeDelta::try_days(42).unwrap().to_string(), "PT3628800S"); + assert_eq!(TimeDelta::try_days(-42).unwrap().to_string(), "-PT3628800S"); + assert_eq!(TimeDelta::try_seconds(42).unwrap().to_string(), "PT42S"); + assert_eq!(TimeDelta::try_milliseconds(42).unwrap().to_string(), "PT0.042S"); + assert_eq!(TimeDelta::microseconds(42).to_string(), "PT0.000042S"); + assert_eq!(TimeDelta::nanoseconds(42).to_string(), "PT0.000000042S"); + assert_eq!( + (TimeDelta::try_days(7).unwrap() + TimeDelta::try_milliseconds(6543).unwrap()) + .to_string(), + "PT604806.543S" + ); + assert_eq!(TimeDelta::try_seconds(-86_401).unwrap().to_string(), "-PT86401S"); + assert_eq!(TimeDelta::nanoseconds(-1).to_string(), "-PT0.000000001S"); + + // the format specifier should have no effect on `TimeDelta` + assert_eq!( + format!( + "{:30}", + TimeDelta::try_days(1).unwrap() + TimeDelta::try_milliseconds(2345).unwrap() + ), + "PT86402.345S" + ); + } + + #[test] + fn test_to_std() { + assert_eq!(TimeDelta::try_seconds(1).unwrap().to_std(), Ok(Duration::new(1, 0))); + assert_eq!(TimeDelta::try_seconds(86_401).unwrap().to_std(), Ok(Duration::new(86_401, 0))); + assert_eq!( + TimeDelta::try_milliseconds(123).unwrap().to_std(), + Ok(Duration::new(0, 123_000_000)) + ); + assert_eq!( + TimeDelta::try_milliseconds(123_765).unwrap().to_std(), + Ok(Duration::new(123, 765_000_000)) + ); + assert_eq!(TimeDelta::nanoseconds(777).to_std(), Ok(Duration::new(0, 777))); + assert_eq!(MAX.to_std(), Ok(Duration::new(9_223_372_036_854_775, 807_000_000))); + assert_eq!(TimeDelta::try_seconds(-1).unwrap().to_std(), Err(OutOfRangeError(()))); + assert_eq!(TimeDelta::try_milliseconds(-1).unwrap().to_std(), Err(OutOfRangeError(()))); + } + + #[test] + fn test_from_std() { + assert_eq!( + Ok(TimeDelta::try_seconds(1).unwrap()), + TimeDelta::from_std(Duration::new(1, 0)) + ); + assert_eq!( + Ok(TimeDelta::try_seconds(86_401).unwrap()), + TimeDelta::from_std(Duration::new(86_401, 0)) + ); + assert_eq!( + Ok(TimeDelta::try_milliseconds(123).unwrap()), + TimeDelta::from_std(Duration::new(0, 123_000_000)) + ); + assert_eq!( + Ok(TimeDelta::try_milliseconds(123_765).unwrap()), + TimeDelta::from_std(Duration::new(123, 765_000_000)) + ); + assert_eq!(Ok(TimeDelta::nanoseconds(777)), TimeDelta::from_std(Duration::new(0, 777))); + assert_eq!(Ok(MAX), TimeDelta::from_std(Duration::new(9_223_372_036_854_775, 807_000_000))); + assert_eq!( + TimeDelta::from_std(Duration::new(9_223_372_036_854_776, 0)), + Err(OutOfRangeError(())) + ); + assert_eq!( + TimeDelta::from_std(Duration::new(9_223_372_036_854_775, 807_000_001)), + Err(OutOfRangeError(())) + ); + } + + #[test] + fn test_duration_const() { + const ONE_WEEK: TimeDelta = expect(TimeDelta::try_weeks(1), ""); + const ONE_DAY: TimeDelta = expect(TimeDelta::try_days(1), ""); + const ONE_HOUR: TimeDelta = expect(TimeDelta::try_hours(1), ""); + const ONE_MINUTE: TimeDelta = expect(TimeDelta::try_minutes(1), ""); + const ONE_SECOND: TimeDelta = expect(TimeDelta::try_seconds(1), ""); + const ONE_MILLI: TimeDelta = expect(TimeDelta::try_milliseconds(1), ""); + const ONE_MICRO: TimeDelta = TimeDelta::microseconds(1); + const ONE_NANO: TimeDelta = TimeDelta::nanoseconds(1); + let combo: TimeDelta = ONE_WEEK + + ONE_DAY + + ONE_HOUR + + ONE_MINUTE + + ONE_SECOND + + ONE_MILLI + + ONE_MICRO + + ONE_NANO; + + assert!(ONE_WEEK != TimeDelta::zero()); + assert!(ONE_DAY != TimeDelta::zero()); + assert!(ONE_HOUR != TimeDelta::zero()); + assert!(ONE_MINUTE != TimeDelta::zero()); + assert!(ONE_SECOND != TimeDelta::zero()); + assert!(ONE_MILLI != TimeDelta::zero()); + assert!(ONE_MICRO != TimeDelta::zero()); + assert!(ONE_NANO != TimeDelta::zero()); + assert_eq!( + combo, + TimeDelta::try_seconds(86400 * 7 + 86400 + 3600 + 60 + 1).unwrap() + + TimeDelta::nanoseconds(1 + 1_000 + 1_000_000) + ); + } + + #[test] + #[cfg(feature = "rkyv-validation")] + fn test_rkyv_validation() { + let duration = TimeDelta::try_seconds(1).unwrap(); + let bytes = rkyv::to_bytes::<_, 16>(&duration).unwrap(); + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), duration); + } +} diff --git a/third_party/rust/chrono/src/traits.rs b/third_party/rust/chrono/src/traits.rs new file mode 100644 index 00000000000..21ed88e9ed3 --- /dev/null +++ b/third_party/rust/chrono/src/traits.rs @@ -0,0 +1,393 @@ +use crate::{IsoWeek, Weekday}; + +/// The common set of methods for date component. +/// +/// Methods such as [`year`], [`month`], [`day`] and [`weekday`] can be used to get basic +/// information about the date. +/// +/// The `with_*` methods can change the date. +/// +/// # Warning +/// +/// The `with_*` methods can be convenient to change a single component of a date, but they must be +/// used with some care. Examples to watch out for: +/// +/// - [`with_year`] changes the year component of a year-month-day value. Don't use this method if +/// you want the ordinal to stay the same after changing the year, of if you want the week and +/// weekday values to stay the same. +/// - Don't combine two `with_*` methods to change two components of the date. For example to +/// change both the year and month components of a date. This could fail because an intermediate +/// value does not exist, while the final date would be valid. +/// +/// For more complex changes to a date, it is best to use the methods on [`NaiveDate`] to create a +/// new value instead of altering an existing date. +/// +/// [`year`]: Datelike::year +/// [`month`]: Datelike::month +/// [`day`]: Datelike::day +/// [`weekday`]: Datelike::weekday +/// [`with_year`]: Datelike::with_year +/// [`NaiveDate`]: crate::NaiveDate +pub trait Datelike: Sized { + /// Returns the year number in the [calendar date](./naive/struct.NaiveDate.html#calendar-date). + fn year(&self) -> i32; + + /// Returns the absolute year number starting from 1 with a boolean flag, + /// which is false when the year predates the epoch (BCE/BC) and true otherwise (CE/AD). + #[inline] + fn year_ce(&self) -> (bool, u32) { + let year = self.year(); + if year < 1 { (false, (1 - year) as u32) } else { (true, year as u32) } + } + + /// Returns the quarter number starting from 1. + /// + /// The return value ranges from 1 to 4. + #[inline] + fn quarter(&self) -> u32 { + (self.month() - 1).div_euclid(3) + 1 + } + + /// Returns the month number starting from 1. + /// + /// The return value ranges from 1 to 12. + fn month(&self) -> u32; + + /// Returns the month number starting from 0. + /// + /// The return value ranges from 0 to 11. + fn month0(&self) -> u32; + + /// Returns the day of month starting from 1. + /// + /// The return value ranges from 1 to 31. (The last day of month differs by months.) + fn day(&self) -> u32; + + /// Returns the day of month starting from 0. + /// + /// The return value ranges from 0 to 30. (The last day of month differs by months.) + fn day0(&self) -> u32; + + /// Returns the day of year starting from 1. + /// + /// The return value ranges from 1 to 366. (The last day of year differs by years.) + fn ordinal(&self) -> u32; + + /// Returns the day of year starting from 0. + /// + /// The return value ranges from 0 to 365. (The last day of year differs by years.) + fn ordinal0(&self) -> u32; + + /// Returns the day of week. + fn weekday(&self) -> Weekday; + + /// Returns the ISO week. + fn iso_week(&self) -> IsoWeek; + + /// Makes a new value with the year number changed, while keeping the same month and day. + /// + /// This method assumes you want to work on the date as a year-month-day value. Don't use it if + /// you want the ordinal to stay the same after changing the year, of if you want the week and + /// weekday values to stay the same. + /// + /// # Errors + /// + /// Returns `None` when: + /// + /// - The resulting date does not exist (February 29 in a non-leap year). + /// - The year is out of range for [`NaiveDate`]. + /// - In case of [`DateTime`] if the resulting date and time fall within a timezone + /// transition such as from DST to standard time. + /// + /// [`NaiveDate`]: crate::NaiveDate + /// [`DateTime`]: crate::DateTime + /// + /// # Examples + /// + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2020, 5, 13).unwrap().with_year(2023).unwrap(), + /// NaiveDate::from_ymd_opt(2023, 5, 13).unwrap() + /// ); + /// // Resulting date 2023-02-29 does not exist: + /// assert!(NaiveDate::from_ymd_opt(2020, 2, 29).unwrap().with_year(2023).is_none()); + /// + /// // Don't use `with_year` if you want the ordinal date to stay the same: + /// assert_ne!( + /// NaiveDate::from_yo_opt(2020, 100).unwrap().with_year(2023).unwrap(), + /// NaiveDate::from_yo_opt(2023, 100).unwrap() // result is 2023-101 + /// ); + /// ``` + fn with_year(&self, year: i32) -> Option; + + /// Makes a new value with the month number (starting from 1) changed. + /// + /// # Errors + /// + /// Returns `None` when: + /// + /// - The resulting date does not exist (for example `month(4)` when day of the month is 31). + /// - In case of [`DateTime`] if the resulting date and time fall within a timezone + /// transition such as from DST to standard time. + /// - The value for `month` is out of range. + /// + /// [`DateTime`]: crate::DateTime + /// + /// # Examples + /// + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// assert_eq!( + /// NaiveDate::from_ymd_opt(2023, 5, 12).unwrap().with_month(9).unwrap(), + /// NaiveDate::from_ymd_opt(2023, 9, 12).unwrap() + /// ); + /// // Resulting date 2023-09-31 does not exist: + /// assert!(NaiveDate::from_ymd_opt(2023, 5, 31).unwrap().with_month(9).is_none()); + /// ``` + /// + /// Don't combine multiple `Datelike::with_*` methods. The intermediate value may not exist. + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// fn with_year_month(date: NaiveDate, year: i32, month: u32) -> Option { + /// date.with_year(year)?.with_month(month) + /// } + /// let d = NaiveDate::from_ymd_opt(2020, 2, 29).unwrap(); + /// assert!(with_year_month(d, 2019, 1).is_none()); // fails because of invalid intermediate value + /// + /// // Correct version: + /// fn with_year_month_fixed(date: NaiveDate, year: i32, month: u32) -> Option { + /// NaiveDate::from_ymd_opt(year, month, date.day()) + /// } + /// let d = NaiveDate::from_ymd_opt(2020, 2, 29).unwrap(); + /// assert_eq!(with_year_month_fixed(d, 2019, 1), NaiveDate::from_ymd_opt(2019, 1, 29)); + /// ``` + fn with_month(&self, month: u32) -> Option; + + /// Makes a new value with the month number (starting from 0) changed. + /// + /// # Errors + /// + /// Returns `None` when: + /// + /// - The resulting date does not exist (for example `month0(3)` when day of the month is 31). + /// - In case of [`DateTime`] if the resulting date and time fall within a timezone + /// transition such as from DST to standard time. + /// - The value for `month0` is out of range. + /// + /// [`DateTime`]: crate::DateTime + fn with_month0(&self, month0: u32) -> Option; + + /// Makes a new value with the day of month (starting from 1) changed. + /// + /// # Errors + /// + /// Returns `None` when: + /// + /// - The resulting date does not exist (for example `day(31)` in April). + /// - In case of [`DateTime`] if the resulting date and time fall within a timezone + /// transition such as from DST to standard time. + /// - The value for `day` is out of range. + /// + /// [`DateTime`]: crate::DateTime + fn with_day(&self, day: u32) -> Option; + + /// Makes a new value with the day of month (starting from 0) changed. + /// + /// # Errors + /// + /// Returns `None` when: + /// + /// - The resulting date does not exist (for example `day0(30)` in April). + /// - In case of [`DateTime`] if the resulting date and time fall within a timezone + /// transition such as from DST to standard time. + /// - The value for `day0` is out of range. + /// + /// [`DateTime`]: crate::DateTime + fn with_day0(&self, day0: u32) -> Option; + + /// Makes a new value with the day of year (starting from 1) changed. + /// + /// # Errors + /// + /// Returns `None` when: + /// + /// - The resulting date does not exist (`with_ordinal(366)` in a non-leap year). + /// - In case of [`DateTime`] if the resulting date and time fall within a timezone + /// transition such as from DST to standard time. + /// - The value for `ordinal` is out of range. + /// + /// [`DateTime`]: crate::DateTime + fn with_ordinal(&self, ordinal: u32) -> Option; + + /// Makes a new value with the day of year (starting from 0) changed. + /// + /// # Errors + /// + /// Returns `None` when: + /// + /// - The resulting date does not exist (`with_ordinal0(365)` in a non-leap year). + /// - In case of [`DateTime`] if the resulting date and time fall within a timezone + /// transition such as from DST to standard time. + /// - The value for `ordinal0` is out of range. + /// + /// [`DateTime`]: crate::DateTime + fn with_ordinal0(&self, ordinal0: u32) -> Option; + + /// Counts the days in the proleptic Gregorian calendar, with January 1, Year 1 (CE) as day 1. + /// + /// # Examples + /// + /// ``` + /// use chrono::{Datelike, NaiveDate}; + /// + /// assert_eq!(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().num_days_from_ce(), 719_163); + /// assert_eq!(NaiveDate::from_ymd_opt(2, 1, 1).unwrap().num_days_from_ce(), 366); + /// assert_eq!(NaiveDate::from_ymd_opt(1, 1, 1).unwrap().num_days_from_ce(), 1); + /// assert_eq!(NaiveDate::from_ymd_opt(0, 1, 1).unwrap().num_days_from_ce(), -365); + /// ``` + fn num_days_from_ce(&self) -> i32 { + // See test_num_days_from_ce_against_alternative_impl below for a more straightforward + // implementation. + + // we know this wouldn't overflow since year is limited to 1/2^13 of i32's full range. + let mut year = self.year() - 1; + let mut ndays = 0; + if year < 0 { + let excess = 1 + (-year) / 400; + year += excess * 400; + ndays -= excess * 146_097; + } + let div_100 = year / 100; + ndays += ((year * 1461) >> 2) - div_100 + (div_100 >> 2); + ndays + self.ordinal() as i32 + } +} + +/// The common set of methods for time component. +pub trait Timelike: Sized { + /// Returns the hour number from 0 to 23. + fn hour(&self) -> u32; + + /// Returns the hour number from 1 to 12 with a boolean flag, + /// which is false for AM and true for PM. + #[inline] + fn hour12(&self) -> (bool, u32) { + let hour = self.hour(); + let mut hour12 = hour % 12; + if hour12 == 0 { + hour12 = 12; + } + (hour >= 12, hour12) + } + + /// Returns the minute number from 0 to 59. + fn minute(&self) -> u32; + + /// Returns the second number from 0 to 59. + fn second(&self) -> u32; + + /// Returns the number of nanoseconds since the whole non-leap second. + /// The range from 1,000,000,000 to 1,999,999,999 represents + /// the [leap second](./naive/struct.NaiveTime.html#leap-second-handling). + fn nanosecond(&self) -> u32; + + /// Makes a new value with the hour number changed. + /// + /// Returns `None` when the resulting value would be invalid. + fn with_hour(&self, hour: u32) -> Option; + + /// Makes a new value with the minute number changed. + /// + /// Returns `None` when the resulting value would be invalid. + fn with_minute(&self, min: u32) -> Option; + + /// Makes a new value with the second number changed. + /// + /// Returns `None` when the resulting value would be invalid. + /// As with the [`second`](#tymethod.second) method, + /// the input range is restricted to 0 through 59. + fn with_second(&self, sec: u32) -> Option; + + /// Makes a new value with nanoseconds since the whole non-leap second changed. + /// + /// Returns `None` when the resulting value would be invalid. + /// As with the [`nanosecond`](#tymethod.nanosecond) method, + /// the input range can exceed 1,000,000,000 for leap seconds. + fn with_nanosecond(&self, nano: u32) -> Option; + + /// Returns the number of non-leap seconds past the last midnight. + /// + /// Every value in 00:00:00-23:59:59 maps to an integer in 0-86399. + /// + /// This method is not intended to provide the real number of seconds since midnight on a given + /// day. It does not take things like DST transitions into account. + #[inline] + fn num_seconds_from_midnight(&self) -> u32 { + self.hour() * 3600 + self.minute() * 60 + self.second() + } +} + +#[cfg(test)] +mod tests { + use super::Datelike; + use crate::{Days, NaiveDate}; + + /// Tests `Datelike::num_days_from_ce` against an alternative implementation. + /// + /// The alternative implementation is not as short as the current one but it is simpler to + /// understand, with less unexplained magic constants. + #[test] + fn test_num_days_from_ce_against_alternative_impl() { + /// Returns the number of multiples of `div` in the range `start..end`. + /// + /// If the range `start..end` is back-to-front, i.e. `start` is greater than `end`, the + /// behaviour is defined by the following equation: + /// `in_between(start, end, div) == - in_between(end, start, div)`. + /// + /// When `div` is 1, this is equivalent to `end - start`, i.e. the length of `start..end`. + /// + /// # Panics + /// + /// Panics if `div` is not positive. + fn in_between(start: i32, end: i32, div: i32) -> i32 { + assert!(div > 0, "in_between: nonpositive div = {}", div); + let start = (start.div_euclid(div), start.rem_euclid(div)); + let end = (end.div_euclid(div), end.rem_euclid(div)); + // The lowest multiple of `div` greater than or equal to `start`, divided. + let start = start.0 + (start.1 != 0) as i32; + // The lowest multiple of `div` greater than or equal to `end`, divided. + let end = end.0 + (end.1 != 0) as i32; + end - start + } + + /// Alternative implementation to `Datelike::num_days_from_ce` + fn num_days_from_ce(date: &Date) -> i32 { + let year = date.year(); + let diff = move |div| in_between(1, year, div); + // 365 days a year, one more in leap years. In the gregorian calendar, leap years are all + // the multiples of 4 except multiples of 100 but including multiples of 400. + date.ordinal() as i32 + 365 * diff(1) + diff(4) - diff(100) + diff(400) + } + + for year in NaiveDate::MIN.year()..=NaiveDate::MAX.year() { + let jan1_year = NaiveDate::from_ymd_opt(year, 1, 1).unwrap(); + assert_eq!( + jan1_year.num_days_from_ce(), + num_days_from_ce(&jan1_year), + "on {:?}", + jan1_year + ); + let mid_year = jan1_year + Days::new(133); + assert_eq!( + mid_year.num_days_from_ce(), + num_days_from_ce(&mid_year), + "on {:?}", + mid_year + ); + } + } +} diff --git a/third_party/rust/chrono/src/weekday.rs b/third_party/rust/chrono/src/weekday.rs new file mode 100644 index 00000000000..d2ffa4b624c --- /dev/null +++ b/third_party/rust/chrono/src/weekday.rs @@ -0,0 +1,408 @@ +use core::fmt; + +#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] +use rkyv::{Archive, Deserialize, Serialize}; + +use crate::OutOfRange; + +/// The day of week. +/// +/// The order of the days of week depends on the context. +/// (This is why this type does *not* implement `PartialOrd` or `Ord` traits.) +/// One should prefer `*_from_monday` or `*_from_sunday` methods to get the correct result. +/// +/// # Example +/// ``` +/// use chrono::Weekday; +/// +/// let monday = "Monday".parse::().unwrap(); +/// assert_eq!(monday, Weekday::Mon); +/// +/// let sunday = Weekday::try_from(6).unwrap(); +/// assert_eq!(sunday, Weekday::Sun); +/// +/// assert_eq!(sunday.num_days_from_monday(), 6); // starts counting with Monday = 0 +/// assert_eq!(sunday.number_from_monday(), 7); // starts counting with Monday = 1 +/// assert_eq!(sunday.num_days_from_sunday(), 0); // starts counting with Sunday = 0 +/// assert_eq!(sunday.number_from_sunday(), 1); // starts counting with Sunday = 1 +/// +/// assert_eq!(sunday.succ(), monday); +/// assert_eq!(sunday.pred(), Weekday::Sat); +/// ``` +#[derive(PartialEq, Eq, Copy, Clone, Debug, Hash)] +#[cfg_attr( + any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), + derive(Archive, Deserialize, Serialize), + archive(compare(PartialEq)), + archive_attr(derive(Clone, Copy, PartialEq, Eq, Debug, Hash)) +)] +#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] +#[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(arbitrary::Arbitrary))] +pub enum Weekday { + /// Monday. + Mon = 0, + /// Tuesday. + Tue = 1, + /// Wednesday. + Wed = 2, + /// Thursday. + Thu = 3, + /// Friday. + Fri = 4, + /// Saturday. + Sat = 5, + /// Sunday. + Sun = 6, +} + +impl Weekday { + /// The next day in the week. + /// + /// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` + /// ----------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- + /// `w.succ()`: | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` | `Mon` + #[inline] + #[must_use] + pub const fn succ(&self) -> Weekday { + match *self { + Weekday::Mon => Weekday::Tue, + Weekday::Tue => Weekday::Wed, + Weekday::Wed => Weekday::Thu, + Weekday::Thu => Weekday::Fri, + Weekday::Fri => Weekday::Sat, + Weekday::Sat => Weekday::Sun, + Weekday::Sun => Weekday::Mon, + } + } + + /// The previous day in the week. + /// + /// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` + /// ----------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- + /// `w.pred()`: | `Sun` | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` + #[inline] + #[must_use] + pub const fn pred(&self) -> Weekday { + match *self { + Weekday::Mon => Weekday::Sun, + Weekday::Tue => Weekday::Mon, + Weekday::Wed => Weekday::Tue, + Weekday::Thu => Weekday::Wed, + Weekday::Fri => Weekday::Thu, + Weekday::Sat => Weekday::Fri, + Weekday::Sun => Weekday::Sat, + } + } + + /// Returns a day-of-week number starting from Monday = 1. (ISO 8601 weekday number) + /// + /// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` + /// ------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- + /// `w.number_from_monday()`: | 1 | 2 | 3 | 4 | 5 | 6 | 7 + #[inline] + pub const fn number_from_monday(&self) -> u32 { + self.days_since(Weekday::Mon) + 1 + } + + /// Returns a day-of-week number starting from Sunday = 1. + /// + /// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` + /// ------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- + /// `w.number_from_sunday()`: | 2 | 3 | 4 | 5 | 6 | 7 | 1 + #[inline] + pub const fn number_from_sunday(&self) -> u32 { + self.days_since(Weekday::Sun) + 1 + } + + /// Returns a day-of-week number starting from Monday = 0. + /// + /// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` + /// --------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- + /// `w.num_days_from_monday()`: | 0 | 1 | 2 | 3 | 4 | 5 | 6 + /// + /// # Example + /// + /// ``` + /// # #[cfg(feature = "clock")] { + /// # use chrono::{Local, Datelike}; + /// // MTWRFSU is occasionally used as a single-letter abbreviation of the weekdays. + /// // Use `num_days_from_monday` to index into the array. + /// const MTWRFSU: [char; 7] = ['M', 'T', 'W', 'R', 'F', 'S', 'U']; + /// + /// let today = Local::now().weekday(); + /// println!("{}", MTWRFSU[today.num_days_from_monday() as usize]); + /// # } + /// ``` + #[inline] + pub const fn num_days_from_monday(&self) -> u32 { + self.days_since(Weekday::Mon) + } + + /// Returns a day-of-week number starting from Sunday = 0. + /// + /// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` + /// --------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- + /// `w.num_days_from_sunday()`: | 1 | 2 | 3 | 4 | 5 | 6 | 0 + #[inline] + pub const fn num_days_from_sunday(&self) -> u32 { + self.days_since(Weekday::Sun) + } + + /// The number of days since the given day. + /// + /// # Examples + /// + /// ``` + /// use chrono::Weekday::*; + /// assert_eq!(Mon.days_since(Mon), 0); + /// assert_eq!(Sun.days_since(Tue), 5); + /// assert_eq!(Wed.days_since(Sun), 3); + /// ``` + pub const fn days_since(&self, other: Weekday) -> u32 { + let lhs = *self as u32; + let rhs = other as u32; + if lhs < rhs { 7 + lhs - rhs } else { lhs - rhs } + } +} + +impl fmt::Display for Weekday { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.pad(match *self { + Weekday::Mon => "Mon", + Weekday::Tue => "Tue", + Weekday::Wed => "Wed", + Weekday::Thu => "Thu", + Weekday::Fri => "Fri", + Weekday::Sat => "Sat", + Weekday::Sun => "Sun", + }) + } +} + +/// Any weekday can be represented as an integer from 0 to 6, which equals to +/// [`Weekday::num_days_from_monday`](#method.num_days_from_monday) in this implementation. +/// Do not heavily depend on this though; use explicit methods whenever possible. +impl TryFrom for Weekday { + type Error = OutOfRange; + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(Weekday::Mon), + 1 => Ok(Weekday::Tue), + 2 => Ok(Weekday::Wed), + 3 => Ok(Weekday::Thu), + 4 => Ok(Weekday::Fri), + 5 => Ok(Weekday::Sat), + 6 => Ok(Weekday::Sun), + _ => Err(OutOfRange::new()), + } + } +} + +/// Any weekday can be represented as an integer from 0 to 6, which equals to +/// [`Weekday::num_days_from_monday`](#method.num_days_from_monday) in this implementation. +/// Do not heavily depend on this though; use explicit methods whenever possible. +impl num_traits::FromPrimitive for Weekday { + #[inline] + fn from_i64(n: i64) -> Option { + match n { + 0 => Some(Weekday::Mon), + 1 => Some(Weekday::Tue), + 2 => Some(Weekday::Wed), + 3 => Some(Weekday::Thu), + 4 => Some(Weekday::Fri), + 5 => Some(Weekday::Sat), + 6 => Some(Weekday::Sun), + _ => None, + } + } + + #[inline] + fn from_u64(n: u64) -> Option { + match n { + 0 => Some(Weekday::Mon), + 1 => Some(Weekday::Tue), + 2 => Some(Weekday::Wed), + 3 => Some(Weekday::Thu), + 4 => Some(Weekday::Fri), + 5 => Some(Weekday::Sat), + 6 => Some(Weekday::Sun), + _ => None, + } + } +} + +/// An error resulting from reading `Weekday` value with `FromStr`. +#[derive(Clone, PartialEq, Eq)] +pub struct ParseWeekdayError { + pub(crate) _dummy: (), +} + +#[cfg(feature = "std")] +impl std::error::Error for ParseWeekdayError {} + +impl fmt::Display for ParseWeekdayError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_fmt(format_args!("{:?}", self)) + } +} + +impl fmt::Debug for ParseWeekdayError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "ParseWeekdayError {{ .. }}") + } +} + +// the actual `FromStr` implementation is in the `format` module to leverage the existing code + +#[cfg(feature = "serde")] +mod weekday_serde { + use super::Weekday; + use core::fmt; + use serde::{de, ser}; + + impl ser::Serialize for Weekday { + fn serialize(&self, serializer: S) -> Result + where + S: ser::Serializer, + { + serializer.collect_str(&self) + } + } + + struct WeekdayVisitor; + + impl de::Visitor<'_> for WeekdayVisitor { + type Value = Weekday; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("Weekday") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + value.parse().map_err(|_| E::custom("short or long weekday names expected")) + } + } + + impl<'de> de::Deserialize<'de> for Weekday { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + deserializer.deserialize_str(WeekdayVisitor) + } + } +} + +#[cfg(test)] +mod tests { + use super::Weekday; + + #[test] + fn test_days_since() { + for i in 0..7 { + let base_day = Weekday::try_from(i).unwrap(); + + assert_eq!(base_day.num_days_from_monday(), base_day.days_since(Weekday::Mon)); + assert_eq!(base_day.num_days_from_sunday(), base_day.days_since(Weekday::Sun)); + + assert_eq!(base_day.days_since(base_day), 0); + + assert_eq!(base_day.days_since(base_day.pred()), 1); + assert_eq!(base_day.days_since(base_day.pred().pred()), 2); + assert_eq!(base_day.days_since(base_day.pred().pred().pred()), 3); + assert_eq!(base_day.days_since(base_day.pred().pred().pred().pred()), 4); + assert_eq!(base_day.days_since(base_day.pred().pred().pred().pred().pred()), 5); + assert_eq!(base_day.days_since(base_day.pred().pred().pred().pred().pred().pred()), 6); + + assert_eq!(base_day.days_since(base_day.succ()), 6); + assert_eq!(base_day.days_since(base_day.succ().succ()), 5); + assert_eq!(base_day.days_since(base_day.succ().succ().succ()), 4); + assert_eq!(base_day.days_since(base_day.succ().succ().succ().succ()), 3); + assert_eq!(base_day.days_since(base_day.succ().succ().succ().succ().succ()), 2); + assert_eq!(base_day.days_since(base_day.succ().succ().succ().succ().succ().succ()), 1); + } + } + + #[test] + fn test_formatting_alignment() { + // No exhaustive testing here as we just delegate the + // implementation to Formatter::pad. Just some basic smoke + // testing to ensure that it's in fact being done. + assert_eq!(format!("{:x>7}", Weekday::Mon), "xxxxMon"); + assert_eq!(format!("{:^7}", Weekday::Mon), " Mon "); + assert_eq!(format!("{:Z<7}", Weekday::Mon), "MonZZZZ"); + } + + #[test] + #[cfg(feature = "serde")] + fn test_serde_serialize() { + use Weekday::*; + use serde_json::to_string; + + let cases: Vec<(Weekday, &str)> = vec![ + (Mon, "\"Mon\""), + (Tue, "\"Tue\""), + (Wed, "\"Wed\""), + (Thu, "\"Thu\""), + (Fri, "\"Fri\""), + (Sat, "\"Sat\""), + (Sun, "\"Sun\""), + ]; + + for (weekday, expected_str) in cases { + let string = to_string(&weekday).unwrap(); + assert_eq!(string, expected_str); + } + } + + #[test] + #[cfg(feature = "serde")] + fn test_serde_deserialize() { + use Weekday::*; + use serde_json::from_str; + + let cases: Vec<(&str, Weekday)> = vec![ + ("\"mon\"", Mon), + ("\"MONDAY\"", Mon), + ("\"MonDay\"", Mon), + ("\"mOn\"", Mon), + ("\"tue\"", Tue), + ("\"tuesday\"", Tue), + ("\"wed\"", Wed), + ("\"wednesday\"", Wed), + ("\"thu\"", Thu), + ("\"thursday\"", Thu), + ("\"fri\"", Fri), + ("\"friday\"", Fri), + ("\"sat\"", Sat), + ("\"saturday\"", Sat), + ("\"sun\"", Sun), + ("\"sunday\"", Sun), + ]; + + for (str, expected_weekday) in cases { + let weekday = from_str::(str).unwrap(); + assert_eq!(weekday, expected_weekday); + } + + let errors: Vec<&str> = + vec!["\"not a weekday\"", "\"monDAYs\"", "\"mond\"", "mon", "\"thur\"", "\"thurs\""]; + + for str in errors { + from_str::(str).unwrap_err(); + } + } + + #[test] + #[cfg(feature = "rkyv-validation")] + fn test_rkyv_validation() { + let mon = Weekday::Mon; + let bytes = rkyv::to_bytes::<_, 1>(&mon).unwrap(); + + assert_eq!(rkyv::from_bytes::(&bytes).unwrap(), mon); + } +} diff --git a/third_party/rust/chrono/tests/dateutils.rs b/third_party/rust/chrono/tests/dateutils.rs new file mode 100644 index 00000000000..849abc72f91 --- /dev/null +++ b/third_party/rust/chrono/tests/dateutils.rs @@ -0,0 +1,165 @@ +#![cfg(all(unix, feature = "clock", feature = "std"))] + +use std::{path, process, thread}; + +#[cfg(target_os = "linux")] +use chrono::Days; +use chrono::{Datelike, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Timelike}; + +fn verify_against_date_command_local(path: &'static str, dt: NaiveDateTime) { + let output = process::Command::new(path) + .arg("-d") + .arg(format!("{}-{:02}-{:02} {:02}:05:01", dt.year(), dt.month(), dt.day(), dt.hour())) + .arg("+%Y-%m-%d %H:%M:%S %:z") + .output() + .unwrap(); + + let date_command_str = String::from_utf8(output.stdout).unwrap(); + + // The below would be preferred. At this stage neither earliest() or latest() + // seems to be consistent with the output of the `date` command, so we simply + // compare both. + // let local = Local + // .with_ymd_and_hms(year, month, day, hour, 5, 1) + // // looks like the "date" command always returns a given time when it is ambiguous + // .earliest(); + + // if let Some(local) = local { + // assert_eq!(format!("{}\n", local), date_command_str); + // } else { + // // we are in a "Spring forward gap" due to DST, and so date also returns "" + // assert_eq!("", date_command_str); + // } + + // This is used while a decision is made whether the `date` output needs to + // be exactly matched, or whether MappedLocalTime::Ambiguous should be handled + // differently + + let date = NaiveDate::from_ymd_opt(dt.year(), dt.month(), dt.day()).unwrap(); + match Local.from_local_datetime(&date.and_hms_opt(dt.hour(), 5, 1).unwrap()) { + chrono::MappedLocalTime::Ambiguous(a, b) => assert!( + format!("{}\n", a) == date_command_str || format!("{}\n", b) == date_command_str + ), + chrono::MappedLocalTime::Single(a) => { + assert_eq!(format!("{}\n", a), date_command_str); + } + chrono::MappedLocalTime::None => { + assert_eq!("", date_command_str); + } + } +} + +/// path to Unix `date` command. Should work on most Linux and Unixes. Not the +/// path for MacOS (/bin/date) which uses a different version of `date` with +/// different arguments (so it won't run which is okay). +/// for testing only +#[allow(dead_code)] +#[cfg(not(target_os = "aix"))] +const DATE_PATH: &str = "/usr/bin/date"; +#[allow(dead_code)] +#[cfg(target_os = "aix")] +const DATE_PATH: &str = "/opt/freeware/bin/date"; + +#[cfg(test)] +/// test helper to sanity check the date command behaves as expected +/// asserts the command succeeded +fn assert_run_date_version() { + // note environment variable `LANG` + match std::env::var_os("LANG") { + Some(lang) => eprintln!("LANG: {:?}", lang), + None => eprintln!("LANG not set"), + } + let out = process::Command::new(DATE_PATH).arg("--version").output().unwrap(); + let stdout = String::from_utf8(out.stdout).unwrap(); + let stderr = String::from_utf8(out.stderr).unwrap(); + // note the `date` binary version + eprintln!("command: {:?} --version\nstdout: {:?}\nstderr: {:?}", DATE_PATH, stdout, stderr); + assert!(out.status.success(), "command failed: {:?} --version", DATE_PATH); +} + +#[test] +fn try_verify_against_date_command() { + if !path::Path::new(DATE_PATH).exists() { + eprintln!("date command {:?} not found, skipping", DATE_PATH); + return; + } + assert_run_date_version(); + + eprintln!( + "Run command {:?} for every hour from 1975 to 2077, skipping some years...", + DATE_PATH, + ); + + let mut children = vec![]; + for year in [1975, 1976, 1977, 2020, 2021, 2022, 2073, 2074, 2075, 2076, 2077].iter() { + children.push(thread::spawn(|| { + let mut date = NaiveDate::from_ymd_opt(*year, 1, 1).unwrap().and_time(NaiveTime::MIN); + let end = NaiveDate::from_ymd_opt(*year + 1, 1, 1).unwrap().and_time(NaiveTime::MIN); + while date <= end { + verify_against_date_command_local(DATE_PATH, date); + date += chrono::TimeDelta::try_hours(1).unwrap(); + } + })); + } + for child in children { + // Wait for the thread to finish. Returns a result. + let _ = child.join(); + } +} + +#[cfg(target_os = "linux")] +fn verify_against_date_command_format_local(path: &'static str, dt: NaiveDateTime) { + let required_format = + "d%d D%D F%F H%H I%I j%j k%k l%l m%m M%M q%q S%S T%T u%u U%U w%w W%W X%X y%y Y%Y z%:z"; + // a%a - depends from localization + // A%A - depends from localization + // b%b - depends from localization + // B%B - depends from localization + // h%h - depends from localization + // c%c - depends from localization + // p%p - depends from localization + // r%r - depends from localization + // x%x - fails, date is dd/mm/yyyy, chrono is dd/mm/yy, same as %D + // Z%Z - too many ways to represent it, will most likely fail + + let output = process::Command::new(path) + .env("LANG", "c") + .env("LC_ALL", "c") + .arg("-d") + .arg(format!( + "{}-{:02}-{:02} {:02}:{:02}:{:02}", + dt.year(), + dt.month(), + dt.day(), + dt.hour(), + dt.minute(), + dt.second() + )) + .arg(format!("+{}", required_format)) + .output() + .unwrap(); + + let date_command_str = String::from_utf8(output.stdout).unwrap(); + let date = NaiveDate::from_ymd_opt(dt.year(), dt.month(), dt.day()).unwrap(); + let ldt = Local + .from_local_datetime(&date.and_hms_opt(dt.hour(), dt.minute(), dt.second()).unwrap()) + .unwrap(); + let formatted_date = format!("{}\n", ldt.format(required_format)); + assert_eq!(date_command_str, formatted_date); +} + +#[test] +#[cfg(target_os = "linux")] +fn try_verify_against_date_command_format() { + if !path::Path::new(DATE_PATH).exists() { + eprintln!("date command {:?} not found, skipping", DATE_PATH); + return; + } + assert_run_date_version(); + + let mut date = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_opt(12, 11, 13).unwrap(); + while date.year() < 2008 { + verify_against_date_command_format_local(DATE_PATH, date); + date = date + Days::new(55); + } +} diff --git a/third_party/rust/chrono/tests/wasm.rs b/third_party/rust/chrono/tests/wasm.rs index 275d120d3b5..ceb9b3de97d 100644 --- a/third_party/rust/chrono/tests/wasm.rs +++ b/third_party/rust/chrono/tests/wasm.rs @@ -1,67 +1,89 @@ -#[cfg(all(test, feature = "wasmbind"))] -mod test { - extern crate chrono; - extern crate wasm_bindgen_test; +//! Run this test with: +//! `env TZ="$(date +%z)" NOW="$(date +%s)" wasm-pack test --node -- --features wasmbind` +//! +//! The `TZ` and `NOW` variables are used to compare the results inside the WASM environment with +//! the host system. +//! The check will fail if the local timezone does not match one of the timezones defined below. - use self::chrono::prelude::*; - use self::wasm_bindgen_test::*; +#![cfg(all( + target_arch = "wasm32", + feature = "wasmbind", + feature = "clock", + not(any(target_os = "emscripten", target_os = "wasi")) +))] - #[wasm_bindgen_test] - fn now() { - let utc: DateTime = Utc::now(); - let local: DateTime = Local::now(); +use chrono::prelude::*; +use wasm_bindgen_test::*; - // Ensure time set by the test script is correct - let now = env!("NOW"); - let actual = Utc.datetime_from_str(&now, "%s").unwrap(); - let diff = utc - actual; - assert!( - diff < chrono::Duration::minutes(5), - "expected {} - {} == {} < 5m (env var: {})", - utc, - actual, - diff, - now, - ); +#[wasm_bindgen_test] +fn now() { + let utc: DateTime = Utc::now(); + let local: DateTime = Local::now(); - let tz = env!("TZ"); - eprintln!("testing with tz={}", tz); + // Ensure time set by the test script is correct + let now = env!("NOW"); + let actual = NaiveDateTime::parse_from_str(&now, "%s").unwrap().and_utc(); + let diff = utc - actual; + assert!( + diff < chrono::TimeDelta::try_minutes(5).unwrap(), + "expected {} - {} == {} < 5m (env var: {})", + utc, + actual, + diff, + now, + ); - // Ensure offset retrieved when getting local time is correct - let expected_offset = match tz { - "ACST-9:30" => FixedOffset::east(19 * 30 * 60), - "Asia/Katmandu" => FixedOffset::east(23 * 15 * 60), // No DST thankfully - "EDT" | "EST4" | "-0400" => FixedOffset::east(-4 * 60 * 60), - "EST" | "-0500" => FixedOffset::east(-5 * 60 * 60), - "UTC0" | "+0000" => FixedOffset::east(0), - tz => panic!("unexpected TZ {}", tz), - }; - assert_eq!( - &expected_offset, - local.offset(), - "expected: {:?} local: {:?}", - expected_offset, - local.offset(), - ); - } + let tz = env!("TZ"); + eprintln!("testing with tz={}", tz); - #[wasm_bindgen_test] - fn from_is_exact() { - let now = js_sys::Date::new_0(); - - let dt = DateTime::::from(now.clone()); - - assert_eq!(now.get_time() as i64, dt.timestamp_millis()); - } - - #[wasm_bindgen_test] - fn local_from_local_datetime() { - let now = Local::now(); - let ndt = now.naive_local(); - let res = match Local.from_local_datetime(&ndt).single() { - Some(v) => v, - None => panic! {"Required for test!"}, - }; - assert_eq!(now, res); - } + // Ensure offset retrieved when getting local time is correct + let expected_offset = match tz { + "ACST-9:30" => FixedOffset::east_opt(19 * 30 * 60).unwrap(), + "Asia/Katmandu" => FixedOffset::east_opt(23 * 15 * 60).unwrap(), // No DST thankfully + "EDT" | "EST4" | "-0400" => FixedOffset::east_opt(-4 * 60 * 60).unwrap(), + "EST" | "-0500" => FixedOffset::east_opt(-5 * 60 * 60).unwrap(), + "UTC0" | "+0000" => FixedOffset::east_opt(0).unwrap(), + tz => panic!("unexpected TZ {}", tz), + }; + assert_eq!( + &expected_offset, + local.offset(), + "expected: {:?} local: {:?}", + expected_offset, + local.offset(), + ); +} + +#[wasm_bindgen_test] +fn from_is_exact() { + let now = js_sys::Date::new_0(); + + let dt = DateTime::::from(now.clone()); + + assert_eq!(now.get_time() as i64, dt.timestamp_millis()); +} + +#[wasm_bindgen_test] +fn local_from_local_datetime() { + let now = Local::now(); + let ndt = now.naive_local(); + let res = match Local.from_local_datetime(&ndt).single() { + Some(v) => v, + None => panic! {"Required for test!"}, + }; + assert_eq!(now, res); +} + +#[wasm_bindgen_test] +fn convert_all_parts_with_milliseconds() { + let time: DateTime = "2020-12-01T03:01:55.974Z".parse().unwrap(); + let js_date = js_sys::Date::from(time); + + assert_eq!(js_date.get_utc_full_year(), 2020); + assert_eq!(js_date.get_utc_month(), 11); // months are numbered 0..=11 + assert_eq!(js_date.get_utc_date(), 1); + assert_eq!(js_date.get_utc_hours(), 3); + assert_eq!(js_date.get_utc_minutes(), 1); + assert_eq!(js_date.get_utc_seconds(), 55); + assert_eq!(js_date.get_utc_milliseconds(), 974); } diff --git a/third_party/rust/chrono/tests/win_bindings.rs b/third_party/rust/chrono/tests/win_bindings.rs new file mode 100644 index 00000000000..d3c2bc3ae38 --- /dev/null +++ b/third_party/rust/chrono/tests/win_bindings.rs @@ -0,0 +1,28 @@ +#![cfg(all(windows, feature = "clock", feature = "std"))] + +use std::fs; +use windows_bindgen::bindgen; + +#[test] +fn gen_bindings() { + let input = "src/offset/local/win_bindings.txt"; + let output = "src/offset/local/win_bindings.rs"; + let existing = fs::read_to_string(output).unwrap(); + + bindgen(["--no-deps", "--etc", input]); + + // Check the output is the same as before. + // Depending on the git configuration the file may have been checked out with `\r\n` newlines or + // with `\n`. Compare line-by-line to ignore this difference. + let mut new = fs::read_to_string(output).unwrap(); + if existing.contains("\r\n") && !new.contains("\r\n") { + new = new.replace("\n", "\r\n"); + } else if !existing.contains("\r\n") && new.contains("\r\n") { + new = new.replace("\r\n", "\n"); + } + + similar_asserts::assert_eq!(existing, new); + if !new.lines().eq(existing.lines()) { + panic!("generated file `{}` is changed.", output); + } +} diff --git a/third_party/rust/iana-time-zone-haiku/.cargo-checksum.json b/third_party/rust/iana-time-zone-haiku/.cargo-checksum.json new file mode 100644 index 00000000000..11e259e18c5 --- /dev/null +++ b/third_party/rust/iana-time-zone-haiku/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"8f06eca1c0e108d0422687eeb87030520ae7bd956efc92352599cc9cf079d9a3","LICENSE-APACHE":"696759d65dfe558ff7d9f031c76db19ec5c0767470fb67c4e8d990820d1e99c9","LICENSE-MIT":"da28ccc6b158fc2d8cccc74e99794b1cff1d29bd7bbeb019442fcf0c04c6cad9","README.md":"5b1ad9309b716374cc1bdcd025f525fac31b2f413e6c4d311e207fa6b1f96a83","build.rs":"10304831100a60c1c2b990762dcfeb47dae8342cf9b54595bec94884e7de5784","src/implementation.cc":"66d2ecfe58ec543e27a6fb3a96526a07cd1ac43a2370344f856529e5a112ce0f","src/lib.rs":"e58db019554bd372f0a187f8f51f96624cdf21bcef507de2093e1d49ca0787cd"},"package":"f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"} \ No newline at end of file diff --git a/third_party/rust/iana-time-zone-haiku/Cargo.toml b/third_party/rust/iana-time-zone-haiku/Cargo.toml new file mode 100644 index 00000000000..6b0ca3df282 --- /dev/null +++ b/third_party/rust/iana-time-zone-haiku/Cargo.toml @@ -0,0 +1,34 @@ +# 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 = "2018" +name = "iana-time-zone-haiku" +version = "0.1.2" +authors = ["René Kijewski "] +description = "iana-time-zone support crate for Haiku OS" +readme = "README.md" +keywords = [ + "IANA", + "time", +] +categories = [ + "date-and-time", + "internationalization", + "os", +] +license = "MIT OR Apache-2.0" +repository = "https://github.com/strawlab/iana-time-zone" + +[dependencies] + +[build-dependencies.cc] +version = "1.0.79" diff --git a/third_party/rust/iana-time-zone-haiku/LICENSE-APACHE b/third_party/rust/iana-time-zone-haiku/LICENSE-APACHE new file mode 100644 index 00000000000..18af3f6fe37 --- /dev/null +++ b/third_party/rust/iana-time-zone-haiku/LICENSE-APACHE @@ -0,0 +1,201 @@ + 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 2020 Andrew Straw + +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/third_party/rust/iana-time-zone-haiku/LICENSE-MIT b/third_party/rust/iana-time-zone-haiku/LICENSE-MIT new file mode 100644 index 00000000000..8f8dd90d887 --- /dev/null +++ b/third_party/rust/iana-time-zone-haiku/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2020 Andrew D. Straw + +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/third_party/rust/iana-time-zone-haiku/README.md b/third_party/rust/iana-time-zone-haiku/README.md new file mode 100644 index 00000000000..87450cdb901 --- /dev/null +++ b/third_party/rust/iana-time-zone-haiku/README.md @@ -0,0 +1,8 @@ +# iana-time-zone-haiku + +[![Crates.io](https://img.shields.io/crates/v/iana-time-zone-haiku.svg)](https://crates.io/crates/iana-time-zone-haiku) +[![Documentation](https://docs.rs/iana-time-zone/badge.svg)](https://docs.rs/iana-time-zone/) +[![Crate License](https://img.shields.io/crates/l/iana-time-zone-haiku.svg)](https://crates.io/crates/iana-time-zone-haiku) +[![build](https://github.com/strawlab/iana-time-zone/workflows/build/badge.svg?branch=main)](https://github.com/strawlab/iana-time-zone/actions?query=branch%3Amain) + +[iana-time-zone](https://github.com/strawlab/iana-time-zone) support crate for Haiku OS. diff --git a/third_party/rust/iana-time-zone-haiku/build.rs b/third_party/rust/iana-time-zone-haiku/build.rs new file mode 100644 index 00000000000..7ef5d933900 --- /dev/null +++ b/third_party/rust/iana-time-zone-haiku/build.rs @@ -0,0 +1,22 @@ +use std::env; + +fn main() { + cc::Build::new() + .warnings(false) + .cpp(true) + .file("src/implementation.cc") + .flag_if_supported("-std=c++11") + .compile("tz_haiku"); + + println!("cargo:rerun-if-changed=src/lib.rs"); + println!("cargo:rerun-if-changed=src/implementation.cc"); + println!("cargo:rerun-if-changed=src/interface.h"); + + let target = env::var_os("TARGET").expect("cargo should set TARGET env var"); + let target = target + .to_str() + .expect("TARGET env var should be valid UTF-8"); + if target.contains("haiku") { + println!("cargo:rustc-link-lib=be"); + } +} diff --git a/third_party/rust/iana-time-zone-haiku/src/implementation.cc b/third_party/rust/iana-time-zone-haiku/src/implementation.cc new file mode 100644 index 00000000000..d1c92cd3b74 --- /dev/null +++ b/third_party/rust/iana-time-zone-haiku/src/implementation.cc @@ -0,0 +1,67 @@ +#include + +#ifdef __HAIKU__ + +#include + +#include +#include +#include +#include + +extern "C" { + +size_t iana_time_zone_haiku_get_tz(char *buf, size_t buf_size) { + try { + static_assert(sizeof(char) == sizeof(uint8_t), "Illegal char size"); + + if (buf_size == 0) { + return 0; + } + + // `BLocaleRoster::Default()` returns a reference to a statically allocated object. + // https://github.com/haiku/haiku/blob/8f16317/src/kits/locale/LocaleRoster.cpp#L143-L147 + BLocaleRoster *locale_roster(BLocaleRoster::Default()); + if (!locale_roster) { + return 0; + } + + BTimeZone tz(NULL, NULL); + if (locale_roster->GetDefaultTimeZone(&tz) != B_OK) { + return 0; + } + + BString bname(tz.ID()); + int32_t ilength(bname.Length()); + if (ilength <= 0) { + return 0; + } + + size_t length(ilength); + if (length > buf_size) { + return 0; + } + + // BString::String() returns a borrowed string. + // https://www.haiku-os.org/docs/api/classBString.html#ae4fe78b06c8e3310093b80305e14ba87 + const char *sname(bname.String()); + if (!sname) { + return 0; + } + + std::memcpy(buf, sname, length); + return length; + } catch (...) { + return 0; + } +} +} // extern "C" + +#else + +extern "C" { + +size_t iana_time_zone_haiku_get_tz(char *buf, size_t buf_size) { return 0; } +} // extern "C" + +#endif diff --git a/third_party/rust/iana-time-zone-haiku/src/lib.rs b/third_party/rust/iana-time-zone-haiku/src/lib.rs new file mode 100644 index 00000000000..8ce821faa33 --- /dev/null +++ b/third_party/rust/iana-time-zone-haiku/src/lib.rs @@ -0,0 +1,71 @@ +#![warn(clippy::all)] +#![warn(clippy::cargo)] +#![warn(clippy::undocumented_unsafe_blocks)] +#![allow(unknown_lints)] +#![warn(missing_copy_implementations)] +#![warn(missing_debug_implementations)] +#![warn(missing_docs)] +#![warn(rust_2018_idioms)] +#![warn(trivial_casts, trivial_numeric_casts)] +#![warn(unsafe_op_in_unsafe_fn)] +#![warn(unused_qualifications)] +#![warn(variant_size_differences)] + +//! # iana-time-zone-haiku +//! +//! [![Crates.io](https://img.shields.io/crates/v/iana-time-zone-haiku.svg)](https://crates.io/crates/iana-time-zone-haiku) +//! [![Documentation](https://docs.rs/iana-time-zone/badge.svg)](https://docs.rs/iana-time-zone/) +//! [![Crate License](https://img.shields.io/crates/l/iana-time-zone-haiku-haiku.svg)](https://crates.io/crates/iana-time-zone-haiku) +//! [![build](https://github.com/strawlab/iana-time-zone/workflows/build/badge.svg?branch=main)](https://github.com/strawlab/iana-time-zone/actions?query=branch%3Amain) +//! +//! [iana-time-zone](https://github.com/strawlab/iana-time-zone) support crate for Haiku OS. + +use std::os::raw::c_char; + +extern "C" { + fn iana_time_zone_haiku_get_tz(buf: *mut c_char, buf_size: usize) -> usize; +} + +/// Get the current IANA time zone as a string. +/// +/// On Haiku platforms this function will return [`Some`] with the timezone string +/// or [`None`] if an error occurs. On all other platforms, [`None`] is returned. +/// +/// # Examples +/// +/// ``` +/// let timezone = iana_time_zone_haiku::get_timezone(); +/// ``` +#[must_use] +pub fn get_timezone() -> Option { + // The longest name in the IANA time zone database is 25 ASCII characters long. + let mut buf = [0u8; 32]; + // SAFETY: a valid, aligned, non-NULL pointer and length are given which + // point to a single allocation. + let len = unsafe { + let buf_size = buf.len(); + let buf_ptr = buf.as_mut_ptr().cast::(); + iana_time_zone_haiku_get_tz(buf_ptr, buf_size) + }; + // The name should not be empty, or excessively long. + match buf.get(..len)? { + b"" => None, + s => Some(std::str::from_utf8(s).ok()?.to_owned()), + } +} + +#[cfg(test)] +mod tests { + #[test] + #[cfg(not(target_os = "haiku"))] + fn test_fallback_on_non_haiku_platforms() { + assert!(super::get_timezone().is_none()); + } + + #[test] + #[cfg(target_os = "haiku")] + fn test_retrieve_time_zone_on_haiku_platforms() { + let timezone = super::get_timezone().unwrap(); + assert!(!timezone.is_empty()); + } +} diff --git a/third_party/rust/iana-time-zone/.cargo-checksum.json b/third_party/rust/iana-time-zone/.cargo-checksum.json new file mode 100644 index 00000000000..a7877325df3 --- /dev/null +++ b/third_party/rust/iana-time-zone/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"CHANGELOG.md":"467a75b20956c8201acdad52b44b1cd146913a4df9f5282e47bf144dea2878da","Cargo.lock":"0cd9cc06007ba8411bb0c1202ac7a587b261aa355287f54737ca0d5c0554f160","Cargo.toml":"b5fcc4b21e07e9f085a5ed540134e01f107e0af2e9358e3cb49aaebb2d70bfae","LICENSE-APACHE":"696759d65dfe558ff7d9f031c76db19ec5c0767470fb67c4e8d990820d1e99c9","LICENSE-MIT":"da28ccc6b158fc2d8cccc74e99794b1cff1d29bd7bbeb019442fcf0c04c6cad9","README.md":"22f9a823adec27aca10eb6dfb5e4e1e9d485e1d5f78348f788bfc81dbee2787f","bindings.txt":"6e9b2e58051bf22fb39b38dc04e54bf75b6204fbd97df06bc58617b1b5d14461","deny.toml":"bde861b1ca6304017f8a57268058aa99ad33d89b1498bba68086e65cca7fe7a2","examples/get_timezone.rs":"c4db7b1cc71c7b3728ddd70e76c0dbd40239c6b1b8c705cb63476757d3177dec","examples/get_timezone_loop.rs":"5e9da42eabd529f5f8d04acc5c1eb84575b9ed38a9226e76e91a1717089b016e","examples/stress-test.rs":"3ad469de5a650389699c9ffe5fd78af2bdd46e7140cc05391c60d793fc6f8e98","src/ffi_utils.rs":"4a49637f60e4d2ab1afecb168e884cee8137a32037d440f9f032196c9fd6f159","src/lib.rs":"0982d7d2e2228b3b4d6a62a4dd5e28ea22086074edce0f0b07ce72fefccacdbf","src/platform.rs":"7847381ca6976d9f39ce84759a3b0d7aabe31eae7a88bc20d7fb99bc376782b0","src/tz_aix.rs":"86fd048ff14062c53d1aa770ee715961f7b1099337453ca2eb297a74fc59f673","src/tz_android.rs":"3da37f1b87f87a0ed215734f2b373b2d187bcf49386adfe8bfff207f9a5e8fe2","src/tz_darwin.rs":"8938e34f1f7c4cf813deb8d008ea6ee130dcb30ada6db3d068c3f0990f81165b","src/tz_freebsd.rs":"4247af5c6dd0712705186ed54d789193c64139f707af316d4fde86aa1e2a1b13","src/tz_haiku.rs":"761afd80301683a44bf937bbf6b13c5c792af42ed7037623bbeccbab6d0aa8fc","src/tz_illumos.rs":"375ae951d1417f63e6d77c9add7f7f646f24c0054cb8407bd4b9f06907494888","src/tz_linux.rs":"fc62ae0275e745c4fc6552c25f70812e37e91d78a16d9a40e5688331a7af04ed","src/tz_netbsd.rs":"ec278bbe1cb5f648c063ec23bff6081146454b9f6aa3918b9ca50b8804d5838f","src/tz_ohos.rs":"f1db259b38f6890ec3c7b357e15d10f2aa166dc84394bac58fa62cb31d4559dc","src/tz_wasm32_unknown.rs":"10aa33caa86645a16e2126fe1a86dda2b57f63caa9addcd726245fbc9657dc1b","src/tz_windows.rs":"804c11a3c638c93626459612cd444df64e9962668d1e8cd6f0774301c00c9529","src/windows_bindings.rs":"d58d11d01f5f5335febb91e83f32956801824553e2b26216bfd16e6a0c87803c"},"package":"b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"} \ No newline at end of file diff --git a/third_party/rust/iana-time-zone/CHANGELOG.md b/third_party/rust/iana-time-zone/CHANGELOG.md new file mode 100644 index 00000000000..4944072b43a --- /dev/null +++ b/third_party/rust/iana-time-zone/CHANGELOG.md @@ -0,0 +1,353 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.1.63] - 2025-03-31 +### Changes +- Bump MSRV (minimum supported rust version) to 1.62 ([#131](https://github.com/strawlab/iana-time-zone/pull/131)) +- Bump `windows-core` to `0.56-0.61` range ([#131](https://github.com/strawlab/iana-time-zone/pull/131), [#133](https://github.com/strawlab/iana-time-zone/pull/133)) + +## [0.1.62] - 2025-03-24 +### Changed +- Bump MSRV (minimum supported rust version) to 1.61 ([#157](https://github.com/strawlab/iana-time-zone/pull/157)) +- Update to rust edition 2021 ([#161](https://github.com/strawlab/iana-time-zone/pull/161)) +- Address high and medium severity zizmor findings ([#163](https://github.com/strawlab/iana-time-zone/pull/163)) + +### Added +- Added support for tvOS, watchOS and visionOS ([#146](https://github.com/strawlab/iana-time-zone/pull/146)). +- implement OpenHarmony support ([#150](https://github.com/strawlab/iana-time-zone/pull/150)) + +## [0.1.61] - 2024-09-16 +### Changed + +- Depend on wasm-bindgen 0.2.89 or higher ([#134](https://github.com/strawlab/iana-time-zone/pull/134)) +- Do not use wasm_bindgen in wasm32-unknown-emscripten environment ([#130](https://github.com/strawlab/iana-time-zone/pull/130)) + +## [0.1.60] - 2024-02-03 +### Changed +- correct `windows-core` dependency version ([#127](https://github.com/strawlab/iana-time-zone/pull/127)) + +## [0.1.59] - 2023-12-30 +### Changed +- update `windows` dependency ([#125](https://github.com/strawlab/iana-time-zone/pull/125)) + +## [0.1.58] - 2023-10-17 +### Added +- use windows-core with embedded bindings via windows-bindgen ([#117](https://github.com/strawlab/iana-time-zone/pull/117)) +- implement GNU Hurd support ([#121](https://github.com/strawlab/iana-time-zone/pull/121)) +- implement AIX support ([#57](https://github.com/strawlab/iana-time-zone/pull/57)) + +## [0.1.57] - 2023-06-07 +### Added +- implement OpenWRT support ([#109](https://github.com/strawlab/iana-time-zone/pull/109)) + +## [0.1.56] - 2023-04-03 +### Changed +- update `windows` dependency ([#102](https://github.com/strawlab/iana-time-zone/pull/102)) + +## [0.1.55] - 2023-03-30 +### Changed +- update `windows` dependency ([#101](https://github.com/strawlab/iana-time-zone/pull/101)) + +## [0.1.54] - 2023-03-21 +### Changed +- replace `winapi` dependency with `windows` ([#97](https://github.com/strawlab/iana-time-zone/pull/97)) +- bump msrv to 1.48 ([#91](https://github.com/strawlab/iana-time-zone/pull/91)) + +## [0.1.53] - 2022-10-28 +### Fixed +- remove lint causing breakage on rust 1.45-1.51 ([#84](https://github.com/strawlab/iana-time-zone/pull/84)) + +## [0.1.52] - 2022-10-28 +### Fixed +- fix for NixOS ([#81](https://github.com/strawlab/iana-time-zone/pull/81)) + +### Changed +- allow building the haiku crate on other hosts([#75](https://github.com/strawlab/iana-time-zone/pull/75)) +- various improvements in continuous integration and source quality + ([#76](https://github.com/strawlab/iana-time-zone/pull/76)), + ([#77](https://github.com/strawlab/iana-time-zone/pull/77)), + ([#78](https://github.com/strawlab/iana-time-zone/pull/78)), + ([#81](https://github.com/strawlab/iana-time-zone/pull/81)) + +## [0.1.51] - 2022-10-08 +### Changed +- bump MSRV to 1.38 ([#70](https://github.com/strawlab/iana-time-zone/pull/70)) +- Refactor Android property key CStr construction to add tests ([#69](https://github.com/strawlab/iana-time-zone/pull/69)) +- Refactor MacOS implementation a lot ([#67](https://github.com/strawlab/iana-time-zone/pull/67)) + +### Added +- Implement for Haiku ([#66](https://github.com/strawlab/iana-time-zone/pull/66)) + +### Fixed +- Fix spelling of 'initialized' in sync::Once statics ([#63](https://github.com/strawlab/iana-time-zone/pull/63)) + +## [0.1.50] - 2022-09-23 +### Fixed +- Reduce MSRV for Android again ([#62](https://github.com/strawlab/iana-time-zone/pull/62)) + +## [0.1.49] - 2022-09-22 +### Changed +- `once_cell` dependency is not needed ([#61](https://github.com/strawlab/iana-time-zone/pull/61)) + +## [0.1.48] - 2022-09-12 +### Changed +- Downgrade requirements for WASM dependencies ([#58](https://github.com/strawlab/iana-time-zone/pull/58)) +- Reduce MSRV for Tier 1 platforms to 1.31 ([#59](https://github.com/strawlab/iana-time-zone/pull/59)) + +## [0.1.47] - 2022-08-30 +### Changed +- Update `android_system_properties` to v0.1.5 to run 9786% faster (YMMV) ([#56](https://github.com/strawlab/iana-time-zone/pull/56)) + +## [0.1.46] - 2022-08-18 +### Added +- Implement for Solaris ([#55](https://github.com/strawlab/iana-time-zone/pull/55)) + +## [0.1.45] - 2022-08-16 +### Fixed +- Fix potential use after free in MacOS / iOS ([#54](https://github.com/strawlab/iana-time-zone/pull/54), [RUSTSEC-2022-0049](https://rustsec.org/advisories/RUSTSEC-2022-0049.html)) +- Fix typos in README ([#53](https://github.com/strawlab/iana-time-zone/pull/53)) + +## [0.1.44] - 2022-08-11 +### Fixed +- "/etc/localtime" may be relative link ([#49](https://github.com/strawlab/iana-time-zone/pull/49)) + +## [0.1.43] - 2022-08-11 +### Changed +- Use `core-foundation-sys` instead of `core-foundation` ([#50](https://github.com/strawlab/iana-time-zone/pull/50)) + +## [0.1.42] - 2022-08-10 +### Fixed +- Fix implementation for Redhat based distros ([#48](https://github.com/strawlab/iana-time-zone/pull/48)) + +## [0.1.41] - 2022-08-02 +### Added +- Add `fallback` feature ([#46](https://github.com/strawlab/iana-time-zone/pull/46)) + +## [0.1.40] - 2022-07-29 +### Added +- Implement for Android ([#45](https://github.com/strawlab/iana-time-zone/pull/45)) + +## [0.1.38] - 2022-07-27 +### Added +- Implement illumos ([#44](https://github.com/strawlab/iana-time-zone/pull/44)) +### Changed +- Update examples in README + +## [0.1.37] - 2022-07-23 +### Added +- Support iOS ([#41](https://github.com/strawlab/iana-time-zone/pull/41)) +### Changed +- Implement `std::err::source()`, format `IoError` ([#42](https://github.com/strawlab/iana-time-zone/pull/42)) + +## [0.1.36] - 2022-07-21 +### Fixed +- Fail to compile for WASI ([#40](https://github.com/strawlab/iana-time-zone/pull/40)) + +## [0.1.35] - 2022-06-29 +### Added +- Implement for FreeBSD, NetBSD, OpenBSD and Dragonfly ([#39](https://github.com/strawlab/iana-time-zone/pull/39)) + +## [0.1.34] - 2022-06-29 +### Added +- Implement for wasm32 ([#38](https://github.com/strawlab/iana-time-zone/pull/38)) + +## [0.1.33] - 2022-04-15 +### Changed +- Use `winapi` crate instead of `windows` crate ([#35](https://github.com/strawlab/iana-time-zone/pull/35)) + +## [0.1.32] - 2022-04-06 +### Changed +- Update `windows` requirement from 0.34 to 0.35 ([#34](https://github.com/strawlab/iana-time-zone/pull/34)) + +## [0.1.31] - 2022-03-16 +### Changed +- Update `windows` requirement from 0.33 to 0.34 ([#33](https://github.com/strawlab/iana-time-zone/pull/33)) + +## [0.1.30] - 2022-02-28 +### Changed +- Fewer string allocations ([#32](https://github.com/strawlab/iana-time-zone/pull/32)) + +## [0.1.29] - 2022-02-25 +### Changed +- Update `windows` requirement from 0.32 to 0.33 ([#31](https://github.com/strawlab/iana-time-zone/pull/31)) + +## [0.1.28] - 2022-02-04 +### Changed +- Update `windows` requirement from 0.30 to 0.32 ([#30](https://github.com/strawlab/iana-time-zone/pull/30)) + +## [0.1.27] - 2022-01-14 +### Changed +- Update `windows` requirement from 0.29 to 0.30 ([#29](https://github.com/strawlab/iana-time-zone/pull/29)) + +## [0.1.26] - 2021-12-23 +### Changed +- Update `windows` requirement from 0.28 to 0.29 ([#28](https://github.com/strawlab/iana-time-zone/pull/28)) + +## [0.1.25] - 2021-11-18 +### Changed +- Update `windows` requirement from 0.27 to 0.28 ([#27](https://github.com/strawlab/iana-time-zone/pull/27)) + +## [0.1.24] - 2021-11-16 +### Changed +- Update `windows` requirement from 0.26 to 0.27 ([#26](https://github.com/strawlab/iana-time-zone/pull/26)) + +## [0.1.23] - 2021-11-12 +### Changed +- Update `windows` requirement from 0.25 to 0.26 ([#25](https://github.com/strawlab/iana-time-zone/pull/25)) + +## [0.1.22] - 2021-11-08 +### Changed +- Update `windows` requirement from 0.24 to 0.25 ([#24](https://github.com/strawlab/iana-time-zone/pull/24)) + +## [0.1.21] - 2021-11-02 +### Changed +- Update `windows` requirement from 0.23 to 0.24 ([#23](https://github.com/strawlab/iana-time-zone/pull/23)) + +## [0.1.20] - 2021-10-29 +### Changed +- Update `windows` requirement from 0.21 to 0.23 ([#22](https://github.com/strawlab/iana-time-zone/pull/22)) + +## [0.1.19] - 2021-09-27 +### Changed +- Update `windows` requirement from 0.19 to 0.21 ([#18](https://github.com/strawlab/iana-time-zone/pull/18), [#20](https://github.com/strawlab/iana-time-zone/pull/20)) +- Update `chrono-tz` requirement from 0.5 to 0.6 ([#19](https://github.com/strawlab/iana-time-zone/pull/19)) + +## [0.1.18] - 2021-08-23 +### Changed +- Update `windows` requirement from 0.18 to 0.19 ([#17](https://github.com/strawlab/iana-time-zone/pull/17)) + +## [0.1.16] - 2021-07-26 +### Changed +- Update `windows` requirement from 0.17 to 0.18 ([#16](https://github.com/strawlab/iana-time-zone/pull/16)) + +## [0.1.15] - 2021-07-08 +### Changed +- Update `windows` requirement from 0.14 to 0.17 ([#15](https://github.com/strawlab/iana-time-zone/pull/15)) + +## [0.1.14] - 2021-07-07 +### Changed +- Update `windows` requirement from 0.13 to 0.14 ([#14](https://github.com/strawlab/iana-time-zone/pull/14)) + +## [0.1.13] - 2021-06-28 +### Changed +- Update `windows` requirement from 0.12 to 0.13 ([#13](https://github.com/strawlab/iana-time-zone/pull/13)) + +## [0.1.12] - 2021-06-28 +### Changed +- Update `windows` requirement from 0.11 to 0.12 ([#12](https://github.com/strawlab/iana-time-zone/pull/12)) + +## [0.1.11] - 2021-06-12 +### Changed +- Update `windows` requirement from 0.10 to 0.11 ([#11](https://github.com/strawlab/iana-time-zone/pull/11)) + +## [0.1.10] - 2021-05-13 +### Changed +- Update `windows` requirement from 0.9 to 0.10 ([#10](https://github.com/strawlab/iana-time-zone/pull/10)) + +## [0.1.9] - 2021-04-28 +### Changed +- Update `windows` requirement from 0.8 to 0.9 ([#8](https://github.com/strawlab/iana-time-zone/pull/8)) + +## [0.1.8] - 2021-04-13 +### Changed +- Update `windows` requirement from 0.7 to 0.8 ([#7](https://github.com/strawlab/iana-time-zone/pull/7)) + +## [0.1.7] - 2021-03-30 +### Changed +- Update `windows` requirement from 0.6 to 0.7 ([#6](https://github.com/strawlab/iana-time-zone/pull/6)) + +## [0.1.6] - 2021-03-24 +### Changed +- Update `windows` requirement from 0.5 to 0.6 ([#5](https://github.com/strawlab/iana-time-zone/pull/5)) + +## [0.1.5] - 2021-03-20 +### Changed +- Update `windows` requirement from 0.4 to 0.5 ([#4](https://github.com/strawlab/iana-time-zone/pull/4)) + +## [0.1.4] - 2021-03-11 +### Changed +- Update `windows` requirement from 0.3 to 0.4 ([#3](https://github.com/strawlab/iana-time-zone/pull/3)) + +## [0.1.3] - 2021-02-22 +### Changed +- Use `windows` crate instead of `winrt` + +## [0.1.2] - 2020-10-09 +### Changed +- Update `core-foundation` requirement from 0.7 to 0.9 ([#1](https://github.com/strawlab/iana-time-zone/pull/1)) + +## [0.1.1] - 2020-06-27 +### Changed +- Update `core-foundation` requirement from 0.5 to 0.7 + +## [0.1.0] - 2020-06-27 +### Added +- Implement for Linux, Windows, MacOS + +[0.1.63]: https://github.com/strawlab/iana-time-zone/releases/tag/v0.1.63 +[0.1.62]: https://github.com/strawlab/iana-time-zone/releases/tag/v0.1.62 +[0.1.61]: https://github.com/strawlab/iana-time-zone/releases/tag/v0.1.61 +[0.1.60]: https://github.com/strawlab/iana-time-zone/releases/tag/v0.1.60 +[0.1.59]: https://github.com/strawlab/iana-time-zone/releases/tag/v0.1.59 +[0.1.58]: https://github.com/strawlab/iana-time-zone/releases/tag/v0.1.58 +[0.1.57]: https://github.com/strawlab/iana-time-zone/releases/tag/v0.1.57 +[0.1.56]: https://github.com/strawlab/iana-time-zone/releases/tag/v0.1.56 +[0.1.55]: https://github.com/strawlab/iana-time-zone/releases/tag/v0.1.55 +[0.1.54]: https://github.com/strawlab/iana-time-zone/releases/tag/v0.1.54 +[0.1.53]: https://github.com/strawlab/iana-time-zone/releases/tag/v0.1.53 +[0.1.52]: https://github.com/strawlab/iana-time-zone/releases/tag/v0.1.52 +[0.1.51]: https://github.com/strawlab/iana-time-zone/releases/tag/v0.1.51 +[0.1.50]: https://github.com/strawlab/iana-time-zone/releases/tag/v0.1.50 +[0.1.49]: https://github.com/strawlab/iana-time-zone/releases/tag/v0.1.49 +[0.1.48]: https://github.com/strawlab/iana-time-zone/releases/tag/v0.1.48 +[0.1.47]: https://github.com/strawlab/iana-time-zone/releases/tag/v0.1.47 +[0.1.46]: https://github.com/strawlab/iana-time-zone/releases/tag/v0.1.46 +[0.1.45]: https://github.com/strawlab/iana-time-zone/releases/tag/v0.1.45 +[0.1.44]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.44 +[0.1.43]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.43 +[0.1.42]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.42 +[0.1.41]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.41 +[0.1.40]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.40 +[0.1.39]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.39 +[0.1.38]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.38 +[0.1.37]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.37 +[0.1.36]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.36 +[0.1.35]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.35 +[0.1.34]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.34 +[0.1.33]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.33 +[0.1.32]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.32 +[0.1.31]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.31 +[0.1.30]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.30 +[0.1.29]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.29 +[0.1.28]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.28 +[0.1.27]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.27 +[0.1.26]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.26 +[0.1.25]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.25 +[0.1.24]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.24 +[0.1.23]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.23 +[0.1.22]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.22 +[0.1.21]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.21 +[0.1.20]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.20 +[0.1.19]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.19 +[0.1.18]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.18 +[0.1.17]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.17 +[0.1.16]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.16 +[0.1.15]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.15 +[0.1.14]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.14 +[0.1.13]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.13 +[0.1.12]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.12 +[0.1.11]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.11 +[0.1.10]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.10 +[0.1.9]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.9 +[0.1.8]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.8 +[0.1.7]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.7 +[0.1.6]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.6 +[0.1.5]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.5 +[0.1.4]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.4 +[0.1.3]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.3 +[0.1.2]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.2 +[0.1.1]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.1 +[0.1.0]: https://github.com/strawlab/iana-time-zone/releases/tag/0.1.0 diff --git a/third_party/rust/iana-time-zone/Cargo.lock b/third_party/rust/iana-time-zone/Cargo.lock new file mode 100644 index 00000000000..9e4314d8746 --- /dev/null +++ b/third_party/rust/iana-time-zone/Cargo.lock @@ -0,0 +1,590 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "bumpalo" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" + +[[package]] +name = "cc" +version = "1.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "chrono-tz" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efdce149c370f133a071ca8ef6ea340b7b88748ab0810097a9e2976eaa34b4f3" +dependencies = [ + "chrono", + "chrono-tz-build", + "phf", +] + +[[package]] +name = "chrono-tz-build" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f10f8c9340e31fc120ff885fcdb54a0b48e474bbd77cab557f0c30a3e569402" +dependencies = [ + "parse-zoneinfo", + "phf_codegen", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.63" +dependencies = [ + "android_system_properties", + "chrono-tz", + "core-foundation-sys", + "getrandom", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "wasm-bindgen-test", + "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 = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.171" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "minicov" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27fe9f1cc3c22e1687f9446c2083c4c5fc7f0bcf1c7a86bdbded14985895b4b" +dependencies = [ + "cc", + "walkdir", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "parse-zoneinfo" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24" +dependencies = [ + "regex", +] + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[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.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rustversion" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" + +[[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 = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "syn" +version = "2.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +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 = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-bindgen-test" +version = "0.3.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66c8d5e33ca3b6d9fa3b4676d774c5778031d27a578c2b007f905acf816152c3" +dependencies = [ + "js-sys", + "minicov", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", +] + +[[package]] +name = "wasm-bindgen-test-macro" +version = "0.3.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17d5042cc5fa009658f9a7333ef24291b1291a25b6382dd68862a7f3b969f69b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows-core" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" + +[[package]] +name = "windows-result" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" +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-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" diff --git a/third_party/rust/iana-time-zone/Cargo.toml b/third_party/rust/iana-time-zone/Cargo.toml new file mode 100644 index 00000000000..6837ecf2bf2 --- /dev/null +++ b/third_party/rust/iana-time-zone/Cargo.toml @@ -0,0 +1,93 @@ +# 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.62.0" +name = "iana-time-zone" +version = "0.1.63" +authors = [ + "Andrew Straw ", + "René Kijewski ", + "Ryan Lopopolo ", +] +build = false +autolib = false +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = "get the IANA time zone for the current system" +readme = "README.md" +keywords = [ + "IANA", + "time", +] +categories = [ + "date-and-time", + "internationalization", + "os", +] +license = "MIT OR Apache-2.0" +repository = "https://github.com/strawlab/iana-time-zone" + +[features] +fallback = [] + +[lib] +name = "iana_time_zone" +path = "src/lib.rs" + +[[example]] +name = "get_timezone" +path = "examples/get_timezone.rs" + +[[example]] +name = "get_timezone_loop" +path = "examples/get_timezone_loop.rs" + +[[example]] +name = "stress-test" +path = "examples/stress-test.rs" + +[dev-dependencies.chrono-tz] +version = "0.10.1" + +[dev-dependencies.getrandom] +version = "0.2.1" + +[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies.js-sys] +version = "0.3.66" + +[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies.log] +version = "0.4.14" + +[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies.wasm-bindgen] +version = "0.2.89" + +[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dev-dependencies.getrandom] +version = "0.2.1" +features = ["js"] + +[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dev-dependencies.wasm-bindgen-test] +version = "0.3.46" + +[target.'cfg(target_os = "android")'.dependencies.android_system_properties] +version = "0.1.5" + +[target.'cfg(target_os = "haiku")'.dependencies.iana-time-zone-haiku] +version = "0.1.1" + +[target.'cfg(target_os = "windows")'.dependencies.windows-core] +version = ">=0.56, <=0.61" + +[target.'cfg(target_vendor = "apple")'.dependencies.core-foundation-sys] +version = "0.8.6" diff --git a/third_party/rust/iana-time-zone/LICENSE-APACHE b/third_party/rust/iana-time-zone/LICENSE-APACHE new file mode 100644 index 00000000000..18af3f6fe37 --- /dev/null +++ b/third_party/rust/iana-time-zone/LICENSE-APACHE @@ -0,0 +1,201 @@ + 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 2020 Andrew Straw + +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/third_party/rust/iana-time-zone/LICENSE-MIT b/third_party/rust/iana-time-zone/LICENSE-MIT new file mode 100644 index 00000000000..8f8dd90d887 --- /dev/null +++ b/third_party/rust/iana-time-zone/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2020 Andrew D. Straw + +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/third_party/rust/iana-time-zone/README.md b/third_party/rust/iana-time-zone/README.md new file mode 100644 index 00000000000..1a17f17f1d7 --- /dev/null +++ b/third_party/rust/iana-time-zone/README.md @@ -0,0 +1,51 @@ +# iana-time-zone - get the IANA time zone for the current system + +[![Crates.io](https://img.shields.io/crates/v/iana-time-zone.svg)](https://crates.io/crates/iana-time-zone) +[![Documentation](https://docs.rs/iana-time-zone/badge.svg)](https://docs.rs/iana-time-zone/) +[![Crate License](https://img.shields.io/crates/l/iana-time-zone.svg)](https://crates.io/crates/iana-time-zone) +[![build](https://github.com/strawlab/iana-time-zone/actions/workflows/rust.yml/badge.svg)](https://github.com/strawlab/iana-time-zone/actions?query=branch%3Amain) + +This small utility crate gets the IANA time zone for the current system. This is +also known as the [tz database], tzdata, the zoneinfo database, and the Olson +database. + +[tz database]: https://en.wikipedia.org/wiki/Tz_database + +Example: + +```rust +// Get the current time zone as a string. +let tz_str = iana_time_zone::get_timezone()?; +println!("The current time zone is: {}", tz_str); +``` + +You can test this is working on your platform with: + +``` +cargo run --example get_timezone +``` + +## Minimum supported rust version policy + +This crate has a minimum supported rust version (MSRV) of 1.62.0 for [Tier 1] +platforms. + +[tier 1]: https://doc.rust-lang.org/1.62.0/rustc/platform-support.html + +Updates to the MSRV are sometimes necessary due to the MSRV of dependencies. +MSRV updates will not be indicated as a breaking change to the semver version. + +## License + +Licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + ) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or + ) + +at your option. + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. diff --git a/third_party/rust/iana-time-zone/bindings.txt b/third_party/rust/iana-time-zone/bindings.txt new file mode 100644 index 00000000000..b032f33ffbe --- /dev/null +++ b/third_party/rust/iana-time-zone/bindings.txt @@ -0,0 +1,4 @@ +--out src/windows_bindings.rs + +--filter + Windows.Globalization.Calendar diff --git a/third_party/rust/iana-time-zone/deny.toml b/third_party/rust/iana-time-zone/deny.toml new file mode 100644 index 00000000000..6cd2193df19 --- /dev/null +++ b/third_party/rust/iana-time-zone/deny.toml @@ -0,0 +1,4 @@ +[licenses] +version = 2 +allow = ["Apache-2.0", "MIT", "Unicode-3.0"] +private = { ignore = true } diff --git a/third_party/rust/iana-time-zone/examples/get_timezone.rs b/third_party/rust/iana-time-zone/examples/get_timezone.rs new file mode 100644 index 00000000000..0f5119f5669 --- /dev/null +++ b/third_party/rust/iana-time-zone/examples/get_timezone.rs @@ -0,0 +1,6 @@ +use iana_time_zone::{get_timezone, GetTimezoneError}; + +fn main() -> Result<(), GetTimezoneError> { + println!("{}", get_timezone()?); + Ok(()) +} diff --git a/third_party/rust/iana-time-zone/examples/get_timezone_loop.rs b/third_party/rust/iana-time-zone/examples/get_timezone_loop.rs new file mode 100644 index 00000000000..39ebb02605c --- /dev/null +++ b/third_party/rust/iana-time-zone/examples/get_timezone_loop.rs @@ -0,0 +1,13 @@ +use std::thread; +use std::time::Duration; + +use iana_time_zone::{get_timezone, GetTimezoneError}; + +const WAIT: Duration = Duration::from_secs(1); + +fn main() -> Result<(), GetTimezoneError> { + loop { + println!("{}", get_timezone()?); + thread::sleep(WAIT); + } +} diff --git a/third_party/rust/iana-time-zone/examples/stress-test.rs b/third_party/rust/iana-time-zone/examples/stress-test.rs new file mode 100644 index 00000000000..73df86f51b6 --- /dev/null +++ b/third_party/rust/iana-time-zone/examples/stress-test.rs @@ -0,0 +1,25 @@ +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::thread::spawn; + +use iana_time_zone::get_timezone; + +const THREADS: usize = 10; +const ITERATIONS: usize = 100_000; + +static COUNT: AtomicUsize = AtomicUsize::new(0); + +fn main() { + let mut threads = Vec::with_capacity(THREADS); + for _ in 0..THREADS { + threads.push(spawn(|| { + for _ in 0..ITERATIONS { + get_timezone().unwrap(); + COUNT.fetch_add(1, Ordering::Relaxed); + } + })); + } + for thread in threads { + thread.join().unwrap(); + } + assert_eq!(COUNT.load(Ordering::SeqCst), THREADS * ITERATIONS); +} diff --git a/third_party/rust/iana-time-zone/src/ffi_utils.rs b/third_party/rust/iana-time-zone/src/ffi_utils.rs new file mode 100644 index 00000000000..17f1b974725 --- /dev/null +++ b/third_party/rust/iana-time-zone/src/ffi_utils.rs @@ -0,0 +1,718 @@ +//! Cross platform FFI helpers. + +#[cfg(any(test, target_os = "android"))] +use std::ffi::CStr; + +/// A buffer to store the timezone name when calling the C API. +#[cfg(any(test, target_vendor = "apple", target_env = "ohos"))] +pub(crate) mod buffer { + /// The longest name in the IANA time zone database is 32 ASCII characters long. + pub const MAX_LEN: usize = 64; + + /// Return a buffer to store the timezone name. + /// + /// The buffer is used to store the timezone name when calling the C API. + pub const fn tzname_buf() -> [u8; MAX_LEN] { + [0; MAX_LEN] + } +} + +// The system property named 'persist.sys.timezone' contains the name of the +// current timezone. +// +// From https://android.googlesource.com/platform/bionic/+/gingerbread-release/libc/docs/OVERVIEW.TXT#79: +// +// > The name of the current timezone is taken from the TZ environment variable, +// > if defined. Otherwise, the system property named 'persist.sys.timezone' is +// > checked instead. +// +// TODO: Use a `c"..."` literal when MSRV is upgraded beyond 1.77.0. +// https://doc.rust-lang.org/edition-guide/rust-2021/c-string-literals.html +#[cfg(any(test, target_os = "android"))] +const ANDROID_TIMEZONE_PROPERTY_NAME: &[u8] = b"persist.sys.timezone\0"; + +/// Return a [`CStr`] to access the timezone from an Android system properties +/// environment. +#[cfg(any(test, target_os = "android"))] +pub(crate) fn android_timezone_property_name() -> &'static CStr { + // In tests or debug mode, opt into extra runtime checks. + if cfg!(any(test, debug_assertions)) { + return CStr::from_bytes_with_nul(ANDROID_TIMEZONE_PROPERTY_NAME).unwrap(); + } + + // SAFETY: the key is NUL-terminated and there are no other NULs, this + // invariant is checked in tests. + unsafe { CStr::from_bytes_with_nul_unchecked(ANDROID_TIMEZONE_PROPERTY_NAME) } +} + +#[cfg(test)] +mod tests { + use core::mem::size_of_val; + use std::ffi::CStr; + + use super::buffer::{tzname_buf, MAX_LEN}; + use super::{android_timezone_property_name, ANDROID_TIMEZONE_PROPERTY_NAME}; + + #[test] + fn test_android_timezone_property_name_is_valid_cstr() { + CStr::from_bytes_with_nul(ANDROID_TIMEZONE_PROPERTY_NAME).unwrap(); + + let mut invalid_property_name = ANDROID_TIMEZONE_PROPERTY_NAME.to_owned(); + invalid_property_name.push(b'\0'); + CStr::from_bytes_with_nul(&invalid_property_name).unwrap_err(); + } + + #[test] + fn test_android_timezone_property_name_getter() { + let key = android_timezone_property_name().to_bytes_with_nul(); + assert_eq!(key, ANDROID_TIMEZONE_PROPERTY_NAME); + std::str::from_utf8(key).unwrap(); + } + + /// An exhaustive set of IANA timezone names for testing. + /// + /// Pulled from Wikipedia as of Sat March 22, 2025: + /// + /// - + /// - + static KNOWN_TIMEZONE_NAMES: &[&str] = &[ + "Africa/Abidjan", + "Africa/Accra", + "Africa/Addis_Ababa", + "Africa/Algiers", + "Africa/Asmara", + "Africa/Asmera", + "Africa/Bamako", + "Africa/Bangui", + "Africa/Banjul", + "Africa/Bissau", + "Africa/Blantyre", + "Africa/Brazzaville", + "Africa/Bujumbura", + "Africa/Cairo", + "Africa/Casablanca", + "Africa/Conakry", + "Africa/Dakar", + "Africa/Dar_es_Salaam", + "Africa/Djibouti", + "Africa/Douala", + "Africa/El_Aaiun", + "Africa/Freetown", + "Africa/Gaborone", + "Africa/Harare", + "Africa/Johannesburg", + "Africa/Juba", + "Africa/Kampala", + "Africa/Khartoum", + "Africa/Kigali", + "Africa/Libreville", + "Africa/Lome", + "Africa/Luanda", + "Africa/Lusaka", + "Africa/Malabo", + "Africa/Maseru", + "Africa/Mbabane", + "Africa/Mogadishu", + "Africa/Monrovia", + "Africa/Nairobi", + "Africa/Ndjamena", + "Africa/Niamey", + "Africa/Nouakchott", + "Africa/Ouagadougou", + "Africa/Porto-Novo", + "Africa/Sao_Tome", + "Africa/Timbuktu", + "Africa/Tripoli", + "Africa/Tunis", + "Africa/Windhoek", + "America/Anguilla", + "America/Antigua", + "America/Argentina/ComodRivadavia", + "America/Aruba", + "America/Asuncion", + "America/Atka", + "America/Barbados", + "America/Belize", + "America/Bogota", + "America/Buenos_Aires", + "America/Caracas", + "America/Catamarca", + "America/Cayenne", + "America/Cayman", + "America/Coral_Harbour", + "America/Cordoba", + "America/Costa_Rica", + "America/Curacao", + "America/Dominica", + "America/El_Salvador", + "America/Ensenada", + "America/Fort_Wayne", + "America/Godthab", + "America/Grand_Turk", + "America/Grenada", + "America/Guadeloupe", + "America/Guatemala", + "America/Guyana", + "America/Havana", + "America/Indianapolis", + "America/Jamaica", + "America/Jujuy", + "America/Knox_IN", + "America/Kralendijk", + "America/La_Paz", + "America/Lima", + "America/Louisville", + "America/Lower_Princes", + "America/Managua", + "America/Marigot", + "America/Martinique", + "America/Mendoza", + "America/Miquelon", + "America/Montevideo", + "America/Montreal", + "America/Montserrat", + "America/Nassau", + "America/Nipigon", + "America/Pangnirtung", + "America/Paramaribo", + "America/Port-au-Prince", + "America/Port_of_Spain", + "America/Porto_Acre", + "America/Rainy_River", + "America/Rosario", + "America/Santa_Isabel", + "America/Santo_Domingo", + "America/Shiprock", + "America/St_Barthelemy", + "America/St_Kitts", + "America/St_Lucia", + "America/St_Thomas", + "America/St_Vincent", + "America/Tegucigalpa", + "America/Thunder_Bay", + "America/Tortola", + "America/Virgin", + "America/Yellowknife", + "Antarctica/South_Pole", + "Arctic/Longyearbyen", + "Asia/Aden", + "Asia/Amman", + "Asia/Ashgabat", + "Asia/Ashkhabad", + "Asia/Baghdad", + "Asia/Bahrain", + "Asia/Baku", + "Asia/Beirut", + "Asia/Bishkek", + "Asia/Brunei", + "Asia/Calcutta", + "Asia/Choibalsan", + "Asia/Chongqing", + "Asia/Chungking", + "Asia/Colombo", + "Asia/Dacca", + "Asia/Damascus", + "Asia/Dhaka", + "Asia/Dili", + "Asia/Dushanbe", + "Asia/Harbin", + "Asia/Hong_Kong", + "Asia/Istanbul", + "Asia/Jerusalem", + "Asia/Kabul", + "Asia/Karachi", + "Asia/Kashgar", + "Asia/Kathmandu", + "Asia/Katmandu", + "Asia/Kolkata", + "Asia/Kuwait", + "Asia/Macao", + "Asia/Macau", + "Asia/Manila", + "Asia/Muscat", + "Asia/Phnom_Penh", + "Asia/Pyongyang", + "Asia/Qatar", + "Asia/Rangoon", + "Asia/Saigon", + "Asia/Seoul", + "Asia/Taipei", + "Asia/Tbilisi", + "Asia/Tehran", + "Asia/Tel_Aviv", + "Asia/Thimbu", + "Asia/Thimphu", + "Asia/Ujung_Pandang", + "Asia/Ulan_Bator", + "Asia/Vientiane", + "Asia/Yangon", + "Asia/Yerevan", + "Atlantic/Bermuda", + "Atlantic/Cape_Verde", + "Atlantic/Faeroe", + "Atlantic/Faroe", + "Atlantic/Jan_Mayen", + "Atlantic/Reykjavik", + "Atlantic/South_Georgia", + "Atlantic/St_Helena", + "Atlantic/Stanley", + "Australia/ACT", + "Australia/Canberra", + "Australia/Currie", + "Australia/LHI", + "Australia/North", + "Australia/NSW", + "Australia/Queensland", + "Australia/South", + "Australia/Tasmania", + "Australia/Victoria", + "Australia/West", + "Australia/Yancowinna", + "Brazil/Acre", + "Brazil/DeNoronha", + "Brazil/East", + "Brazil/West", + "Canada/Atlantic", + "Canada/Central", + "Canada/Eastern", + "Canada/Mountain", + "Canada/Newfoundland", + "Canada/Pacific", + "Canada/Saskatchewan", + "Canada/Yukon", + "CET", + "Chile/Continental", + "Chile/EasterIsland", + "CST6CDT", + "Cuba", + "EET", + "Egypt", + "Eire", + "EST", + "EST5EDT", + "Etc/GMT", + "Etc/GMT+0", + "Etc/GMT+1", + "Etc/GMT+10", + "Etc/GMT+11", + "Etc/GMT+12", + "Etc/GMT+2", + "Etc/GMT+3", + "Etc/GMT+4", + "Etc/GMT+5", + "Etc/GMT+6", + "Etc/GMT+7", + "Etc/GMT+8", + "Etc/GMT+9", + "Etc/GMT-0", + "Etc/GMT-1", + "Etc/GMT-10", + "Etc/GMT-11", + "Etc/GMT-12", + "Etc/GMT-13", + "Etc/GMT-14", + "Etc/GMT-2", + "Etc/GMT-3", + "Etc/GMT-4", + "Etc/GMT-5", + "Etc/GMT-6", + "Etc/GMT-7", + "Etc/GMT-8", + "Etc/GMT-9", + "Etc/GMT0", + "Etc/Greenwich", + "Etc/UCT", + "Etc/Universal", + "Etc/UTC", + "Etc/Zulu", + "Europe/Amsterdam", + "Europe/Andorra", + "Europe/Athens", + "Europe/Belfast", + "Europe/Belgrade", + "Europe/Bratislava", + "Europe/Brussels", + "Europe/Bucharest", + "Europe/Budapest", + "Europe/Chisinau", + "Europe/Copenhagen", + "Europe/Dublin", + "Europe/Gibraltar", + "Europe/Guernsey", + "Europe/Helsinki", + "Europe/Isle_of_Man", + "Europe/Istanbul", + "Europe/Jersey", + "Europe/Kiev", + "Europe/Ljubljana", + "Europe/London", + "Europe/Luxembourg", + "Europe/Malta", + "Europe/Mariehamn", + "Europe/Minsk", + "Europe/Monaco", + "Europe/Nicosia", + "Europe/Oslo", + "Europe/Paris", + "Europe/Podgorica", + "Europe/Prague", + "Europe/Riga", + "Europe/Rome", + "Europe/San_Marino", + "Europe/Sarajevo", + "Europe/Skopje", + "Europe/Sofia", + "Europe/Stockholm", + "Europe/Tallinn", + "Europe/Tirane", + "Europe/Tiraspol", + "Europe/Uzhgorod", + "Europe/Vaduz", + "Europe/Vatican", + "Europe/Vienna", + "Europe/Vilnius", + "Europe/Warsaw", + "Europe/Zagreb", + "Europe/Zaporozhye", + "Factory", + "GB", + "GB-Eire", + "GMT", + "GMT+0", + "GMT-0", + "GMT0", + "Greenwich", + "Hongkong", + "HST", + "Iceland", + "Indian/Antananarivo", + "Indian/Chagos", + "Indian/Christmas", + "Indian/Cocos", + "Indian/Comoro", + "Indian/Kerguelen", + "Indian/Mahe", + "Indian/Mauritius", + "Indian/Mayotte", + "Indian/Reunion", + "Iran", + "Israel", + "Jamaica", + "Japan", + "Kwajalein", + "Libya", + "MET", + "Mexico/BajaNorte", + "Mexico/BajaSur", + "Mexico/General", + "MST", + "MST7MDT", + "Navajo", + "NZ", + "NZ-CHAT", + "Pacific/Apia", + "Pacific/Efate", + "Pacific/Enderbury", + "Pacific/Fakaofo", + "Pacific/Fiji", + "Pacific/Funafuti", + "Pacific/Guam", + "Pacific/Johnston", + "Pacific/Nauru", + "Pacific/Niue", + "Pacific/Norfolk", + "Pacific/Noumea", + "Pacific/Palau", + "Pacific/Pitcairn", + "Pacific/Ponape", + "Pacific/Rarotonga", + "Pacific/Saipan", + "Pacific/Samoa", + "Pacific/Tongatapu", + "Pacific/Truk", + "Pacific/Wallis", + "Pacific/Yap", + "Poland", + "Portugal", + "PRC", + "PST8PDT", + "ROC", + "ROK", + "Singapore", + "Turkey", + "UCT", + "Universal", + "US/Alaska", + "US/Aleutian", + "US/Arizona", + "US/Central", + "US/East-Indiana", + "US/Eastern", + "US/Hawaii", + "US/Indiana-Starke", + "US/Michigan", + "US/Mountain", + "US/Pacific", + "US/Samoa", + "UTC", + "W-SU", + "WET", + "Zulu", + "America/Rio_Branco", + "America/Maceio", + "America/Metlakatla", + "America/Juneau", + "America/Sitka", + "America/Adak", + "America/Yakutat", + "America/Anchorage", + "America/Nome", + "America/Manaus", + "America/Eirunepe", + "Asia/Aqtobe", + "America/Blanc-Sablon", + "America/Puerto_Rico", + "America/Goose_Bay", + "America/Moncton", + "America/Glace_Bay", + "America/Halifax", + "America/Noronha", + "Asia/Atyrau", + "Atlantic/Azores", + "America/Bahia", + "America/Bahia_Banderas", + "America/Tijuana", + "America/Mazatlan", + "Asia/Hovd", + "Asia/Shanghai", + "Asia/Makassar", + "Asia/Pontianak", + "Pacific/Bougainville", + "America/Fortaleza", + "America/Sao_Paulo", + "America/Argentina/Buenos_Aires", + "Europe/Busingen", + "Europe/Zurich", + "America/Merida", + "Atlantic/Canary", + "Antarctica/Casey", + "America/Argentina/Catamarca", + "America/Indiana/Tell_City", + "America/Indiana/Knox", + "America/Menominee", + "America/North_Dakota/Beulah", + "America/North_Dakota/New_Salem", + "America/North_Dakota/Center", + "America/Rankin_Inlet", + "America/Resolute", + "America/Winnipeg", + "America/Chicago", + "Africa/Maputo", + "America/Mexico_City", + "Africa/Ceuta", + "Pacific/Chatham", + "America/Chihuahua", + "America/Ojinaga", + "America/Ciudad_Juarez", + "Pacific/Chuuk", + "America/Matamoros", + "Europe/Simferopol", + "Asia/Dubai", + "America/Swift_Current", + "America/Regina", + "Antarctica/Davis", + "Africa/Lubumbashi", + "Africa/Kinshasa", + "Antarctica/DumontDUrville", + "America/Monterrey", + "Pacific/Easter", + "America/Indiana/Marengo", + "America/Indiana/Vincennes", + "America/Indiana/Indianapolis", + "America/Indiana/Petersburg", + "America/Indiana/Winamac", + "America/Indiana/Vevay", + "America/Kentucky/Louisville", + "America/Kentucky/Monticello", + "America/Detroit", + "America/Iqaluit", + "America/Toronto", + "America/New_York", + "America/Guayaquil", + "America/Atikokan", + "America/Panama", + "Asia/Tokyo", + "Pacific/Galapagos", + "Pacific/Gambier", + "Asia/Gaza", + "Pacific/Tarawa", + "Pacific/Honolulu", + "Asia/Jakarta", + "America/Argentina/Jujuy", + "Indian/Maldives", + "Pacific/Kosrae", + "Pacific/Kwajalein", + "America/Argentina/La_Rioja", + "Pacific/Kiritimati", + "Australia/Lord_Howe", + "Antarctica/Macquarie", + "Atlantic/Madeira", + "Asia/Kuala_Lumpur", + "Asia/Aqtau", + "Pacific/Marquesas", + "America/Cuiaba", + "America/Campo_Grande", + "Antarctica/Mawson", + "America/Argentina/Mendoza", + "Pacific/Pago_Pago", + "Pacific/Midway", + "America/Argentina/Cordoba", + "America/Santiago", + "Asia/Nicosia", + "Europe/Berlin", + "America/Nuuk", + "Asia/Almaty", + "Pacific/Majuro", + "Asia/Ulaanbaatar", + "Europe/Kyiv", + "America/Edmonton", + "America/Boise", + "America/Inuvik", + "America/Cambridge_Bay", + "America/Denver", + "Europe/Kaliningrad", + "Europe/Kirov", + "Europe/Moscow", + "Europe/Volgograd", + "Europe/Astrakhan", + "Europe/Samara", + "Europe/Saratov", + "Europe/Ulyanovsk", + "Asia/Yekaterinburg", + "Asia/Omsk", + "Asia/Barnaul", + "Asia/Novokuznetsk", + "Asia/Krasnoyarsk", + "Asia/Novosibirsk", + "Asia/Tomsk", + "Asia/Irkutsk", + "Asia/Yakutsk", + "Asia/Khandyga", + "Asia/Chita", + "Asia/Vladivostok", + "Asia/Ust-Nera", + "Asia/Magadan", + "Asia/Srednekolymsk", + "Asia/Sakhalin", + "Asia/Anadyr", + "Asia/Kamchatka", + "America/Phoenix", + "America/Creston", + "America/Dawson_Creek", + "America/Fort_Nelson", + "America/Whitehorse", + "America/Dawson", + "America/Danmarkshavn", + "Asia/Jayapura", + "Australia/Sydney", + "Australia/Broken_Hill", + "Pacific/Auckland", + "Antarctica/McMurdo", + "America/St_Johns", + "Asia/Bangkok", + "Asia/Famagusta", + "Australia/Darwin", + "America/Los_Angeles", + "America/Vancouver", + "Antarctica/Palmer", + "Pacific/Port_Moresby", + "America/Belem", + "America/Santarem", + "Asia/Singapore", + "America/Recife", + "Pacific/Kanton", + "Pacific/Guadalcanal", + "Pacific/Pohnpei", + "Europe/Lisbon", + "Asia/Qostanay", + "Australia/Brisbane", + "Australia/Lindeman", + "America/Cancun", + "Asia/Qyzylorda", + "America/Punta_Arenas", + "America/Porto_Velho", + "America/Boa_Vista", + "Antarctica/Rothera", + "Asia/Kuching", + "America/Argentina/Salta", + "America/Argentina/San_Juan", + "America/Argentina/San_Luis", + "America/Argentina/Rio_Gallegos", + "America/Scoresbysund", + "Pacific/Tahiti", + "America/Hermosillo", + "Australia/Adelaide", + "Asia/Ho_Chi_Minh", + "Europe/Madrid", + "Antarctica/Syowa", + "Asia/Riyadh", + "Australia/Hobart", + "America/Thule", + "America/Argentina/Ushuaia", + "America/Araguaina", + "Antarctica/Troll", + "America/Argentina/Tucuman", + "Asia/Tashkent", + "Asia/Samarkand", + "Australia/Melbourne", + "Antarctica/Vostok", + "Pacific/Wake", + "Africa/Lagos", + "Asia/Hebron", + "Asia/Oral", + "Australia/Eucla", + "Australia/Perth", + "Asia/Urumqi", + ]; + + #[test] + fn test_tzname_buffer_fits_all_iana_names() { + let buf = tzname_buf(); + let max_len = buf.len(); + + let mut failed_tz_names = vec![]; + + for &tz in KNOWN_TIMEZONE_NAMES { + // Require max_len + 1 to account for an optional NUL terminator. + if tz.len() >= max_len { + failed_tz_names.push(tz); + } + } + + assert!( + failed_tz_names.is_empty(), + "One or more timezone names exceed the buffer length of {}. Max length of found timezone: {}\n{:?}", + max_len, + failed_tz_names.iter().map(|s| s.len()).max().unwrap(), + failed_tz_names + ); + } + + #[test] + fn test_tzname_buffer_correct_size() { + assert_eq!( + MAX_LEN, 64, + "Buffer length changed unexpectedly, ensure consistency with documented limit." + ); + assert_eq!( + tzname_buf().len(), + MAX_LEN, + "Buffer length changed unexpectedly, ensure consistency with documented limit." + ); + assert_eq!( + size_of_val(&tzname_buf()), + MAX_LEN, + "Buffer length changed unexpectedly, ensure consistency with documented limit." + ); + } +} diff --git a/third_party/rust/iana-time-zone/src/lib.rs b/third_party/rust/iana-time-zone/src/lib.rs new file mode 100644 index 00000000000..7d5bcb657b0 --- /dev/null +++ b/third_party/rust/iana-time-zone/src/lib.rs @@ -0,0 +1,119 @@ +#![warn(clippy::all)] +#![warn(clippy::cargo)] +#![warn(clippy::undocumented_unsafe_blocks)] +#![allow(unknown_lints)] +#![warn(missing_copy_implementations)] +#![warn(missing_debug_implementations)] +#![warn(missing_docs)] +#![warn(rust_2018_idioms)] +#![warn(trivial_casts, trivial_numeric_casts)] +#![warn(unused_qualifications)] +#![warn(variant_size_differences)] + +//! get the IANA time zone for the current system +//! +//! This small utility crate provides the +//! [`get_timezone()`](fn.get_timezone.html) function. +//! +//! ```rust +//! // Get the current time zone as a string. +//! let tz_str = iana_time_zone::get_timezone()?; +//! println!("The current time zone is: {}", tz_str); +//! # Ok::<(), iana_time_zone::GetTimezoneError>(()) +//! ``` +//! +//! The resulting string can be parsed to a +//! [`chrono-tz::Tz`](https://docs.rs/chrono-tz/latest/chrono_tz/enum.Tz.html) +//! variant like this: +//! ```rust +//! let tz_str = iana_time_zone::get_timezone()?; +//! let tz: chrono_tz::Tz = tz_str.parse()?; +//! # Ok::<(), Box>(()) +//! ``` + +#[allow(dead_code)] +mod ffi_utils; + +#[cfg_attr( + any(all(target_os = "linux", not(target_env = "ohos")), target_os = "hurd"), + path = "tz_linux.rs" +)] +#[cfg_attr(all(target_os = "linux", target_env = "ohos"), path = "tz_ohos.rs")] +#[cfg_attr(target_os = "windows", path = "tz_windows.rs")] +#[cfg_attr(target_vendor = "apple", path = "tz_darwin.rs")] +#[cfg_attr( + all(target_arch = "wasm32", target_os = "unknown"), + path = "tz_wasm32_unknown.rs" +)] +#[cfg_attr( + any(target_os = "freebsd", target_os = "dragonfly"), + path = "tz_freebsd.rs" +)] +#[cfg_attr( + any(target_os = "netbsd", target_os = "openbsd"), + path = "tz_netbsd.rs" +)] +#[cfg_attr( + any(target_os = "illumos", target_os = "solaris"), + path = "tz_illumos.rs" +)] +#[cfg_attr(target_os = "aix", path = "tz_aix.rs")] +#[cfg_attr(target_os = "android", path = "tz_android.rs")] +#[cfg_attr(target_os = "haiku", path = "tz_haiku.rs")] +mod platform; + +/// Error types +#[derive(Debug)] +pub enum GetTimezoneError { + /// Failed to parse + FailedParsingString, + /// Wrapped IO error + IoError(std::io::Error), + /// Platform-specific error from the operating system + OsError, +} + +impl std::error::Error for GetTimezoneError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + GetTimezoneError::FailedParsingString => None, + GetTimezoneError::IoError(err) => Some(err), + GetTimezoneError::OsError => None, + } + } +} + +impl std::fmt::Display for GetTimezoneError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + f.write_str(match self { + GetTimezoneError::FailedParsingString => "GetTimezoneError::FailedParsingString", + GetTimezoneError::IoError(err) => return err.fmt(f), + GetTimezoneError::OsError => "OsError", + }) + } +} + +impl From for GetTimezoneError { + fn from(orig: std::io::Error) -> Self { + GetTimezoneError::IoError(orig) + } +} + +/// Get the current IANA time zone as a string. +/// +/// See the module-level documentation for a usage example and more details +/// about this function. +#[inline] +pub fn get_timezone() -> Result { + platform::get_timezone_inner() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn get_current() { + println!("current: {}", get_timezone().unwrap()); + } +} diff --git a/third_party/rust/iana-time-zone/src/platform.rs b/third_party/rust/iana-time-zone/src/platform.rs new file mode 100644 index 00000000000..2cf3da96617 --- /dev/null +++ b/third_party/rust/iana-time-zone/src/platform.rs @@ -0,0 +1,9 @@ +pub fn get_timezone_inner() -> Result { + Err(crate::GetTimezoneError::OsError) +} + +#[cfg(not(feature = "fallback"))] +compile_error!( + "iana-time-zone is currently implemented for Linux, Window, MacOS, FreeBSD, NetBSD, \ + OpenBSD, Dragonfly, WebAssembly (browser), iOS, Illumos, Android, AIX, Solaris and Haiku.", +); diff --git a/third_party/rust/iana-time-zone/src/tz_aix.rs b/third_party/rust/iana-time-zone/src/tz_aix.rs new file mode 100644 index 00000000000..8f0f4520ecd --- /dev/null +++ b/third_party/rust/iana-time-zone/src/tz_aix.rs @@ -0,0 +1,7 @@ +use std::env; +use std::fs::OpenOptions; +use std::io::{BufRead, BufReader}; + +pub(crate) fn get_timezone_inner() -> Result { + env::var("TZ").map_err(|_| crate::GetTimezoneError::OsError) +} diff --git a/third_party/rust/iana-time-zone/src/tz_android.rs b/third_party/rust/iana-time-zone/src/tz_android.rs new file mode 100644 index 00000000000..27255b52f9e --- /dev/null +++ b/third_party/rust/iana-time-zone/src/tz_android.rs @@ -0,0 +1,27 @@ +use std::sync::Once; + +use android_system_properties::AndroidSystemProperties; + +use crate::ffi_utils::android_timezone_property_name; + +pub(crate) fn get_timezone_inner() -> Result { + let key = android_timezone_property_name(); + + get_properties() + .and_then(|properties| properties.get_from_cstr(key)) + .ok_or(crate::GetTimezoneError::OsError) +} + +fn get_properties() -> Option<&'static AndroidSystemProperties> { + static INITIALIZED: Once = Once::new(); + static mut PROPERTIES: Option = None; + + INITIALIZED.call_once(|| { + let properties = AndroidSystemProperties::new(); + // SAFETY: `INITIALIZED` is synchronizing. The variable is only assigned to once. + unsafe { PROPERTIES = Some(properties) }; + }); + + // SAFETY: `INITIALIZED` is synchronizing. The variable is only assigned to once. + unsafe { PROPERTIES.as_ref() } +} diff --git a/third_party/rust/iana-time-zone/src/tz_darwin.rs b/third_party/rust/iana-time-zone/src/tz_darwin.rs new file mode 100644 index 00000000000..0b8c56e1d94 --- /dev/null +++ b/third_party/rust/iana-time-zone/src/tz_darwin.rs @@ -0,0 +1,179 @@ +use crate::ffi_utils::buffer::{tzname_buf, MAX_LEN}; + +pub(crate) fn get_timezone_inner() -> Result { + get_timezone().ok_or(crate::GetTimezoneError::OsError) +} + +#[inline] +fn get_timezone() -> Option { + let mut buf = tzname_buf(); + // Get system time zone, and borrow its name. + let tz = system_time_zone::SystemTimeZone::new()?; + let name = tz.name()?; + + // If the name is encoded in UTF-8, copy it directly. + let name = if let Some(name) = name.as_utf8() { + name + } else { + // Otherwise convert the name to UTF-8. + name.to_utf8(&mut buf)? + }; + + if name.is_empty() || name.len() > MAX_LEN { + // The name should not be empty, or excessively long. + return None; + } else { + Some(name.to_owned()) + } +} + +mod system_time_zone { + //! create a safe wrapper around `CFTimeZoneRef` + + use core_foundation_sys::base::{CFRelease, CFTypeRef}; + use core_foundation_sys::timezone::{ + CFTimeZoneCopySystem, CFTimeZoneGetName, CFTimeZoneRef, CFTimeZoneResetSystem, + }; + + pub(crate) struct SystemTimeZone(CFTimeZoneRef); + + impl Drop for SystemTimeZone { + fn drop(&mut self) { + // SAFETY: `SystemTimeZone` is only ever created with a valid `CFTimeZoneRef`. + unsafe { CFRelease(self.0 as CFTypeRef) }; + } + } + + impl SystemTimeZone { + /// Creates a new `SystemTimeZone` by querying the current Darwin system + /// timezone. + /// + /// This function implicitly calls `CFTimeZoneResetSystem` to invalidate + /// the cached timezone and ensure we always retrieve the current system + /// timezone. + /// + /// Due to CoreFoundation's internal caching mechanism, subsequent calls + /// to `CFTimeZoneCopySystem` do not reflect system timezone changes + /// made while the process is running. Thus, we explicitly call + /// `CFTimeZoneResetSystem` first to invalidate the cached value and + /// ensure we always retrieve the current system timezone. + pub(crate) fn new() -> Option { + // SAFETY: + // - Both `CFTimeZoneResetSystem` and `CFTimeZoneCopySystem` are FFI + // calls to macOS CoreFoundation. + // - `CFTimeZoneResetSystem` safely invalidates the cached timezone + // without any external invariants. + // - The pointer returned by `CFTimeZoneCopySystem` is managed and + // released properly within `Drop`. + let v: CFTimeZoneRef = unsafe { + // First, clear the potentially cached timezone. This call will + // take the global lock on the timezone data. + // + // See + // for context on why we reset the timezone here. + CFTimeZoneResetSystem(); + + // Fetch the current value. This will likely allocate. This call + // will again take the global lock on the timezone data. + CFTimeZoneCopySystem() + }; + if v.is_null() { + None + } else { + Some(SystemTimeZone(v)) + } + } + + /// Get the time zone name as a [`StringRef`]. + /// + /// The lifetime of the `StringRef` is bound to our lifetime. Mutable + /// access is also prevented by taking a reference to `self`. + /// + /// [`StringRef`]: super::string_ref::StringRef + pub(crate) fn name(&self) -> Option> { + // SAFETY: `SystemTimeZone` is only ever created with a valid `CFTimeZoneRef`. + let string = unsafe { CFTimeZoneGetName(self.0) }; + if string.is_null() { + None + } else { + // SAFETY: here we ensure that `string` is a valid pointer. + Some(unsafe { super::string_ref::StringRef::new(string, self) }) + } + } + } +} + +mod string_ref { + //! create safe wrapper around `CFStringRef` + + use core::convert::TryInto; + + use core_foundation_sys::base::{Boolean, CFRange}; + use core_foundation_sys::string::{ + kCFStringEncodingUTF8, CFStringGetBytes, CFStringGetCStringPtr, CFStringGetLength, + CFStringRef, + }; + + pub(crate) struct StringRef<'a, T> { + string: CFStringRef, + // We exclude mutable access to the parent by taking a reference to the + // parent (rather than, for example, just using a marker to enforce the + // parent's lifetime). + _parent: &'a T, + } + + impl<'a, T> StringRef<'a, T> { + // SAFETY: `string` must be valid pointer + pub(crate) unsafe fn new(string: CFStringRef, _parent: &'a T) -> Self { + Self { string, _parent } + } + + pub(crate) fn as_utf8(&self) -> Option<&'a str> { + // SAFETY: `StringRef` is only ever created with a valid `CFStringRef`. + let v = unsafe { CFStringGetCStringPtr(self.string, kCFStringEncodingUTF8) }; + if !v.is_null() { + // SAFETY: `CFStringGetCStringPtr()` returns NUL-terminated + // strings and will return NULL if the internal representation + // of the `CFString`` is not compatible with the requested + // encoding. + let v = unsafe { std::ffi::CStr::from_ptr(v) }; + if let Ok(v) = v.to_str() { + return Some(v); + } + } + None + } + + pub(crate) fn to_utf8<'b>(&self, buf: &'b mut [u8]) -> Option<&'b str> { + // SAFETY: `StringRef` is only ever created with a valid `CFStringRef`. + let length = unsafe { CFStringGetLength(self.string) }; + + let mut buf_bytes = 0; + let range = CFRange { + location: 0, + length, + }; + + // SAFETY: `StringRef` is only ever created with a valid `CFStringRef`. + let converted_bytes = unsafe { + CFStringGetBytes( + self.string, + range, + kCFStringEncodingUTF8, + b'\0', + false as Boolean, + buf.as_mut_ptr(), + buf.len() as isize, + &mut buf_bytes, + ) + }; + if converted_bytes != length { + return None; + } + + let len = buf_bytes.try_into().ok()?; + let s = buf.get(..len)?; + std::str::from_utf8(s).ok() + } + } +} diff --git a/third_party/rust/iana-time-zone/src/tz_freebsd.rs b/third_party/rust/iana-time-zone/src/tz_freebsd.rs new file mode 100644 index 00000000000..4d55e153698 --- /dev/null +++ b/third_party/rust/iana-time-zone/src/tz_freebsd.rs @@ -0,0 +1,7 @@ +pub(crate) fn get_timezone_inner() -> Result { + // see https://gitlab.gnome.org/GNOME/evolution-data-server/-/issues/19 + let mut contents = std::fs::read_to_string("/var/db/zoneinfo")?; + // Trim to the correct length without allocating. + contents.truncate(contents.trim_end().len()); + Ok(contents) +} diff --git a/third_party/rust/iana-time-zone/src/tz_haiku.rs b/third_party/rust/iana-time-zone/src/tz_haiku.rs new file mode 100644 index 00000000000..d78372b6d93 --- /dev/null +++ b/third_party/rust/iana-time-zone/src/tz_haiku.rs @@ -0,0 +1,3 @@ +pub(crate) fn get_timezone_inner() -> Result { + iana_time_zone_haiku::get_timezone().ok_or(crate::GetTimezoneError::OsError) +} diff --git a/third_party/rust/iana-time-zone/src/tz_illumos.rs b/third_party/rust/iana-time-zone/src/tz_illumos.rs new file mode 100644 index 00000000000..17b099b10a7 --- /dev/null +++ b/third_party/rust/iana-time-zone/src/tz_illumos.rs @@ -0,0 +1,22 @@ +use std::fs::OpenOptions; +use std::io::{BufRead, BufReader}; + +pub(crate) fn get_timezone_inner() -> Result { + // https://illumos.org/man/5/TIMEZONE + // https://docs.oracle.com/cd/E23824_01/html/821-1473/uc-timezone-4.html + + let file = OpenOptions::new().read(true).open("/etc/default/init")?; + let mut reader = BufReader::with_capacity(1536, file); + let mut line = String::with_capacity(80); + loop { + line.clear(); + let count = reader.read_line(&mut line)?; + if count == 0 { + return Err(crate::GetTimezoneError::FailedParsingString); + } else if line.starts_with("TZ=") { + line.truncate(line.trim_end().len()); + line.replace_range(..3, ""); + return Ok(line); + } + } +} diff --git a/third_party/rust/iana-time-zone/src/tz_linux.rs b/third_party/rust/iana-time-zone/src/tz_linux.rs new file mode 100644 index 00000000000..e12d1c8fa00 --- /dev/null +++ b/third_party/rust/iana-time-zone/src/tz_linux.rs @@ -0,0 +1,184 @@ +use std::fs::{read_link, read_to_string}; + +pub(crate) fn get_timezone_inner() -> Result { + etc_localtime() + .or_else(|_| etc_timezone()) + .or_else(|_| openwrt::etc_config_system()) +} + +fn etc_timezone() -> Result { + // see https://stackoverflow.com/a/12523283 + let mut contents = read_to_string("/etc/timezone")?; + // Trim to the correct length without allocating. + contents.truncate(contents.trim_end().len()); + Ok(contents) +} + +fn etc_localtime() -> Result { + // Per : + // “ The /etc/localtime file configures the system-wide timezone of the local system that is + // used by applications for presentation to the user. It should be an absolute or relative + // symbolic link pointing to /usr/share/zoneinfo/, followed by a timezone identifier such as + // "Europe/Berlin" or "Etc/UTC". The resulting link should lead to the corresponding binary + // tzfile(5) timezone data for the configured timezone. ” + + // Systemd does not canonicalize the link, but only checks if it is prefixed by + // "/usr/share/zoneinfo/" or "../usr/share/zoneinfo/". So we do the same. + // + + const PREFIXES: &[&str] = &[ + "/usr/share/zoneinfo/", // absolute path + "../usr/share/zoneinfo/", // relative path + "/etc/zoneinfo/", // absolute path for NixOS + "../etc/zoneinfo/", // relative path for NixOS + ]; + let mut s = read_link("/etc/localtime")? + .into_os_string() + .into_string() + .map_err(|_| crate::GetTimezoneError::FailedParsingString)?; + for &prefix in PREFIXES { + if s.starts_with(prefix) { + // Trim to the correct length without allocating. + s.replace_range(..prefix.len(), ""); + return Ok(s); + } + } + Err(crate::GetTimezoneError::FailedParsingString) +} + +mod openwrt { + use std::io::BufRead; + use std::{fs, io, iter}; + + pub(crate) fn etc_config_system() -> Result { + let f = fs::OpenOptions::new() + .read(true) + .open("/etc/config/system")?; + let mut f = io::BufReader::new(f); + let mut in_system_section = false; + let mut line = String::with_capacity(80); + + // prefer option "zonename" (IANA time zone) over option "timezone" (POSIX time zone) + let mut timezone = None; + loop { + line.clear(); + f.read_line(&mut line)?; + if line.is_empty() { + break; + } + + let mut iter = IterWords(&line); + let mut next = || iter.next().transpose(); + + if let Some(keyword) = next()? { + if keyword == "config" { + in_system_section = next()? == Some("system") && next()?.is_none(); + } else if in_system_section && keyword == "option" { + if let Some(key) = next()? { + if key == "zonename" { + if let (Some(zonename), None) = (next()?, next()?) { + return Ok(zonename.to_owned()); + } + } else if key == "timezone" { + if let (Some(value), None) = (next()?, next()?) { + timezone = Some(value.to_owned()); + } + } + } + } + } + } + + timezone.ok_or(crate::GetTimezoneError::OsError) + } + + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] + struct BrokenQuote; + + impl From for crate::GetTimezoneError { + fn from(_: BrokenQuote) -> Self { + crate::GetTimezoneError::FailedParsingString + } + } + + /// Iterated over all words in a OpenWRT config line. + struct IterWords<'a>(&'a str); + + impl<'a> Iterator for IterWords<'a> { + type Item = Result<&'a str, BrokenQuote>; + + fn next(&mut self) -> Option { + match read_word(self.0) { + Ok(Some((item, tail))) => { + self.0 = tail; + Some(Ok(item)) + } + Ok(None) => { + self.0 = ""; + None + } + Err(err) => { + self.0 = ""; + Some(Err(err)) + } + } + } + } + + impl iter::FusedIterator for IterWords<'_> {} + + /// Read the next word in a OpenWRT config line. Strip any surrounding quotation marks. + /// + /// Returns + /// + /// * a tuple `Some((word, remaining_line))` if found, + /// * `None` if the line is exhausted, or + /// * `Err(BrokenQuote)` if the line could not be parsed. + #[allow(clippy::manual_strip)] // needs to be compatile to 1.36 + fn read_word(s: &str) -> Result, BrokenQuote> { + let s = s.trim_start(); + if s.is_empty() || s.starts_with('#') { + Ok(None) + } else if s.starts_with('\'') { + let mut iter = s[1..].splitn(2, '\''); + match (iter.next(), iter.next()) { + (Some(item), Some(tail)) => Ok(Some((item, tail))), + _ => Err(BrokenQuote), + } + } else if s.starts_with('"') { + let mut iter = s[1..].splitn(2, '"'); + match (iter.next(), iter.next()) { + (Some(item), Some(tail)) => Ok(Some((item, tail))), + _ => Err(BrokenQuote), + } + } else { + let mut iter = s.splitn(2, |c: char| c.is_whitespace()); + match (iter.next(), iter.next()) { + (Some(item), Some(tail)) => Ok(Some((item, tail))), + _ => Ok(Some((s, ""))), + } + } + } + + #[cfg(test)] + #[test] + fn test_read_word() { + assert_eq!( + read_word(" option timezone 'CST-8'\n").unwrap(), + Some(("option", "timezone 'CST-8'\n")), + ); + assert_eq!( + read_word("timezone 'CST-8'\n").unwrap(), + Some(("timezone", "'CST-8'\n")), + ); + assert_eq!(read_word("'CST-8'\n").unwrap(), Some(("CST-8", "\n"))); + assert_eq!(read_word("\n").unwrap(), None); + + assert_eq!( + read_word(r#""time 'Zone'""#).unwrap(), + Some(("time 'Zone'", "")), + ); + + assert_eq!(read_word("'CST-8").unwrap_err(), BrokenQuote); + } +} diff --git a/third_party/rust/iana-time-zone/src/tz_netbsd.rs b/third_party/rust/iana-time-zone/src/tz_netbsd.rs new file mode 100644 index 00000000000..84cf8b05ec7 --- /dev/null +++ b/third_party/rust/iana-time-zone/src/tz_netbsd.rs @@ -0,0 +1,25 @@ +use std::fs::read_link; + +pub(crate) fn get_timezone_inner() -> Result { + // see https://www.cyberciti.biz/faq/openbsd-time-zone-howto/ + + // This is a backport of the Linux implementation. + // NetBSDs is less than thorough how the softlink should be set up. + + const PREFIXES: &[&str] = &[ + "/usr/share/zoneinfo/", // absolute path + "../usr/share/zoneinfo/", // relative path + ]; + let mut s = read_link("/etc/localtime")? + .into_os_string() + .into_string() + .map_err(|_| crate::GetTimezoneError::FailedParsingString)?; + for &prefix in PREFIXES { + if s.starts_with(prefix) { + // Trim to the correct length without allocating. + s.replace_range(..prefix.len(), ""); + return Ok(s); + } + } + Err(crate::GetTimezoneError::FailedParsingString) +} diff --git a/third_party/rust/iana-time-zone/src/tz_ohos.rs b/third_party/rust/iana-time-zone/src/tz_ohos.rs new file mode 100644 index 00000000000..2566bdaf30a --- /dev/null +++ b/third_party/rust/iana-time-zone/src/tz_ohos.rs @@ -0,0 +1,47 @@ +//! OpenHarmony doesn't have `/etc/localtime`, we have to use it's "Time Service" to get the timezone information: +//! +//! - [API Reference](https://gitee.com/openharmony/docs/blob/43726785b4033887cd1a838aaaca5e255897a71e/en/application-dev/reference/apis-basic-services-kit/_time_service.md#oh_timeservice_gettimezone) + +use crate::ffi_utils::buffer::{tzname_buf, MAX_LEN}; +use crate::GetTimezoneError; +use std::ffi::{c_char, CStr}; + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[repr(C)] +#[allow(non_camel_case_types)] +#[allow(dead_code)] +enum TimeService_ErrCode { + TIMESERVICE_ERR_OK = 0, + TIMESERVICE_ERR_INTERNAL_ERROR = 13000001, + TIMESERVICE_ERR_INVALID_PARAMETER = 13000002, +} + +#[link(name = "time_service_ndk", kind = "dylib")] +extern "C" { + fn OH_TimeService_GetTimeZone(timeZone: *mut c_char, len: u32) -> TimeService_ErrCode; +} + +/// TODO: Change this `CStr::from_bytes_until_nul` once MSRV was bumped above 1.69.0 +fn from_bytes_until_nul(bytes: &[u8]) -> Option<&CStr> { + let nul_pos = bytes.iter().position(|&b| b == 0)?; + // SAFETY: + // 1. nul_pos + 1 <= bytes.len() + // 2. We know there is a nul byte at nul_pos, so this slice (ending at the nul byte) is a well-formed C string. + Some(unsafe { CStr::from_bytes_with_nul_unchecked(&bytes[..=nul_pos]) }) +} + +pub(crate) fn get_timezone_inner() -> Result { + let mut time_zone = tzname_buf(); + // SAFETY: + // `time_zone` is a valid buffer with a length of 40 bytes. + let ret = unsafe { + OH_TimeService_GetTimeZone(time_zone.as_mut_ptr().cast::(), MAX_LEN as u32 - 1) + }; + if ret != TimeService_ErrCode::TIMESERVICE_ERR_OK { + return Err(GetTimezoneError::OsError); + } + from_bytes_until_nul(&time_zone) + .and_then(|x| x.to_str().ok()) + .map(|x| x.to_owned()) + .ok_or(GetTimezoneError::OsError) +} diff --git a/third_party/rust/iana-time-zone/src/tz_wasm32_unknown.rs b/third_party/rust/iana-time-zone/src/tz_wasm32_unknown.rs new file mode 100644 index 00000000000..69c36b5532a --- /dev/null +++ b/third_party/rust/iana-time-zone/src/tz_wasm32_unknown.rs @@ -0,0 +1,21 @@ +use js_sys::{Array, Intl, Object, Reflect}; +use wasm_bindgen::JsValue; + +pub(crate) fn get_timezone_inner() -> Result { + let intl = Intl::DateTimeFormat::new(&Array::new(), &Object::new()).resolved_options(); + Reflect::get(&intl, &JsValue::from_str("timeZone")) + .ok() + .and_then(|tz| tz.as_string()) + .ok_or(crate::GetTimezoneError::OsError) +} + +#[cfg(test)] +mod tests { + use wasm_bindgen_test::*; + + #[wasm_bindgen_test] + fn pass() { + let tz = super::get_timezone_inner().unwrap(); + console_log!("tz={:?}", tz); + } +} diff --git a/third_party/rust/iana-time-zone/src/tz_windows.rs b/third_party/rust/iana-time-zone/src/tz_windows.rs new file mode 100644 index 00000000000..4608be4c614 --- /dev/null +++ b/third_party/rust/iana-time-zone/src/tz_windows.rs @@ -0,0 +1,16 @@ +#[path = "windows_bindings.rs"] +#[allow(missing_debug_implementations, clippy::undocumented_unsafe_blocks)] +mod windows_bindings; +use windows_bindings::Windows::Globalization::Calendar; + +impl From for crate::GetTimezoneError { + fn from(orig: windows_core::Error) -> Self { + crate::GetTimezoneError::IoError(std::io::Error::new(std::io::ErrorKind::Other, orig)) + } +} + +pub(crate) fn get_timezone_inner() -> Result { + let cal = Calendar::new()?; + let tz_hstring = cal.GetTimeZone()?; + Ok(tz_hstring.to_string()) +} diff --git a/third_party/rust/iana-time-zone/src/windows_bindings.rs b/third_party/rust/iana-time-zone/src/windows_bindings.rs new file mode 100644 index 00000000000..2988d9d23dd --- /dev/null +++ b/third_party/rust/iana-time-zone/src/windows_bindings.rs @@ -0,0 +1,1630 @@ +// Bindings generated by `windows-bindgen` 0.59.0 + +#![allow( + non_snake_case, + non_upper_case_globals, + non_camel_case_types, + dead_code, + clippy::all +)] + +pub mod Windows { + pub mod Globalization { + #[repr(transparent)] + #[derive(Clone, Debug, Eq, PartialEq)] + pub struct Calendar(windows_core::IUnknown); + windows_core::imp::interface_hierarchy!( + Calendar, + windows_core::IUnknown, + windows_core::IInspectable + ); + impl Calendar { + pub fn new() -> windows_core::Result { + Self::IActivationFactory(|f| f.ActivateInstance::()) + } + fn IActivationFactory< + R, + F: FnOnce(&windows_core::imp::IGenericFactory) -> windows_core::Result, + >( + callback: F, + ) -> windows_core::Result { + static SHARED: windows_core::imp::FactoryCache< + Calendar, + windows_core::imp::IGenericFactory, + > = windows_core::imp::FactoryCache::new(); + SHARED.call(callback) + } + pub fn Clone(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).Clone)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .and_then(|| windows_core::Type::from_abi(result__)) + } + } + pub fn SetToMin(&self) -> windows_core::Result<()> { + let this = self; + unsafe { + (windows_core::Interface::vtable(this).SetToMin)( + windows_core::Interface::as_raw(this), + ) + .ok() + } + } + pub fn SetToMax(&self) -> windows_core::Result<()> { + let this = self; + unsafe { + (windows_core::Interface::vtable(this).SetToMax)( + windows_core::Interface::as_raw(this), + ) + .ok() + } + } + pub fn NumeralSystem(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).NumeralSystem)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| core::mem::transmute(result__)) + } + } + pub fn SetNumeralSystem( + &self, + value: &windows_core::HSTRING, + ) -> windows_core::Result<()> { + let this = self; + unsafe { + (windows_core::Interface::vtable(this).SetNumeralSystem)( + windows_core::Interface::as_raw(this), + core::mem::transmute_copy(value), + ) + .ok() + } + } + pub fn GetCalendarSystem(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).GetCalendarSystem)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| core::mem::transmute(result__)) + } + } + pub fn ChangeCalendarSystem( + &self, + value: &windows_core::HSTRING, + ) -> windows_core::Result<()> { + let this = self; + unsafe { + (windows_core::Interface::vtable(this).ChangeCalendarSystem)( + windows_core::Interface::as_raw(this), + core::mem::transmute_copy(value), + ) + .ok() + } + } + pub fn GetClock(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).GetClock)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| core::mem::transmute(result__)) + } + } + pub fn ChangeClock(&self, value: &windows_core::HSTRING) -> windows_core::Result<()> { + let this = self; + unsafe { + (windows_core::Interface::vtable(this).ChangeClock)( + windows_core::Interface::as_raw(this), + core::mem::transmute_copy(value), + ) + .ok() + } + } + pub fn SetToNow(&self) -> windows_core::Result<()> { + let this = self; + unsafe { + (windows_core::Interface::vtable(this).SetToNow)( + windows_core::Interface::as_raw(this), + ) + .ok() + } + } + pub fn FirstEra(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).FirstEra)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| result__) + } + } + pub fn LastEra(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).LastEra)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| result__) + } + } + pub fn NumberOfEras(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).NumberOfEras)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| result__) + } + } + pub fn Era(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).Era)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| result__) + } + } + pub fn SetEra(&self, value: i32) -> windows_core::Result<()> { + let this = self; + unsafe { + (windows_core::Interface::vtable(this).SetEra)( + windows_core::Interface::as_raw(this), + value, + ) + .ok() + } + } + pub fn AddEras(&self, eras: i32) -> windows_core::Result<()> { + let this = self; + unsafe { + (windows_core::Interface::vtable(this).AddEras)( + windows_core::Interface::as_raw(this), + eras, + ) + .ok() + } + } + pub fn EraAsFullString(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).EraAsFullString)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| core::mem::transmute(result__)) + } + } + pub fn EraAsString( + &self, + ideallength: i32, + ) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).EraAsString)( + windows_core::Interface::as_raw(this), + ideallength, + &mut result__, + ) + .map(|| core::mem::transmute(result__)) + } + } + pub fn FirstYearInThisEra(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).FirstYearInThisEra)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| result__) + } + } + pub fn LastYearInThisEra(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).LastYearInThisEra)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| result__) + } + } + pub fn NumberOfYearsInThisEra(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).NumberOfYearsInThisEra)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| result__) + } + } + pub fn Year(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).Year)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| result__) + } + } + pub fn SetYear(&self, value: i32) -> windows_core::Result<()> { + let this = self; + unsafe { + (windows_core::Interface::vtable(this).SetYear)( + windows_core::Interface::as_raw(this), + value, + ) + .ok() + } + } + pub fn AddYears(&self, years: i32) -> windows_core::Result<()> { + let this = self; + unsafe { + (windows_core::Interface::vtable(this).AddYears)( + windows_core::Interface::as_raw(this), + years, + ) + .ok() + } + } + pub fn YearAsString(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).YearAsString)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| core::mem::transmute(result__)) + } + } + pub fn YearAsTruncatedString( + &self, + remainingdigits: i32, + ) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).YearAsTruncatedString)( + windows_core::Interface::as_raw(this), + remainingdigits, + &mut result__, + ) + .map(|| core::mem::transmute(result__)) + } + } + pub fn YearAsPaddedString( + &self, + mindigits: i32, + ) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).YearAsPaddedString)( + windows_core::Interface::as_raw(this), + mindigits, + &mut result__, + ) + .map(|| core::mem::transmute(result__)) + } + } + pub fn FirstMonthInThisYear(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).FirstMonthInThisYear)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| result__) + } + } + pub fn LastMonthInThisYear(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).LastMonthInThisYear)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| result__) + } + } + pub fn NumberOfMonthsInThisYear(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).NumberOfMonthsInThisYear)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| result__) + } + } + pub fn Month(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).Month)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| result__) + } + } + pub fn SetMonth(&self, value: i32) -> windows_core::Result<()> { + let this = self; + unsafe { + (windows_core::Interface::vtable(this).SetMonth)( + windows_core::Interface::as_raw(this), + value, + ) + .ok() + } + } + pub fn AddMonths(&self, months: i32) -> windows_core::Result<()> { + let this = self; + unsafe { + (windows_core::Interface::vtable(this).AddMonths)( + windows_core::Interface::as_raw(this), + months, + ) + .ok() + } + } + pub fn MonthAsFullString(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).MonthAsFullString)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| core::mem::transmute(result__)) + } + } + pub fn MonthAsString( + &self, + ideallength: i32, + ) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).MonthAsString)( + windows_core::Interface::as_raw(this), + ideallength, + &mut result__, + ) + .map(|| core::mem::transmute(result__)) + } + } + pub fn MonthAsFullSoloString(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).MonthAsFullSoloString)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| core::mem::transmute(result__)) + } + } + pub fn MonthAsSoloString( + &self, + ideallength: i32, + ) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).MonthAsSoloString)( + windows_core::Interface::as_raw(this), + ideallength, + &mut result__, + ) + .map(|| core::mem::transmute(result__)) + } + } + pub fn MonthAsNumericString(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).MonthAsNumericString)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| core::mem::transmute(result__)) + } + } + pub fn MonthAsPaddedNumericString( + &self, + mindigits: i32, + ) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).MonthAsPaddedNumericString)( + windows_core::Interface::as_raw(this), + mindigits, + &mut result__, + ) + .map(|| core::mem::transmute(result__)) + } + } + pub fn AddWeeks(&self, weeks: i32) -> windows_core::Result<()> { + let this = self; + unsafe { + (windows_core::Interface::vtable(this).AddWeeks)( + windows_core::Interface::as_raw(this), + weeks, + ) + .ok() + } + } + pub fn FirstDayInThisMonth(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).FirstDayInThisMonth)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| result__) + } + } + pub fn LastDayInThisMonth(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).LastDayInThisMonth)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| result__) + } + } + pub fn NumberOfDaysInThisMonth(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).NumberOfDaysInThisMonth)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| result__) + } + } + pub fn Day(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).Day)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| result__) + } + } + pub fn SetDay(&self, value: i32) -> windows_core::Result<()> { + let this = self; + unsafe { + (windows_core::Interface::vtable(this).SetDay)( + windows_core::Interface::as_raw(this), + value, + ) + .ok() + } + } + pub fn AddDays(&self, days: i32) -> windows_core::Result<()> { + let this = self; + unsafe { + (windows_core::Interface::vtable(this).AddDays)( + windows_core::Interface::as_raw(this), + days, + ) + .ok() + } + } + pub fn DayAsString(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).DayAsString)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| core::mem::transmute(result__)) + } + } + pub fn DayAsPaddedString( + &self, + mindigits: i32, + ) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).DayAsPaddedString)( + windows_core::Interface::as_raw(this), + mindigits, + &mut result__, + ) + .map(|| core::mem::transmute(result__)) + } + } + pub fn DayOfWeekAsFullString(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).DayOfWeekAsFullString)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| core::mem::transmute(result__)) + } + } + pub fn DayOfWeekAsString( + &self, + ideallength: i32, + ) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).DayOfWeekAsString)( + windows_core::Interface::as_raw(this), + ideallength, + &mut result__, + ) + .map(|| core::mem::transmute(result__)) + } + } + pub fn DayOfWeekAsFullSoloString(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).DayOfWeekAsFullSoloString)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| core::mem::transmute(result__)) + } + } + pub fn DayOfWeekAsSoloString( + &self, + ideallength: i32, + ) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).DayOfWeekAsSoloString)( + windows_core::Interface::as_raw(this), + ideallength, + &mut result__, + ) + .map(|| core::mem::transmute(result__)) + } + } + pub fn FirstPeriodInThisDay(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).FirstPeriodInThisDay)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| result__) + } + } + pub fn LastPeriodInThisDay(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).LastPeriodInThisDay)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| result__) + } + } + pub fn NumberOfPeriodsInThisDay(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).NumberOfPeriodsInThisDay)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| result__) + } + } + pub fn Period(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).Period)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| result__) + } + } + pub fn SetPeriod(&self, value: i32) -> windows_core::Result<()> { + let this = self; + unsafe { + (windows_core::Interface::vtable(this).SetPeriod)( + windows_core::Interface::as_raw(this), + value, + ) + .ok() + } + } + pub fn AddPeriods(&self, periods: i32) -> windows_core::Result<()> { + let this = self; + unsafe { + (windows_core::Interface::vtable(this).AddPeriods)( + windows_core::Interface::as_raw(this), + periods, + ) + .ok() + } + } + pub fn PeriodAsFullString(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).PeriodAsFullString)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| core::mem::transmute(result__)) + } + } + pub fn PeriodAsString( + &self, + ideallength: i32, + ) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).PeriodAsString)( + windows_core::Interface::as_raw(this), + ideallength, + &mut result__, + ) + .map(|| core::mem::transmute(result__)) + } + } + pub fn FirstHourInThisPeriod(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).FirstHourInThisPeriod)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| result__) + } + } + pub fn LastHourInThisPeriod(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).LastHourInThisPeriod)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| result__) + } + } + pub fn NumberOfHoursInThisPeriod(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).NumberOfHoursInThisPeriod)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| result__) + } + } + pub fn Hour(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).Hour)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| result__) + } + } + pub fn SetHour(&self, value: i32) -> windows_core::Result<()> { + let this = self; + unsafe { + (windows_core::Interface::vtable(this).SetHour)( + windows_core::Interface::as_raw(this), + value, + ) + .ok() + } + } + pub fn AddHours(&self, hours: i32) -> windows_core::Result<()> { + let this = self; + unsafe { + (windows_core::Interface::vtable(this).AddHours)( + windows_core::Interface::as_raw(this), + hours, + ) + .ok() + } + } + pub fn HourAsString(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).HourAsString)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| core::mem::transmute(result__)) + } + } + pub fn HourAsPaddedString( + &self, + mindigits: i32, + ) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).HourAsPaddedString)( + windows_core::Interface::as_raw(this), + mindigits, + &mut result__, + ) + .map(|| core::mem::transmute(result__)) + } + } + pub fn Minute(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).Minute)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| result__) + } + } + pub fn SetMinute(&self, value: i32) -> windows_core::Result<()> { + let this = self; + unsafe { + (windows_core::Interface::vtable(this).SetMinute)( + windows_core::Interface::as_raw(this), + value, + ) + .ok() + } + } + pub fn AddMinutes(&self, minutes: i32) -> windows_core::Result<()> { + let this = self; + unsafe { + (windows_core::Interface::vtable(this).AddMinutes)( + windows_core::Interface::as_raw(this), + minutes, + ) + .ok() + } + } + pub fn MinuteAsString(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).MinuteAsString)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| core::mem::transmute(result__)) + } + } + pub fn MinuteAsPaddedString( + &self, + mindigits: i32, + ) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).MinuteAsPaddedString)( + windows_core::Interface::as_raw(this), + mindigits, + &mut result__, + ) + .map(|| core::mem::transmute(result__)) + } + } + pub fn Second(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).Second)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| result__) + } + } + pub fn SetSecond(&self, value: i32) -> windows_core::Result<()> { + let this = self; + unsafe { + (windows_core::Interface::vtable(this).SetSecond)( + windows_core::Interface::as_raw(this), + value, + ) + .ok() + } + } + pub fn AddSeconds(&self, seconds: i32) -> windows_core::Result<()> { + let this = self; + unsafe { + (windows_core::Interface::vtable(this).AddSeconds)( + windows_core::Interface::as_raw(this), + seconds, + ) + .ok() + } + } + pub fn SecondAsString(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).SecondAsString)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| core::mem::transmute(result__)) + } + } + pub fn SecondAsPaddedString( + &self, + mindigits: i32, + ) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).SecondAsPaddedString)( + windows_core::Interface::as_raw(this), + mindigits, + &mut result__, + ) + .map(|| core::mem::transmute(result__)) + } + } + pub fn Nanosecond(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).Nanosecond)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| result__) + } + } + pub fn SetNanosecond(&self, value: i32) -> windows_core::Result<()> { + let this = self; + unsafe { + (windows_core::Interface::vtable(this).SetNanosecond)( + windows_core::Interface::as_raw(this), + value, + ) + .ok() + } + } + pub fn AddNanoseconds(&self, nanoseconds: i32) -> windows_core::Result<()> { + let this = self; + unsafe { + (windows_core::Interface::vtable(this).AddNanoseconds)( + windows_core::Interface::as_raw(this), + nanoseconds, + ) + .ok() + } + } + pub fn NanosecondAsString(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).NanosecondAsString)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| core::mem::transmute(result__)) + } + } + pub fn NanosecondAsPaddedString( + &self, + mindigits: i32, + ) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).NanosecondAsPaddedString)( + windows_core::Interface::as_raw(this), + mindigits, + &mut result__, + ) + .map(|| core::mem::transmute(result__)) + } + } + pub fn Compare(&self, other: P0) -> windows_core::Result + where + P0: windows_core::Param, + { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).Compare)( + windows_core::Interface::as_raw(this), + other.param().abi(), + &mut result__, + ) + .map(|| result__) + } + } + pub fn CopyTo(&self, other: P0) -> windows_core::Result<()> + where + P0: windows_core::Param, + { + let this = self; + unsafe { + (windows_core::Interface::vtable(this).CopyTo)( + windows_core::Interface::as_raw(this), + other.param().abi(), + ) + .ok() + } + } + pub fn FirstMinuteInThisHour(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).FirstMinuteInThisHour)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| result__) + } + } + pub fn LastMinuteInThisHour(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).LastMinuteInThisHour)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| result__) + } + } + pub fn NumberOfMinutesInThisHour(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).NumberOfMinutesInThisHour)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| result__) + } + } + pub fn FirstSecondInThisMinute(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).FirstSecondInThisMinute)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| result__) + } + } + pub fn LastSecondInThisMinute(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).LastSecondInThisMinute)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| result__) + } + } + pub fn NumberOfSecondsInThisMinute(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).NumberOfSecondsInThisMinute)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| result__) + } + } + pub fn ResolvedLanguage(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).ResolvedLanguage)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| core::mem::transmute(result__)) + } + } + pub fn IsDaylightSavingTime(&self) -> windows_core::Result { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).IsDaylightSavingTime)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| result__) + } + } + pub fn GetTimeZone(&self) -> windows_core::Result { + let this = &windows_core::Interface::cast::(self)?; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).GetTimeZone)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| core::mem::transmute(result__)) + } + } + pub fn ChangeTimeZone( + &self, + timezoneid: &windows_core::HSTRING, + ) -> windows_core::Result<()> { + let this = &windows_core::Interface::cast::(self)?; + unsafe { + (windows_core::Interface::vtable(this).ChangeTimeZone)( + windows_core::Interface::as_raw(this), + core::mem::transmute_copy(timezoneid), + ) + .ok() + } + } + pub fn TimeZoneAsFullString(&self) -> windows_core::Result { + let this = &windows_core::Interface::cast::(self)?; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).TimeZoneAsFullString)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .map(|| core::mem::transmute(result__)) + } + } + pub fn TimeZoneAsString( + &self, + ideallength: i32, + ) -> windows_core::Result { + let this = &windows_core::Interface::cast::(self)?; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).TimeZoneAsString)( + windows_core::Interface::as_raw(this), + ideallength, + &mut result__, + ) + .map(|| core::mem::transmute(result__)) + } + } + fn ICalendarFactory windows_core::Result>( + callback: F, + ) -> windows_core::Result { + static SHARED: windows_core::imp::FactoryCache = + windows_core::imp::FactoryCache::new(); + SHARED.call(callback) + } + fn ICalendarFactory2 windows_core::Result>( + callback: F, + ) -> windows_core::Result { + static SHARED: windows_core::imp::FactoryCache = + windows_core::imp::FactoryCache::new(); + SHARED.call(callback) + } + } + impl windows_core::RuntimeType for Calendar { + const SIGNATURE: windows_core::imp::ConstBuffer = + windows_core::imp::ConstBuffer::for_class::(); + } + unsafe impl windows_core::Interface for Calendar { + type Vtable = ::Vtable; + const IID: windows_core::GUID = ::IID; + } + impl windows_core::RuntimeName for Calendar { + const NAME: &'static str = "Windows.Globalization.Calendar"; + } + unsafe impl Send for Calendar {} + unsafe impl Sync for Calendar {} + windows_core::imp::define_interface!( + ICalendar, + ICalendar_Vtbl, + 0xca30221d_86d9_40fb_a26b_d44eb7cf08ea + ); + impl windows_core::RuntimeType for ICalendar { + const SIGNATURE: windows_core::imp::ConstBuffer = + windows_core::imp::ConstBuffer::for_interface::(); + } + #[repr(C)] + pub struct ICalendar_Vtbl { + pub base__: windows_core::IInspectable_Vtbl, + pub Clone: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut *mut core::ffi::c_void, + ) -> windows_core::HRESULT, + pub SetToMin: + unsafe extern "system" fn(*mut core::ffi::c_void) -> windows_core::HRESULT, + pub SetToMax: + unsafe extern "system" fn(*mut core::ffi::c_void) -> windows_core::HRESULT, + get_Languages: usize, + pub NumeralSystem: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut *mut core::ffi::c_void, + ) -> windows_core::HRESULT, + pub SetNumeralSystem: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut core::ffi::c_void, + ) -> windows_core::HRESULT, + pub GetCalendarSystem: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut *mut core::ffi::c_void, + ) -> windows_core::HRESULT, + pub ChangeCalendarSystem: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut core::ffi::c_void, + ) + -> windows_core::HRESULT, + pub GetClock: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut *mut core::ffi::c_void, + ) -> windows_core::HRESULT, + pub ChangeClock: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut core::ffi::c_void, + ) -> windows_core::HRESULT, + GetDateTime: usize, + SetDateTime: usize, + pub SetToNow: + unsafe extern "system" fn(*mut core::ffi::c_void) -> windows_core::HRESULT, + pub FirstEra: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut i32, + ) -> windows_core::HRESULT, + pub LastEra: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut i32, + ) -> windows_core::HRESULT, + pub NumberOfEras: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut i32, + ) -> windows_core::HRESULT, + pub Era: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut i32, + ) -> windows_core::HRESULT, + pub SetEra: + unsafe extern "system" fn(*mut core::ffi::c_void, i32) -> windows_core::HRESULT, + pub AddEras: + unsafe extern "system" fn(*mut core::ffi::c_void, i32) -> windows_core::HRESULT, + pub EraAsFullString: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut *mut core::ffi::c_void, + ) -> windows_core::HRESULT, + pub EraAsString: unsafe extern "system" fn( + *mut core::ffi::c_void, + i32, + *mut *mut core::ffi::c_void, + ) -> windows_core::HRESULT, + pub FirstYearInThisEra: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut i32, + ) -> windows_core::HRESULT, + pub LastYearInThisEra: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut i32, + ) -> windows_core::HRESULT, + pub NumberOfYearsInThisEra: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut i32, + ) + -> windows_core::HRESULT, + pub Year: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut i32, + ) -> windows_core::HRESULT, + pub SetYear: + unsafe extern "system" fn(*mut core::ffi::c_void, i32) -> windows_core::HRESULT, + pub AddYears: + unsafe extern "system" fn(*mut core::ffi::c_void, i32) -> windows_core::HRESULT, + pub YearAsString: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut *mut core::ffi::c_void, + ) -> windows_core::HRESULT, + pub YearAsTruncatedString: unsafe extern "system" fn( + *mut core::ffi::c_void, + i32, + *mut *mut core::ffi::c_void, + ) + -> windows_core::HRESULT, + pub YearAsPaddedString: unsafe extern "system" fn( + *mut core::ffi::c_void, + i32, + *mut *mut core::ffi::c_void, + ) -> windows_core::HRESULT, + pub FirstMonthInThisYear: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut i32, + ) + -> windows_core::HRESULT, + pub LastMonthInThisYear: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut i32, + ) + -> windows_core::HRESULT, + pub NumberOfMonthsInThisYear: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut i32, + ) + -> windows_core::HRESULT, + pub Month: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut i32, + ) -> windows_core::HRESULT, + pub SetMonth: + unsafe extern "system" fn(*mut core::ffi::c_void, i32) -> windows_core::HRESULT, + pub AddMonths: + unsafe extern "system" fn(*mut core::ffi::c_void, i32) -> windows_core::HRESULT, + pub MonthAsFullString: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut *mut core::ffi::c_void, + ) -> windows_core::HRESULT, + pub MonthAsString: unsafe extern "system" fn( + *mut core::ffi::c_void, + i32, + *mut *mut core::ffi::c_void, + ) -> windows_core::HRESULT, + pub MonthAsFullSoloString: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut *mut core::ffi::c_void, + ) + -> windows_core::HRESULT, + pub MonthAsSoloString: unsafe extern "system" fn( + *mut core::ffi::c_void, + i32, + *mut *mut core::ffi::c_void, + ) -> windows_core::HRESULT, + pub MonthAsNumericString: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut *mut core::ffi::c_void, + ) + -> windows_core::HRESULT, + pub MonthAsPaddedNumericString: unsafe extern "system" fn( + *mut core::ffi::c_void, + i32, + *mut *mut core::ffi::c_void, + ) + -> windows_core::HRESULT, + pub AddWeeks: + unsafe extern "system" fn(*mut core::ffi::c_void, i32) -> windows_core::HRESULT, + pub FirstDayInThisMonth: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut i32, + ) + -> windows_core::HRESULT, + pub LastDayInThisMonth: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut i32, + ) -> windows_core::HRESULT, + pub NumberOfDaysInThisMonth: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut i32, + ) + -> windows_core::HRESULT, + pub Day: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut i32, + ) -> windows_core::HRESULT, + pub SetDay: + unsafe extern "system" fn(*mut core::ffi::c_void, i32) -> windows_core::HRESULT, + pub AddDays: + unsafe extern "system" fn(*mut core::ffi::c_void, i32) -> windows_core::HRESULT, + pub DayAsString: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut *mut core::ffi::c_void, + ) -> windows_core::HRESULT, + pub DayAsPaddedString: unsafe extern "system" fn( + *mut core::ffi::c_void, + i32, + *mut *mut core::ffi::c_void, + ) -> windows_core::HRESULT, + get_DayOfWeek: usize, + pub DayOfWeekAsFullString: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut *mut core::ffi::c_void, + ) + -> windows_core::HRESULT, + pub DayOfWeekAsString: unsafe extern "system" fn( + *mut core::ffi::c_void, + i32, + *mut *mut core::ffi::c_void, + ) -> windows_core::HRESULT, + pub DayOfWeekAsFullSoloString: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut *mut core::ffi::c_void, + ) + -> windows_core::HRESULT, + pub DayOfWeekAsSoloString: unsafe extern "system" fn( + *mut core::ffi::c_void, + i32, + *mut *mut core::ffi::c_void, + ) + -> windows_core::HRESULT, + pub FirstPeriodInThisDay: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut i32, + ) + -> windows_core::HRESULT, + pub LastPeriodInThisDay: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut i32, + ) + -> windows_core::HRESULT, + pub NumberOfPeriodsInThisDay: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut i32, + ) + -> windows_core::HRESULT, + pub Period: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut i32, + ) -> windows_core::HRESULT, + pub SetPeriod: + unsafe extern "system" fn(*mut core::ffi::c_void, i32) -> windows_core::HRESULT, + pub AddPeriods: + unsafe extern "system" fn(*mut core::ffi::c_void, i32) -> windows_core::HRESULT, + pub PeriodAsFullString: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut *mut core::ffi::c_void, + ) -> windows_core::HRESULT, + pub PeriodAsString: unsafe extern "system" fn( + *mut core::ffi::c_void, + i32, + *mut *mut core::ffi::c_void, + ) -> windows_core::HRESULT, + pub FirstHourInThisPeriod: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut i32, + ) + -> windows_core::HRESULT, + pub LastHourInThisPeriod: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut i32, + ) + -> windows_core::HRESULT, + pub NumberOfHoursInThisPeriod: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut i32, + ) + -> windows_core::HRESULT, + pub Hour: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut i32, + ) -> windows_core::HRESULT, + pub SetHour: + unsafe extern "system" fn(*mut core::ffi::c_void, i32) -> windows_core::HRESULT, + pub AddHours: + unsafe extern "system" fn(*mut core::ffi::c_void, i32) -> windows_core::HRESULT, + pub HourAsString: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut *mut core::ffi::c_void, + ) -> windows_core::HRESULT, + pub HourAsPaddedString: unsafe extern "system" fn( + *mut core::ffi::c_void, + i32, + *mut *mut core::ffi::c_void, + ) -> windows_core::HRESULT, + pub Minute: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut i32, + ) -> windows_core::HRESULT, + pub SetMinute: + unsafe extern "system" fn(*mut core::ffi::c_void, i32) -> windows_core::HRESULT, + pub AddMinutes: + unsafe extern "system" fn(*mut core::ffi::c_void, i32) -> windows_core::HRESULT, + pub MinuteAsString: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut *mut core::ffi::c_void, + ) -> windows_core::HRESULT, + pub MinuteAsPaddedString: unsafe extern "system" fn( + *mut core::ffi::c_void, + i32, + *mut *mut core::ffi::c_void, + ) + -> windows_core::HRESULT, + pub Second: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut i32, + ) -> windows_core::HRESULT, + pub SetSecond: + unsafe extern "system" fn(*mut core::ffi::c_void, i32) -> windows_core::HRESULT, + pub AddSeconds: + unsafe extern "system" fn(*mut core::ffi::c_void, i32) -> windows_core::HRESULT, + pub SecondAsString: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut *mut core::ffi::c_void, + ) -> windows_core::HRESULT, + pub SecondAsPaddedString: unsafe extern "system" fn( + *mut core::ffi::c_void, + i32, + *mut *mut core::ffi::c_void, + ) + -> windows_core::HRESULT, + pub Nanosecond: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut i32, + ) -> windows_core::HRESULT, + pub SetNanosecond: + unsafe extern "system" fn(*mut core::ffi::c_void, i32) -> windows_core::HRESULT, + pub AddNanoseconds: + unsafe extern "system" fn(*mut core::ffi::c_void, i32) -> windows_core::HRESULT, + pub NanosecondAsString: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut *mut core::ffi::c_void, + ) -> windows_core::HRESULT, + pub NanosecondAsPaddedString: unsafe extern "system" fn( + *mut core::ffi::c_void, + i32, + *mut *mut core::ffi::c_void, + ) + -> windows_core::HRESULT, + pub Compare: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut core::ffi::c_void, + *mut i32, + ) -> windows_core::HRESULT, + CompareDateTime: usize, + pub CopyTo: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut core::ffi::c_void, + ) -> windows_core::HRESULT, + pub FirstMinuteInThisHour: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut i32, + ) + -> windows_core::HRESULT, + pub LastMinuteInThisHour: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut i32, + ) + -> windows_core::HRESULT, + pub NumberOfMinutesInThisHour: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut i32, + ) + -> windows_core::HRESULT, + pub FirstSecondInThisMinute: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut i32, + ) + -> windows_core::HRESULT, + pub LastSecondInThisMinute: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut i32, + ) + -> windows_core::HRESULT, + pub NumberOfSecondsInThisMinute: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut i32, + ) + -> windows_core::HRESULT, + pub ResolvedLanguage: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut *mut core::ffi::c_void, + ) -> windows_core::HRESULT, + pub IsDaylightSavingTime: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut bool, + ) + -> windows_core::HRESULT, + } + windows_core::imp::define_interface!( + ICalendarFactory, + ICalendarFactory_Vtbl, + 0x83f58412_e56b_4c75_a66e_0f63d57758a6 + ); + impl windows_core::RuntimeType for ICalendarFactory { + const SIGNATURE: windows_core::imp::ConstBuffer = + windows_core::imp::ConstBuffer::for_interface::(); + } + #[repr(C)] + pub struct ICalendarFactory_Vtbl { + pub base__: windows_core::IInspectable_Vtbl, + CreateCalendarDefaultCalendarAndClock: usize, + CreateCalendar: usize, + } + windows_core::imp::define_interface!( + ICalendarFactory2, + ICalendarFactory2_Vtbl, + 0xb44b378c_ca7e_4590_9e72_ea2bec1a5115 + ); + impl windows_core::RuntimeType for ICalendarFactory2 { + const SIGNATURE: windows_core::imp::ConstBuffer = + windows_core::imp::ConstBuffer::for_interface::(); + } + #[repr(C)] + pub struct ICalendarFactory2_Vtbl { + pub base__: windows_core::IInspectable_Vtbl, + CreateCalendarWithTimeZone: usize, + } + windows_core::imp::define_interface!( + ITimeZoneOnCalendar, + ITimeZoneOnCalendar_Vtbl, + 0xbb3c25e5_46cf_4317_a3f5_02621ad54478 + ); + impl windows_core::RuntimeType for ITimeZoneOnCalendar { + const SIGNATURE: windows_core::imp::ConstBuffer = + windows_core::imp::ConstBuffer::for_interface::(); + } + #[repr(C)] + pub struct ITimeZoneOnCalendar_Vtbl { + pub base__: windows_core::IInspectable_Vtbl, + pub GetTimeZone: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut *mut core::ffi::c_void, + ) -> windows_core::HRESULT, + pub ChangeTimeZone: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut core::ffi::c_void, + ) -> windows_core::HRESULT, + pub TimeZoneAsFullString: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut *mut core::ffi::c_void, + ) + -> windows_core::HRESULT, + pub TimeZoneAsString: unsafe extern "system" fn( + *mut core::ffi::c_void, + i32, + *mut *mut core::ffi::c_void, + ) -> windows_core::HRESULT, + } + } +} diff --git a/third_party/rust/suggest/.cargo-checksum.json b/third_party/rust/suggest/.cargo-checksum.json index 7f80a636eff..138fca2f603 100644 --- a/third_party/rust/suggest/.cargo-checksum.json +++ b/third_party/rust/suggest/.cargo-checksum.json @@ -1 +1 @@ -{"files":{"Cargo.toml":"922b2e4d85f325dbef99d5e439558a75dbfda82e1d263c2dd3bfadac9879df18","README.md":"5e28baf874b643d756228bdab345e287bf107d3182dfe6a18aafadcc4b9a3fc9","benches/benchmark_all.rs":"5909dfb1e62793afb1f2bc15b75914527a4d14fce6796307c04a309e45c0598c","metrics.yaml":"0540ab2271aeab7f07335c7ceec12acde942995f9dcb3c29070489aa61899d56","src/benchmarks/README.md":"ccee8dbddba8762d0453fa855bd6984137b224b8c019f3dd8e86a3c303f51d71","src/benchmarks/client.rs":"e5897d4e2eda06809fa6dc6db4e780b9ef266f613fb113aa6613b83f7005dd0b","src/benchmarks/geoname.rs":"00fab05cf9465cf8e22e143cde75a81885411001b240af00efda4071975d0563","src/benchmarks/ingest.rs":"1f3b5eca704c51bc8f972e7a3492a518516461e5834f97a5f7d1855a048ab16b","src/benchmarks/mod.rs":"24751c377f549ead6b6e1d46685c649f51303cc090f6374c93633f71dcf3cadf","src/benchmarks/query.rs":"d54946063e72cf98e7f46d94665c17c66af637774c2bb50cd5798dbe63d74f3c","src/bin/debug_ingestion_sizes.rs":"ce6e810be7b3fc19e826d75b622b82cfab5a1a99397a6d0833c2c4eebff2d364","src/config.rs":"0ca876e845841bb6429862c0904c82265003f53b55aea053fac60aed278586a7","src/db.rs":"b4f1b46984ff5a6806546aa2acb2f4c5de3da2d067b3d42e0e7d13c2279ffcf1","src/error.rs":"e2ef3ec0e0b2b8ecbb8f2f1717d4cb753af06913b8395d086b7643098ad100a7","src/fakespot.rs":"f501c9fe5296e7c130a9fcb532b861465717652cb5ef688230bc7a3b94df91b1","src/geoname.rs":"77376dbc7d06532a7797a93b863f150317df7f31d9200d375c8ea489ac8bee6f","src/lib.rs":"a4c0989a01a7c13184049c1f11bc7813cd3cbfb6354fcca1f5a7204e45a0dc9c","src/metrics.rs":"871f0d834efbbc9e26d61f66fa31f0021dcf41444746cd7c082f93ba9628e399","src/pocket.rs":"1316668840ec9b4ea886223921dc9d3b5a1731d1a5206c0b1089f2a6c45c1b7b","src/provider.rs":"cf00114a4293a4c9d956efffb8a6859f3568ea0e4868c6b63b8273daca69b0d5","src/query.rs":"66f229272c9245eb8ee0cab237071627aec599f145f64da8894bcaeb1ed7c6f9","src/rs.rs":"f539abfa9997a0711c6a2bd9ae616698eaa5b89983b667d2b9881cfb9635b7b2","src/schema.rs":"206c3cf7198783b8fa8a7b3039a538f8408a0a7eeb1331adf1b8022e14f46a2b","src/store.rs":"a2bd55aa9b686a80d59eacef9ddf43bbdc9fffb995af4a85c24fbe48bfd17ba6","src/suggestion.rs":"e74abdc1eace082ad7ed8762bcee25b1943ec3d04d7e644959c5737b42732660","src/testing/client.rs":"47a32fd84c733001f11e8bfff94dc8c060b6b0780346dca5ddc7a5f5489c1d85","src/testing/data.rs":"6b3dad0414dd862d939f31672547e33a852056e8f891cfec9c1a9cc9fb91d54d","src/testing/mod.rs":"34120abb160a913069c3f7df03c4f52815be3461dbbf08fb0fcc4251a517ba71","src/util.rs":"52c6ec405637afa2d1a89f29fbbb7dcc341546b6deb97d326c4490bbf8713cb0","src/weather.rs":"7cc9167dcdfca49d6ad91eba6fba4d5fd49f45052f25a7fe3ad6749d3e6783fb","src/yelp.rs":"4e7cf36318c061bf73e20e52b05d8b1c6a0a904129594d92dddeb071ea52db93","uniffi.toml":"8205e4679ac26d53e70af0f85c013fd27cda1119f4322aebf5f2b9403d45a611"},"package":null} \ No newline at end of file +{"files":{"Cargo.toml":"922b2e4d85f325dbef99d5e439558a75dbfda82e1d263c2dd3bfadac9879df18","README.md":"5e28baf874b643d756228bdab345e287bf107d3182dfe6a18aafadcc4b9a3fc9","benches/benchmark_all.rs":"5909dfb1e62793afb1f2bc15b75914527a4d14fce6796307c04a309e45c0598c","metrics.yaml":"0540ab2271aeab7f07335c7ceec12acde942995f9dcb3c29070489aa61899d56","src/benchmarks/README.md":"ccee8dbddba8762d0453fa855bd6984137b224b8c019f3dd8e86a3c303f51d71","src/benchmarks/client.rs":"e5897d4e2eda06809fa6dc6db4e780b9ef266f613fb113aa6613b83f7005dd0b","src/benchmarks/geoname.rs":"00fab05cf9465cf8e22e143cde75a81885411001b240af00efda4071975d0563","src/benchmarks/ingest.rs":"1f3b5eca704c51bc8f972e7a3492a518516461e5834f97a5f7d1855a048ab16b","src/benchmarks/mod.rs":"24751c377f549ead6b6e1d46685c649f51303cc090f6374c93633f71dcf3cadf","src/benchmarks/query.rs":"d54946063e72cf98e7f46d94665c17c66af637774c2bb50cd5798dbe63d74f3c","src/bin/debug_ingestion_sizes.rs":"ce6e810be7b3fc19e826d75b622b82cfab5a1a99397a6d0833c2c4eebff2d364","src/config.rs":"0ca876e845841bb6429862c0904c82265003f53b55aea053fac60aed278586a7","src/db.rs":"566e62ab951c0a372450ffb8842ded85a0147242cf191254d895dc0fe320a58c","src/error.rs":"e2ef3ec0e0b2b8ecbb8f2f1717d4cb753af06913b8395d086b7643098ad100a7","src/fakespot.rs":"f501c9fe5296e7c130a9fcb532b861465717652cb5ef688230bc7a3b94df91b1","src/geoname.rs":"77376dbc7d06532a7797a93b863f150317df7f31d9200d375c8ea489ac8bee6f","src/lib.rs":"a4c0989a01a7c13184049c1f11bc7813cd3cbfb6354fcca1f5a7204e45a0dc9c","src/metrics.rs":"871f0d834efbbc9e26d61f66fa31f0021dcf41444746cd7c082f93ba9628e399","src/pocket.rs":"1316668840ec9b4ea886223921dc9d3b5a1731d1a5206c0b1089f2a6c45c1b7b","src/provider.rs":"cf00114a4293a4c9d956efffb8a6859f3568ea0e4868c6b63b8273daca69b0d5","src/query.rs":"66f229272c9245eb8ee0cab237071627aec599f145f64da8894bcaeb1ed7c6f9","src/rs.rs":"37cce2cb0068421f6b40e13f3bcb1f6212acee2f7117759cd095d381b25580f8","src/schema.rs":"51dfe27c01884dfbd3bb7c868c01505dd69cf119d9bb37ddb653f77ead170956","src/store.rs":"d127c58e26a3e380e815e11e6660feb58df335ab40426aa051902a6fec659b5a","src/suggestion.rs":"e74abdc1eace082ad7ed8762bcee25b1943ec3d04d7e644959c5737b42732660","src/testing/client.rs":"47a32fd84c733001f11e8bfff94dc8c060b6b0780346dca5ddc7a5f5489c1d85","src/testing/data.rs":"5b1935946185f55be2383f2f979e966155825b1b9df7913d93eb50289052639c","src/testing/mod.rs":"34120abb160a913069c3f7df03c4f52815be3461dbbf08fb0fcc4251a517ba71","src/util.rs":"52c6ec405637afa2d1a89f29fbbb7dcc341546b6deb97d326c4490bbf8713cb0","src/weather.rs":"7cc9167dcdfca49d6ad91eba6fba4d5fd49f45052f25a7fe3ad6749d3e6783fb","src/yelp.rs":"0b9dfa698d9c3162d47c0103d1799838d444345f9d7f943eedc6bcc98fd8b57d","uniffi.toml":"8205e4679ac26d53e70af0f85c013fd27cda1119f4322aebf5f2b9403d45a611"},"package":null} \ No newline at end of file diff --git a/third_party/rust/suggest/src/db.rs b/third_party/rust/suggest/src/db.rs index abc3602d048..427473567d9 100644 --- a/third_party/rust/suggest/src/db.rs +++ b/third_party/rust/suggest/src/db.rs @@ -1340,6 +1340,11 @@ impl<'a> SuggestDao<'a> { named_params! { ":record_id": record_id.as_str() }, )?; self.scope.err_if_interrupted()?; + self.conn.execute_cached( + "DELETE FROM yelp_location_signs WHERE record_id = :record_id", + named_params! { ":record_id": record_id.as_str() }, + )?; + self.scope.err_if_interrupted()?; self.conn.execute_cached( "DELETE FROM yelp_custom_details WHERE record_id = :record_id", named_params! { ":record_id": record_id.as_str() }, diff --git a/third_party/rust/suggest/src/rs.rs b/third_party/rust/suggest/src/rs.rs index e833ba68340..c5eacfdabc1 100644 --- a/third_party/rust/suggest/src/rs.rs +++ b/third_party/rust/suggest/src/rs.rs @@ -42,8 +42,6 @@ use serde_json::{Map, Value}; use crate::{error::Error, query::full_keywords_to_fts_content, Result}; -use rusqlite::{types::ToSqlOutput, ToSql}; - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Collection { Amp, @@ -455,21 +453,12 @@ pub(crate) struct DownloadedPocketSuggestion { pub high_confidence_keywords: Vec, pub score: f64, } -/// Yelp location sign data type +/// A location sign for Yelp to ingest from a Yelp Attachment #[derive(Clone, Debug, Deserialize)] -#[serde(untagged)] -pub enum DownloadedYelpLocationSign { - V1 { keyword: String }, - V2(String), -} -impl ToSql for DownloadedYelpLocationSign { - fn to_sql(&self) -> rusqlite::Result> { - let keyword = match self { - DownloadedYelpLocationSign::V1 { keyword } => keyword, - DownloadedYelpLocationSign::V2(keyword) => keyword, - }; - Ok(ToSqlOutput::from(keyword.as_str())) - } +pub(crate) struct DownloadedYelpLocationSign { + pub keyword: String, + #[serde(rename = "needLocation")] + pub need_location: bool, } /// A Yelp suggestion to ingest from a Yelp Attachment #[derive(Clone, Debug, Deserialize)] diff --git a/third_party/rust/suggest/src/schema.rs b/third_party/rust/suggest/src/schema.rs index 5bf8057389b..db00c858169 100644 --- a/third_party/rust/suggest/src/schema.rs +++ b/third_party/rust/suggest/src/schema.rs @@ -23,7 +23,7 @@ use sql_support::{ /// `clear_database()` by adding their names to `conditional_tables`, unless /// they are cleared via a deletion trigger or there's some other good /// reason not to do so. -pub const VERSION: u32 = 36; +pub const VERSION: u32 = 35; /// The current Suggest database schema. pub const SQL: &str = " @@ -171,6 +171,12 @@ CREATE TABLE yelp_modifiers( PRIMARY KEY (type, keyword) ) WITHOUT ROWID; +CREATE TABLE yelp_location_signs( + keyword TEXT PRIMARY KEY, + need_location INTEGER NOT NULL, + record_id TEXT NOT NULL +) WITHOUT ROWID; + CREATE TABLE yelp_custom_details( icon_id TEXT PRIMARY KEY, score REAL NOT NULL, @@ -639,10 +645,6 @@ impl ConnectionInitializer for SuggestConnectionInitializer<'_> { )?; Ok(()) } - 35 => { - tx.execute_batch("DROP TABLE yelp_location_signs;")?; - Ok(()) - } _ => Err(open_database::Error::IncompatibleVersion(version)), } } @@ -660,6 +662,7 @@ pub fn clear_database(db: &Connection) -> rusqlite::Result<()> { DELETE FROM icons; DELETE FROM yelp_subjects; DELETE FROM yelp_modifiers; + DELETE FROM yelp_location_signs; DELETE FROM yelp_custom_details; ", )?; diff --git a/third_party/rust/suggest/src/store.rs b/third_party/rust/suggest/src/store.rs index b9d60a20906..951e882988e 100644 --- a/third_party/rust/suggest/src/store.rs +++ b/third_party/rust/suggest/src/store.rs @@ -1850,21 +1850,9 @@ pub(crate) mod tests { "https://www.yelp.com/search?find_desc=ramen+super+delivery&find_loc=tokyo" ),], ); - assert_eq!( - store.fetch_suggestions(SuggestionQuery::yelp("ramen invalid_delivery")), - vec![ramen_suggestion( - "ramen invalid_delivery", - "https://www.yelp.com/search?find_desc=ramen&find_loc=invalid_delivery" - ) - .has_location_sign(false),], - ); assert_eq!( store.fetch_suggestions(SuggestionQuery::yelp("ramen invalid_delivery in tokyo")), - vec![ramen_suggestion( - "ramen invalid_delivery in tokyo", - "https://www.yelp.com/search?find_desc=ramen&find_loc=invalid_delivery+in+tokyo" - ) - .has_location_sign(false),], + vec![], ); assert_eq!( store.fetch_suggestions(SuggestionQuery::yelp("ramen in tokyo")), @@ -1882,11 +1870,7 @@ pub(crate) mod tests { ); assert_eq!( store.fetch_suggestions(SuggestionQuery::yelp("ramen invalid_in tokyo")), - vec![ramen_suggestion( - "ramen invalid_in tokyo", - "https://www.yelp.com/search?find_desc=ramen&find_loc=invalid_in+tokyo" - ) - .has_location_sign(false),], + vec![], ); assert_eq!( store.fetch_suggestions(SuggestionQuery::yelp("ramen in San Francisco")), @@ -1906,22 +1890,21 @@ pub(crate) mod tests { store.fetch_suggestions(SuggestionQuery::yelp("ramen near by")), vec![ramen_suggestion( "ramen near by", - "https://www.yelp.com/search?find_desc=ramen" - )], + "https://www.yelp.com/search?find_desc=ramen+near+by" + ) + .has_location_sign(false),], ); assert_eq!( store.fetch_suggestions(SuggestionQuery::yelp("ramen near me")), vec![ramen_suggestion( "ramen near me", - "https://www.yelp.com/search?find_desc=ramen" - )], + "https://www.yelp.com/search?find_desc=ramen+near+me" + ) + .has_location_sign(false),], ); assert_eq!( store.fetch_suggestions(SuggestionQuery::yelp("ramen near by tokyo")), - vec![ramen_suggestion( - "ramen near by tokyo", - "https://www.yelp.com/search?find_desc=ramen&find_loc=tokyo" - )], + vec![], ); assert_eq!( store.fetch_suggestions(SuggestionQuery::yelp("ramen")), @@ -2068,38 +2051,6 @@ pub(crate) mod tests { .has_location_sign(false) .subject_exact_match(false)], ); - // Test for prefix match. - assert_eq!( - store.fetch_suggestions(SuggestionQuery::yelp("ramen D")), - vec![ramen_suggestion( - "ramen Delivery", - "https://www.yelp.com/search?find_desc=ramen+Delivery" - ) - .has_location_sign(false)], - ); - assert_eq!( - store.fetch_suggestions(SuggestionQuery::yelp("ramen I")), - vec![ramen_suggestion( - "ramen In", - "https://www.yelp.com/search?find_desc=ramen" - )], - ); - assert_eq!( - store.fetch_suggestions(SuggestionQuery::yelp("ramen Y")), - vec![ - ramen_suggestion("ramen", "https://www.yelp.com/search?find_desc=ramen") - .has_location_sign(false) - ], - ); - // Prefix match is available only for last words. - assert_eq!( - store.fetch_suggestions(SuggestionQuery::yelp("ramen I Tokyo")), - vec![ramen_suggestion( - "ramen I Tokyo", - "https://www.yelp.com/search?find_desc=ramen&find_loc=I+Tokyo" - ) - .has_location_sign(false)], - ); Ok(()) } diff --git a/third_party/rust/suggest/src/testing/data.rs b/third_party/rust/suggest/src/testing/data.rs index f1d699cc99f..d685f3c7a92 100644 --- a/third_party/rust/suggest/src/testing/data.rs +++ b/third_party/rust/suggest/src/testing/data.rs @@ -297,12 +297,10 @@ pub fn ramen_yelp() -> JsonValue { "preModifiers": ["best", "super best", "same_modifier"], "postModifiers": ["delivery", "super delivery", "same_modifier"], "locationSigns": [ - // V1 format also can be used as location sign. { "keyword": "in", "needLocation": true }, + { "keyword": "near", "needLocation": true }, { "keyword": "near by", "needLocation": false }, - // V2 format. - "near", - "near me", + { "keyword": "near me", "needLocation": false }, ], "yelpModifiers": ["yelp", "yelp keyword"], "icon": "yelp-favicon", diff --git a/third_party/rust/suggest/src/yelp.rs b/third_party/rust/suggest/src/yelp.rs index 81ec6d5dcf6..442985ed131 100644 --- a/third_party/rust/suggest/src/yelp.rs +++ b/third_party/rust/suggest/src/yelp.rs @@ -22,7 +22,6 @@ enum Modifier { Pre = 0, Post = 1, Yelp = 2, - LocationSign = 3, } impl ToSql for Modifier { @@ -31,12 +30,6 @@ impl ToSql for Modifier { } } -#[derive(Eq, PartialEq)] -enum FindFrom { - First, - Last, -} - /// This module assumes like following query. /// "Yelp-modifier? Pre-modifier? Subject Post-modifier? (Location-modifier | Location-sign Location?)? Yelp-modifier?" /// For example, the query below is valid. @@ -111,14 +104,14 @@ impl SuggestDao<'_> { )?; } - for keyword in &suggestion.location_signs { + for sign in &suggestion.location_signs { self.scope.err_if_interrupted()?; self.conn.execute_cached( - "INSERT INTO yelp_modifiers(record_id, type, keyword) VALUES(:record_id, :type, :keyword)", + "INSERT INTO yelp_location_signs(record_id, keyword, need_location) VALUES(:record_id, :keyword, :need_location)", named_params! { ":record_id": record_id.as_str(), - ":type": Modifier::LocationSign, - ":keyword": keyword, + ":keyword": sign.keyword, + ":need_location": sign.need_location, }, )?; } @@ -149,57 +142,62 @@ impl SuggestDao<'_> { return Ok(vec![]); } - let query_vec: Vec<_> = query.keyword.split_whitespace().collect(); - let mut query_words: &[&str] = &query_vec; - - let pre_yelp_modifier_tuple = - self.find_modifier(query_words, Modifier::Yelp, FindFrom::First)?; - if let Some((_, n)) = pre_yelp_modifier_tuple { - query_words = &query_words[n..]; + let query_string = &query.keyword.trim(); + if !query_string.contains(' ') { + let Some((subject, subject_exact_match)) = self.find_subject(query_string)? else { + return Ok(vec![]); + }; + let (icon, icon_mimetype, score) = self.fetch_custom_details()?; + let builder = SuggestionBuilder { + subject: &subject, + subject_exact_match, + pre_modifier: None, + post_modifier: None, + location_sign: None, + location: None, + need_location: false, + icon, + icon_mimetype, + score, + }; + return Ok(vec![builder.into()]); } - let pre_modifier_tuple = self.find_modifier(query_words, Modifier::Pre, FindFrom::First)?; - if let Some((_, n)) = pre_modifier_tuple { - query_words = &query_words[n..]; - } + // Find the yelp keyword modifier and remove them from the query. + let (query_without_yelp_modifiers, _, _) = + self.find_modifiers(query_string, Modifier::Yelp, Modifier::Yelp)?; - let Some(subject_tuple) = self.find_subject(query_words)? else { + // Find the location sign and the location. + let (query_without_location, location_sign, location, need_location) = + self.find_location(&query_without_yelp_modifiers)?; + + if let (Some(_), false) = (&location, need_location) { + // The location sign does not need the specific location, but user is setting something. return Ok(vec![]); - }; - query_words = &query_words[subject_tuple.2..]; - - let post_modifier_tuple = - self.find_modifier(query_words, Modifier::Post, FindFrom::First)?; - if let Some((_, n)) = post_modifier_tuple { - query_words = &query_words[n..]; } - let location_sign_tuple = - self.find_modifier(query_words, Modifier::LocationSign, FindFrom::First)?; - if let Some((_, n)) = location_sign_tuple { - query_words = &query_words[n..]; + if query_without_location.is_empty() { + // No remained query. + return Ok(vec![]); } - let post_yelp_modifier_tuple = - self.find_modifier(query_words, Modifier::Yelp, FindFrom::Last)?; - if let Some((_, n)) = post_yelp_modifier_tuple { - query_words = &query_words[0..query_words.len() - n]; - } + // Find the modifiers. + let (subject_candidate, pre_modifier, post_modifier) = + self.find_modifiers(&query_without_location, Modifier::Pre, Modifier::Post)?; - let location = if query_words.is_empty() { - None - } else { - Some(query_words.join(" ")) + let Some((subject, subject_exact_match)) = self.find_subject(&subject_candidate)? else { + return Ok(vec![]); }; let (icon, icon_mimetype, score) = self.fetch_custom_details()?; let builder = SuggestionBuilder { - subject: &subject_tuple.0, - subject_exact_match: subject_tuple.1, - pre_modifier: pre_modifier_tuple.map(|(words, _)| words.to_string()), - post_modifier: post_modifier_tuple.map(|(words, _)| words.to_string()), - location_sign: location_sign_tuple.map(|(words, _)| words.to_string()), + subject: &subject, + subject_exact_match, + pre_modifier, + post_modifier, + location_sign, location, + need_location, icon, icon_mimetype, score, @@ -207,122 +205,6 @@ impl SuggestDao<'_> { Ok(vec![builder.into()]) } - /// Find the modifier for given query and modifier type. - /// Find from last word, if set FindFrom::Last to find_from. - /// It returns Option as follows: - /// ( - /// String: The keyword in DB (but the case is inherited by query). - /// usize: Number of words in query_words that match the keyword. - /// Maximum number is MAX_MODIFIER_WORDS_NUMBER. - /// ) - fn find_modifier( - &self, - query_words: &[&str], - modifier_type: Modifier, - find_from: FindFrom, - ) -> Result> { - if query_words.is_empty() { - return Ok(None); - } - - for n in (1..=std::cmp::min(MAX_MODIFIER_WORDS_NUMBER, query_words.len())).rev() { - let candidate_chunk = match find_from { - FindFrom::First => query_words.chunks(n).next(), - FindFrom::Last => query_words.rchunks(n).next(), - }; - let candidate = candidate_chunk - .map(|chunk| chunk.join(" ")) - .unwrap_or_default(); - if let Some(keyword_lowercase) = self.conn.try_query_one::( - if n == query_words.len() { - " - SELECT keyword FROM yelp_modifiers - WHERE type = :type AND keyword BETWEEN :word AND :word || x'FFFF' - LIMIT 1 - " - } else { - " - SELECT keyword FROM yelp_modifiers - WHERE type = :type AND keyword = :word - LIMIT 1 - " - }, - named_params! { - ":type": modifier_type, - ":word": candidate.to_lowercase(), - }, - true, - )? { - // Preserve the query as the user typed it including its case. - let keyword = format!("{}{}", candidate, &keyword_lowercase[candidate.len()..]); - return Ok(Some((keyword, n))); - } - } - - Ok(None) - } - - /// Find the subject for given query. - /// It returns Option as follows: - /// ( - /// String: The keyword in DB (but the case is inherited by query). - /// bool: Whether or not the keyword is exact match. - /// usize: Number of words in query_words that match the keyword. - /// ) - fn find_subject(&self, query_words: &[&str]) -> Result> { - if query_words.is_empty() { - return Ok(None); - } - - let query_string = query_words.join(" "); - - // This checks if keyword is a substring of the query. - if let Some(keyword_lowercase) = self.conn.try_query_one::( - "SELECT keyword - FROM yelp_subjects - WHERE :query BETWEEN keyword AND keyword || x'FFFF' - ORDER BY LENGTH(keyword) ASC, keyword ASC - LIMIT 1", - named_params! { - ":query": query_string.to_lowercase(), - }, - true, - )? { - // Preserve the query as the user typed it including its case. - let keyword = &query_string[0..keyword_lowercase.len()]; - let count = keyword.split_whitespace().count(); - return Ok(Some((keyword.to_string(), true, count))); - }; - - if query_string.len() < SUBJECT_PREFIX_MATCH_THRESHOLD { - return Ok(None); - } - - // Oppositely, this checks if the query is a substring of keyword. - if let Some(keyword_lowercase) = self.conn.try_query_one::( - "SELECT keyword - FROM yelp_subjects - WHERE keyword BETWEEN :query AND :query || x'FFFF' - ORDER BY LENGTH(keyword) ASC, keyword ASC - LIMIT 1", - named_params! { - ":query": query_string.to_lowercase(), - }, - true, - )? { - // Preserve the query as the user typed it including its case. - let keyword = format!( - "{}{}", - query_string, - &keyword_lowercase[query_string.len()..] - ); - let count = keyword.split_whitespace().count(); - return Ok(Some((keyword, false, count))); - }; - - Ok(None) - } - /// Fetch the custom details for Yelp suggestions. /// It returns the location tuple as follows: /// ( @@ -362,6 +244,202 @@ impl SuggestDao<'_> { Ok(result) } + + /// Find the location information from the given query string. + /// It returns the location tuple as follows: + /// ( + /// String: Query string that is removed found location information. + /// Option: Location sign found in yelp_location_signs table. If not found, returns None. + /// Option: Specific location name after location sign. If not found, returns None. + /// bool: Reflects need_location field in the table. + /// ) + fn find_location(&self, query: &str) -> Result<(String, Option, Option, bool)> { + let query_with_spaces = format!(" {} ", query); + let mut results: Vec<(usize, usize, i8)> = self.conn.query_rows_and_then_cached( + " + SELECT + INSTR(:query, ' ' || keyword || ' ') AS sign_index, + LENGTH(keyword) AS sign_length, + need_location + FROM yelp_location_signs + WHERE + sign_index > 0 + ORDER BY + sign_length DESC + LIMIT 1 + ", + named_params! { + ":query": &query_with_spaces.to_lowercase(), + }, + |row| -> Result<_> { + Ok(( + row.get::<_, usize>("sign_index")?, + row.get::<_, usize>("sign_length")?, + row.get::<_, i8>("need_location")?, + )) + }, + )?; + + let (sign_index, sign_length, need_location) = if let Some(res) = results.pop() { + res + } else { + return Ok((query.trim().to_string(), None, None, false)); + }; + + let pre_location = query_with_spaces + .get(..sign_index) + .map(str::trim) + .map(str::to_string) + .unwrap_or_default(); + let location_sign = query_with_spaces + .get(sign_index..sign_index + sign_length) + .map(str::trim) + .filter(|s| !s.is_empty()) + .map(str::to_string); + let location = query_with_spaces + .get(sign_index + sign_length..) + .map(str::trim) + .filter(|s| !s.is_empty()) + .map(str::to_string); + + Ok((pre_location, location_sign, location, need_location == 1)) + } + + /// Find the pre/post modifier from the given query string. + /// It returns the modifiers tuple as follows: + /// ( + /// String: Query string that is removed found the modifiers. + /// Option: Pre-modifier found in the yelp_modifiers table. If not found, returns None. + /// Option: Post-modifier found in the yelp_modifiers table. If not found, returns None. + /// ) + fn find_modifiers( + &self, + query: &str, + pre_modifier_type: Modifier, + post_modifier_type: Modifier, + ) -> Result<(String, Option, Option)> { + if !query.contains(' ') { + return Ok((query.to_string(), None, None)); + } + + let words: Vec<_> = query.split_whitespace().collect(); + + let mut pre_modifier = None; + for n in (1..=MAX_MODIFIER_WORDS_NUMBER).rev() { + let mut candidate_chunks = words.chunks(n); + let candidate = candidate_chunks.next().unwrap_or(&[""]).join(" "); + if self.is_modifier(&candidate, pre_modifier_type)? { + pre_modifier = Some(candidate); + break; + } + } + + let mut post_modifier = None; + for n in (1..=MAX_MODIFIER_WORDS_NUMBER).rev() { + let mut candidate_chunks = words.rchunks(n); + let candidate = candidate_chunks.next().unwrap_or(&[""]).join(" "); + if self.is_modifier(&candidate, post_modifier_type)? { + post_modifier = Some(candidate); + break; + } + } + + let mut without_modifiers = query; + if let Some(ref modifier) = pre_modifier { + without_modifiers = &without_modifiers[modifier.len()..]; + } + if let Some(ref modifier) = post_modifier { + without_modifiers = &without_modifiers[..without_modifiers.len() - modifier.len()]; + } + + Ok(( + without_modifiers.trim().to_string(), + pre_modifier, + post_modifier, + )) + } + + /// Find the subject from the given string. + /// It returns the Option. If it is not none, it contains the tuple as follows: + /// ( + /// String: Subject. + /// bool: Whether the subject matched exactly with the parameter. + /// ) + fn find_subject(&self, candidate: &str) -> Result> { + if candidate.is_empty() { + return Ok(None); + } + + // If the length of subject candidate is less than + // SUBJECT_PREFIX_MATCH_THRESHOLD, should exact match. + if candidate.len() < SUBJECT_PREFIX_MATCH_THRESHOLD { + return Ok(if self.is_subject(candidate)? { + Some((candidate.to_string(), true)) + } else { + None + }); + } + + // Otherwise, apply prefix-match. + Ok( + match self.conn.query_row_and_then_cachable( + "SELECT keyword + FROM yelp_subjects + WHERE keyword BETWEEN :candidate AND :candidate || x'FFFF' + ORDER BY LENGTH(keyword) ASC, keyword ASC + LIMIT 1", + named_params! { + ":candidate": candidate.to_lowercase(), + }, + |row| row.get::<_, String>(0), + true, + ) { + Ok(keyword) => { + debug_assert!(candidate.len() <= keyword.len()); + Some(( + format!("{}{}", candidate, &keyword[candidate.len()..]), + candidate.len() == keyword.len(), + )) + } + Err(_) => None, + }, + ) + } + + fn is_modifier(&self, word: &str, modifier_type: Modifier) -> Result { + let result = self.conn.query_row_and_then_cachable( + " + SELECT EXISTS ( + SELECT 1 FROM yelp_modifiers WHERE type = :type AND keyword = :word LIMIT 1 + ) + ", + named_params! { + ":type": modifier_type, + ":word": word.to_lowercase(), + }, + |row| row.get::<_, bool>(0), + true, + )?; + + Ok(result) + } + + fn is_subject(&self, word: &str) -> Result { + let result = self.conn.query_row_and_then_cachable( + " + SELECT EXISTS ( + SELECT 1 FROM yelp_subjects WHERE keyword = :word LIMIT 1 + ) + ", + named_params! { + ":word": word.to_lowercase(), + }, + |row| row.get::<_, bool>(0), + true, + )?; + + Ok(result) + } } struct SuggestionBuilder<'a> { @@ -371,6 +449,7 @@ struct SuggestionBuilder<'a> { post_modifier: Option, location_sign: Option, location: Option, + need_location: bool, icon: Option>, icon_mimetype: Option, score: f64, @@ -378,10 +457,17 @@ struct SuggestionBuilder<'a> { impl<'a> From> for Suggestion { fn from(builder: SuggestionBuilder<'a>) -> Suggestion { + // This location sign such the 'near by' needs to add as a description parameter. + let location_modifier = if !builder.need_location { + builder.location_sign.as_deref() + } else { + None + }; let description = [ builder.pre_modifier.as_deref(), Some(builder.subject), builder.post_modifier.as_deref(), + location_modifier, ] .iter() .flatten() @@ -393,7 +479,7 @@ impl<'a> From> for Suggestion { let mut url = String::from("https://www.yelp.com/search?"); let mut parameters = form_urlencoded::Serializer::new(String::new()); parameters.append_pair("find_desc", &description); - if let Some(location) = &builder.location { + if let (Some(location), true) = (&builder.location, builder.need_location) { parameters.append_pair("find_loc", location); } url.push_str(¶meters.finish()); @@ -417,7 +503,7 @@ impl<'a> From> for Suggestion { icon: builder.icon, icon_mimetype: builder.icon_mimetype, score: builder.score, - has_location_sign: builder.location_sign.is_some(), + has_location_sign: location_modifier.is_none() && builder.location_sign.is_some(), subject_exact_match: builder.subject_exact_match, location_param: "find_loc".to_string(), } diff --git a/third_party/rust/windows-link/.cargo-checksum.json b/third_party/rust/windows-link/.cargo-checksum.json new file mode 100644 index 00000000000..25de8468b06 --- /dev/null +++ b/third_party/rust/windows-link/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.lock":"45d4fcedd34058d064b8c2cff7d288d9463d548eb9d10357f6124eab8b5aea06","Cargo.toml":"bff03cd2cb573427f5db58e0553ef8291fba08c9dda06b4a3bff8d0488fe4526","license-apache-2.0":"c16f8dcf1a368b83be78d826ea23de4079fe1b4469a0ab9ee20563f37ff3d44b","license-mit":"c2cfccb812fe482101a8f04597dfc5a9991a6b2748266c47ac91b6a5aae15383","readme.md":"1d119f8a35c7c95c4cc80f0609c3fb4a897d4bd244e98bdf5054f9fd96942418","src/lib.rs":"af49fec4ea3b0f96b7fd6002b4b4365b2f4c3608ae5adde502f9cd335064906f"},"package":"76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"} \ No newline at end of file diff --git a/third_party/rust/windows-link/Cargo.lock b/third_party/rust/windows-link/Cargo.lock new file mode 100644 index 00000000000..b6d15839a78 --- /dev/null +++ b/third_party/rust/windows-link/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "windows-link" +version = "0.1.1" diff --git a/third_party/rust/windows-link/Cargo.toml b/third_party/rust/windows-link/Cargo.toml new file mode 100644 index 00000000000..c9feccbb1e5 --- /dev/null +++ b/third_party/rust/windows-link/Cargo.toml @@ -0,0 +1,44 @@ +# 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 = "windows-link" +version = "0.1.1" +authors = ["Microsoft"] +build = false +autolib = false +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = "Linking for Windows" +readme = "readme.md" +license = "MIT OR Apache-2.0" +repository = "https://github.com/microsoft/windows-rs" + +[lib] +name = "windows_link" +path = "src/lib.rs" + +[lints.rust] +missing_docs = "warn" +unsafe_op_in_unsafe_fn = "warn" + +[lints.rust.rust_2018_idioms] +level = "warn" +priority = -1 + +[lints.rust.unexpected_cfgs] +level = "warn" +priority = 0 +check-cfg = ["cfg(windows_raw_dylib, windows_debugger_visualizer, windows_slim_errors)"] diff --git a/third_party/rust/windows-link/license-apache-2.0 b/third_party/rust/windows-link/license-apache-2.0 new file mode 100644 index 00000000000..b5ed4ecec27 --- /dev/null +++ b/third_party/rust/windows-link/license-apache-2.0 @@ -0,0 +1,201 @@ + 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 (c) Microsoft Corporation. + + 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/third_party/rust/windows-link/license-mit b/third_party/rust/windows-link/license-mit new file mode 100644 index 00000000000..9e841e7a26e --- /dev/null +++ b/third_party/rust/windows-link/license-mit @@ -0,0 +1,21 @@ + MIT License + + Copyright (c) Microsoft Corporation. + + 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/third_party/rust/windows-link/readme.md b/third_party/rust/windows-link/readme.md new file mode 100644 index 00000000000..d6eb15f073f --- /dev/null +++ b/third_party/rust/windows-link/readme.md @@ -0,0 +1,26 @@ +## Linking for Windows + +The [windows-link](https://crates.io/crates/windows-link) crate provides the `link` macro that simplifies linking. The `link` macro is much the same as the one provided by [windows-targets](https://crates.io/crates/windows-targets) but uses `raw-dylib` and thus does not require import lib files. + +* [Getting started](https://kennykerr.ca/rust-getting-started/) +* [Samples](https://github.com/microsoft/windows-rs/tree/master/crates/samples) +* [Releases](https://github.com/microsoft/windows-rs/releases) + +Start by adding the following to your Cargo.toml file: + +```toml +[dependencies.windows-link] +version = "0.1" +``` + +Use the `link` macro to define the external functions you wish to call: + +```rust +windows_link::link!("kernel32.dll" "system" fn SetLastError(code: u32)); +windows_link::link!("kernel32.dll" "system" fn GetLastError() -> u32); + +unsafe { + SetLastError(1234); + assert_eq!(GetLastError(), 1234); +} +``` diff --git a/third_party/rust/windows-link/src/lib.rs b/third_party/rust/windows-link/src/lib.rs new file mode 100644 index 00000000000..feee0da270c --- /dev/null +++ b/third_party/rust/windows-link/src/lib.rs @@ -0,0 +1,39 @@ +#![doc = include_str!("../readme.md")] +#![no_std] + +/// Defines an external function to import. +#[cfg(all(windows, target_arch = "x86"))] +#[macro_export] +macro_rules! link { + ($library:literal $abi:literal $($link_name:literal)? fn $($function:tt)*) => ( + #[link(name = $library, kind = "raw-dylib", modifiers = "+verbatim", import_name_type = "undecorated")] + extern $abi { + $(#[link_name=$link_name])? + pub fn $($function)*; + } + ) +} + +/// Defines an external function to import. +#[cfg(all(windows, not(target_arch = "x86")))] +#[macro_export] +macro_rules! link { + ($library:literal $abi:literal $($link_name:literal)? fn $($function:tt)*) => ( + #[link(name = $library, kind = "raw-dylib", modifiers = "+verbatim")] + extern "C" { + $(#[link_name=$link_name])? + pub fn $($function)*; + } + ) +} + +/// Defines an external function to import. +#[cfg(not(windows))] +#[macro_export] +macro_rules! link { + ($library:literal $abi:literal $($link_name:literal)? fn $($function:tt)*) => ( + extern $abi { + pub fn $($function)*; + } + ) +} diff --git a/toolkit/components/formautofill/FormAutofillChild.sys.mjs b/toolkit/components/formautofill/FormAutofillChild.sys.mjs index a392920ba27..062c0b0dae4 100644 --- a/toolkit/components/formautofill/FormAutofillChild.sys.mjs +++ b/toolkit/components/formautofill/FormAutofillChild.sys.mjs @@ -105,7 +105,7 @@ export class FormAutofillChild extends JSWindowActorChild { // by it, so don't touch the fieldDetails in this case. return; } - handler.updateFormIfNeeded(fieldDetails[0].element); + handler.updateFormByElement(fieldDetails[0].element); this._fieldDetailsManager.addFormHandlerByElementEntries(handler); } @@ -230,12 +230,22 @@ export class FormAutofillChild extends JSWindowActorChild { const handler = this._fieldDetailsManager.getOrCreateFormHandler(element); - // If the child process is still waiting for the parent to send to - // `onFieldsDetectedComplete` or `onFieldsUpdatedComplete` message, bail out. if ( this.#handlerWaitingForDetectedComplete.has(handler) || - this.#handlerWaitingForFillOnFormChangeComplete.has(handler) + this.#handlerWaitingForFillOnFormChangeComplete.has(handler) || + this.#handlerWaitingForFormSubmissionComplete.has(handler) ) { + // Bail out if the child process is still waiting for the parent to send a + // `onFieldsDetectedComplete` or `onFieldsUpdatedComplete` message, + // or a form submission is currently still getting processed. + return; + } + + if (handler.fillOnFormChangeData.isWithinDynamicFormChangeThreshold) { + // Received the focus event immediately after an autofill action, which was not + // initiated by a user but by the site due to the form change. Bail out here, + // because we will receive the form-changed-event anyway and should not process the + // field detection here, since this would block the second autofill process. return; } @@ -547,7 +557,8 @@ export class FormAutofillChild extends JSWindowActorChild { if ( this.#handlerWaitingForFormSubmissionComplete.has(handler) || !form.isConnected || - !form.checkVisibility() + (HTMLFormElement.isInstance(form) && + !Array.from(form.elements).find(e => e.checkVisibility())) ) { // Bail out if a form submission is happening, or the whole form is disconnected or invisible, // because then we're suspecting a form submission of reason "form-removal-after-fetch" next. @@ -572,13 +583,39 @@ export class FormAutofillChild extends JSWindowActorChild { return; } - // createFromField needs an input, select or iframe element - const anchorElement = handler.form.elements.find( - element => - lazy.FormAutofillUtils.isCreditCardOrAddressFieldType(element) || - HTMLIFrameElement.isInstance(element) - ); - const currentForm = lazy.AutofillFormFactory.createFromField(anchorElement); + let currentForm; + if (HTMLFormElement.isInstance(form)) { + currentForm = lazy.AutofillFormFactory.createFromForm(form); + } + + if (!currentForm) { + const findAnchorElement = elements => { + return elements.find( + element => + element.isConnected && + // createFromField needs an input, select or iframe element + (lazy.FormAutofillUtils.isCreditCardOrAddressFieldType(element) || + HTMLIFrameElement.isInstance(element)) + ); + }; + + let anchorElement = findAnchorElement(handler.form.elements); + + if (!anchorElement) { + // Unable to find an anchor element under the previously tracked fields. + // So going over the elements that were added/became visible on form change + const addedElements = changes[lazy.FORM_CHANGE_REASON.ADDED_NODES]; + const visibleElements = + changes[lazy.FORM_CHANGE_REASON.ELEMENT_VISIBLE]; + anchorElement = findAnchorElement([addedElements, visibleElements]); + } + if (anchorElement) { + currentForm = lazy.AutofillFormFactory.createFromField(anchorElement); + } else { + currentForm = lazy.AutofillFormFactory.createFromDocumentRoot(form); + } + } + const currentFields = lazy.FormAutofillHandler.collectFormFieldDetails(currentForm); @@ -614,9 +651,21 @@ export class FormAutofillChild extends JSWindowActorChild { handler.fillOnFormChangeData.isWithinDynamicFormChangeThreshold && !this.#handlerWaitingForFillOnFormChangeComplete.has(handler) ) { + const previouslyFocusedId = + handler.fillOnFormChangeData.previouslyFocusedId; + const prevFocusedElement = + lazy.FormAutofillUtils.getElementByIdentifier(previouslyFocusedId); + + // Determine the element that will be re-focused after the autofill action (ideally that + // should be the previously focused one). It is also used by the parent to retrieve the section that should be filled. + // If the previous one was removed in the form change, then just take the first element of the newly detected fields. + const elementToFocus = prevFocusedElement.isConnected + ? previouslyFocusedId + : mergedFields[0].elementId; + this.#handlerWaitingForFillOnFormChangeComplete.add(handler); this.sendAsyncMessage("FormAutofill:FieldsUpdatedDuringAutofill", { - elementId: handler.fillOnFormChangeData.previouslyFocusedId, + elementId: elementToFocus, profile: handler.fillOnFormChangeData.previouslyUsedProfile, }); } @@ -650,8 +699,12 @@ export class FormAutofillChild extends JSWindowActorChild { switch (message.name) { case "FormAutofill:FillFields": { const { focusedId, ids, profile } = message.data; + + // Retrieving the handler before filling, + // since the filling could trigger a form change and fields removal + const handler = this.#getHandlerByElementId(focusedId); const result = this.fillFields(focusedId, ids, profile); - this.prepareFillingFieldsOnFormChange(focusedId, ids, profile); + this.prepareFillingFieldsOnFormChange(handler, focusedId, ids, profile); // Return the autofilled result to the parent. The result // is used by both tests and telemetry. @@ -852,13 +905,11 @@ export class FormAutofillChild extends JSWindowActorChild { * The timeout gets cancelled early and the data cleared if a "click" or "keydown" event * is dispatched on the form. */ - prepareFillingFieldsOnFormChange(focusedId, elementIds, profile) { + prepareFillingFieldsOnFormChange(handler, focusedId, elementIds, profile) { if (!lazy.FormAutofill.fillOnDynamicFormChanges) { return; } - const handler = this.#getHandlerByElementId(elementIds[0]); - // TODO bug 1953231: // FormAutofillParent should keep of which profile is used for which section, e.g. by introducing profile // ids. It's not ideal that we are cachine the whole used profile data in the child and then send it back to diff --git a/toolkit/components/formautofill/shared/AutofillFormFactory.sys.mjs b/toolkit/components/formautofill/shared/AutofillFormFactory.sys.mjs index 68db1436401..450e8e96fad 100644 --- a/toolkit/components/formautofill/shared/AutofillFormFactory.sys.mjs +++ b/toolkit/components/formautofill/shared/AutofillFormFactory.sys.mjs @@ -39,4 +39,8 @@ export const AutofillFormFactory = { } return lazy.FormLikeFactory.createFromField(aField, { ignoreForm }); }, + + createFromDocumentRoot(aDocRoot) { + return lazy.FormLikeFactory.createFromDocumentRoot(aDocRoot); + }, }; diff --git a/toolkit/components/formautofill/shared/FormAutofillHandler.sys.mjs b/toolkit/components/formautofill/shared/FormAutofillHandler.sys.mjs index 4b03f376538..01d29bef4b8 100644 --- a/toolkit/components/formautofill/shared/FormAutofillHandler.sys.mjs +++ b/toolkit/components/formautofill/shared/FormAutofillHandler.sys.mjs @@ -268,6 +268,11 @@ export class FormAutofillHandler { return false; } + updateFormByElement(element) { + const formLike = lazy.AutofillFormFactory.createFromField(element); + this._updateForm(formLike); + } + /** * Update the form with a new FormLike, and the related fields should be * updated or clear to ensure the data consistency. @@ -344,7 +349,7 @@ export class FormAutofillHandler { } /** - * Resetting the state element's fieldDetail after it was removed from the form + * Resetting the filled state after an element was removed from the form * Todo: We'll need to update this.filledResult in FormAutofillParent (Bug 1948077). * * @param {HTMLElement} element that was removed @@ -353,8 +358,7 @@ export class FormAutofillHandler { if (this.getFilledStateByElement(element) != FIELD_STATES.AUTO_FILLED) { return; } - const fieldDetail = this.getFieldDetailByElement(element); - this.#filledStateByElement.delete(fieldDetail); + this.#filledStateByElement.delete(element); } /** diff --git a/toolkit/components/formautofill/shared/FormAutofillUtils.sys.mjs b/toolkit/components/formautofill/shared/FormAutofillUtils.sys.mjs index 2951e720fac..36d53a1eba5 100644 --- a/toolkit/components/formautofill/shared/FormAutofillUtils.sys.mjs +++ b/toolkit/components/formautofill/shared/FormAutofillUtils.sys.mjs @@ -456,7 +456,9 @@ FormAutofillUtils = { * @returns {boolean} true if the element can be autofilled */ isFieldAutofillable(element) { - return element && !element.readOnly && !element.disabled; + return ( + element && !element.readOnly && !element.disabled && element.isConnected + ); }, /** diff --git a/toolkit/components/glean/api/src/private/datetime.rs b/toolkit/components/glean/api/src/private/datetime.rs index 96977741d71..dc2ea0fde93 100644 --- a/toolkit/components/glean/api/src/private/datetime.rs +++ b/toolkit/components/glean/api/src/private/datetime.rs @@ -134,6 +134,7 @@ impl DatetimeMetric { /// Hemisphere. Negative seconds mean Western Hemisphere. #[cfg_attr(not(feature = "with_gecko"), allow(dead_code))] #[allow(clippy::too_many_arguments)] + #[allow(deprecated)] // use of deprecated chrono functions. pub(crate) fn set_with_details( &self, year: i32, @@ -325,6 +326,7 @@ mod test { use crate::{common_test::*, ipc, metrics}; #[test] + #[allow(deprecated)] // use of deprecated chrono functions. fn sets_datetime_value() { let _lock = lock_test(); @@ -356,6 +358,7 @@ mod test { } #[test] + #[allow(deprecated)] // use of deprecated chrono functions. fn datetime_ipc() { // DatetimeMetric doesn't support IPC. let _lock = lock_test(); diff --git a/toolkit/components/glean/api/src/private/mod.rs b/toolkit/components/glean/api/src/private/mod.rs index 7c768c80bf2..4ed19cce353 100644 --- a/toolkit/components/glean/api/src/private/mod.rs +++ b/toolkit/components/glean/api/src/private/mod.rs @@ -147,6 +147,7 @@ pub(crate) mod profiler_utils { /// /// This converts from the `Local` timezone into its fixed-offset equivalent. /// If a timezone outside of [-24h, +24h] is detected it corrects the timezone offset to UTC (+0). + #[allow(deprecated)] // use of deprecated chrono functions. pub(crate) fn local_now_with_offset() -> chrono::DateTime { use chrono::{DateTime, Local}; #[cfg(target_os = "windows")] @@ -195,6 +196,7 @@ pub(crate) mod profiler_utils { /// the glean::Datetime offset is not a valid timezone We would prefer to /// use .into or similar, but we need to wait until this is implemented in /// the Glean SDK. See Bug 1925313 for more details. + #[allow(deprecated)] // use of deprecated chrono functions. pub(crate) fn glean_to_chrono_datetime( gdt: &glean::Datetime, ) -> Option>> { diff --git a/toolkit/components/ml/tests/browser/data/suggest/yelp_val_keywords_data.json b/toolkit/components/ml/tests/browser/data/suggest/yelp_val_keywords_data.json index 5062a7340be..07ef9332318 100644 --- a/toolkit/components/ml/tests/browser/data/suggest/yelp_val_keywords_data.json +++ b/toolkit/components/ml/tests/browser/data/suggest/yelp_val_keywords_data.json @@ -2890,7 +2890,14 @@ "list of" ], "postModifiers": ["delivery"], - "locationSigns": ["near me", "in", "nearby", "near", "near by", "in area"], + "locationSigns": [ + { "keyword": "near me", "needLocation": false }, + { "keyword": "in", "needLocation": true }, + { "keyword": "nearby", "needLocation": false }, + { "keyword": "near", "needLocation": true }, + { "keyword": "near by", "needLocation": false }, + { "keyword": "in area", "needLocation": false } + ], "yelpModifiers": [ "yekp", "yel", diff --git a/toolkit/components/nimbus/ExperimentAPI.sys.mjs b/toolkit/components/nimbus/ExperimentAPI.sys.mjs index dd22640b41c..8a73f090aa2 100644 --- a/toolkit/components/nimbus/ExperimentAPI.sys.mjs +++ b/toolkit/components/nimbus/ExperimentAPI.sys.mjs @@ -8,6 +8,7 @@ import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs"; const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { + _ExperimentManager: "resource://nimbus/lib/ExperimentManager.sys.mjs", CleanupManager: "resource://normandy/lib/CleanupManager.sys.mjs", ExperimentManager: "resource://nimbus/lib/ExperimentManager.sys.mjs", FeatureManifest: "resource://nimbus/FeatureManifest.sys.mjs", @@ -52,25 +53,6 @@ function parseJSON(value) { return null; } -function featuresCompat(branch) { - if (!branch) { - return []; - } - let { features } = branch; - // In <=v1.5.0 of the Nimbus API, experiments had single feature - if (!features) { - features = [branch.feature]; - } - - return features; -} - -function getBranchFeature(enrollment, targetFeatureId) { - return featuresCompat(enrollment.branch).find( - ({ featureId }) => featureId === targetFeatureId - ); -} - const experimentBranchAccessor = { get: (target, prop) => { // Offer an API where we can access `branch.feature.*`. @@ -703,7 +685,10 @@ export class _ExperimentFeature { return undefined; } - const allValues = getBranchFeature(enrollment, this.featureId)?.value; + const allValues = lazy._ExperimentManager.getFeatureConfigFromBranch( + enrollment.branch, + this.featureId + )?.value; const value = typeof variable === "undefined" ? allValues : allValues?.[variable]; diff --git a/toolkit/components/nimbus/lib/ExperimentManager.sys.mjs b/toolkit/components/nimbus/lib/ExperimentManager.sys.mjs index 330a64e389c..ce8d0fb1a0c 100644 --- a/toolkit/components/nimbus/lib/ExperimentManager.sys.mjs +++ b/toolkit/components/nimbus/lib/ExperimentManager.sys.mjs @@ -36,25 +36,6 @@ const STUDIES_OPT_OUT_PREF = "app.shield.optoutstudies.enabled"; const STUDIES_ENABLED_CHANGED = "nimbus:studies-enabled-changed"; -function featuresCompat(branch) { - if (!branch || (!branch.feature && !branch.features)) { - return []; - } - let { features } = branch; - // In <=v1.5.0 of the Nimbus API, experiments had single feature - if (!features) { - features = [branch.feature]; - } - - return features; -} - -function getFeatureFromBranch(branch, featureId) { - return featuresCompat(branch).find( - featureConfig => featureConfig.featureId === featureId - ); -} - export const UnenrollmentCause = { fromCheckRecipeResult(result) { const { UnenrollReason } = lazy.NimbusTelemetry; @@ -560,8 +541,7 @@ export class _ExperimentManager { branch = await this.chooseBranch(slug, branches, userId); } - const features = featuresCompat(branch); - for (const feature of features) { + for (const feature of branch.features) { const existingEnrollment = storeLookupByFeature(feature?.featureId); if (existingEnrollment) { lazy.log.debug( @@ -617,7 +597,7 @@ export class _ExperimentManager { ].filter(enrollment => enrollment); for (const enrollment of prefFlipEnrollments) { - const featureValue = getFeatureFromBranch( + const featureValue = _ExperimentManager.getFeatureConfigFromBranch( enrollment.branch, PrefFlipsFeature.FEATURE_ID ).value; @@ -691,8 +671,7 @@ export class _ExperimentManager { * If the experiment has the same slug after unenrollment adding it to the * store will overwrite the initial experiment. */ - const features = featuresCompat(branch); - for (let feature of features) { + for (let feature of branch.features) { const isRollout = recipe.isRollout ?? false; let enrollment = isRollout ? this.store.getRolloutForFeature(feature?.featureId) @@ -1064,7 +1043,7 @@ export class _ExperimentManager { const getConflictingEnrollment = this._makeEnrollmentCache(isRollout); - for (const { featureId, value: featureValue } of featuresCompat(branch)) { + for (const { featureId, value: featureValue } of branch.features) { const feature = lazy.NimbusFeatures[featureId]; if (!feature) { @@ -1244,7 +1223,7 @@ export class _ExperimentManager { // If we are an unenrolling from an experiment, we have a rollout that would // set the same pref, so we update the pref to that value instead of // the original value. - newValue = getFeatureFromBranch( + newValue = _ExperimentManager.getFeatureConfigFromBranch( conflictingEnrollment.branch, pref.featureId ).value[pref.variable]; @@ -1290,8 +1269,8 @@ export class _ExperimentManager { return false; } - const featuresById = Object.assign( - ...featuresCompat(branch).map(f => ({ [f.featureId]: f })) + const featuresById = Object.fromEntries( + branch.features.map(f => [f.featureId, f]) ); for (const { name, featureId, variable } of prefs) { @@ -1546,7 +1525,7 @@ export class _ExperimentManager { } } - const feature = getFeatureFromBranch( + const feature = _ExperimentManager.getFeatureConfigFromBranch( enrollments.at(-1).branch, pref.featureId ); @@ -1564,6 +1543,23 @@ export class _ExperimentManager { this._unenroll(enrollment, UnenrollmentCause.ChangedPref(changedPref)); } } + + /** + * Return the feature configuration with the matching feature ID from the + * given branch. + * + * @param {object} branch + * The branch object. + * + * @param {string} featureId + * The feature to search for. + * + * @returns {object} + * The feature configuration, including the feature ID and the value. + */ + static getFeatureConfigFromBranch(branch, featureId) { + return branch.features.find(f => f.featureId === featureId); + } } export const ExperimentManager = new _ExperimentManager(); diff --git a/toolkit/components/nimbus/lib/ExperimentStore.sys.mjs b/toolkit/components/nimbus/lib/ExperimentStore.sys.mjs index 780a6d2b86d..7c376334fdd 100644 --- a/toolkit/components/nimbus/lib/ExperimentStore.sys.mjs +++ b/toolkit/components/nimbus/lib/ExperimentStore.sys.mjs @@ -107,6 +107,11 @@ ChromeUtils.defineLazyGetter(lazy, "syncDataStore", () => { prefBranch, featureId ); + // We store the enrollment in the pref in a single-feature format, but + // Nimbus only supports multi-featured experiments, so we massage the + // enrollment into a multi-featured one. + metadata.branch.features = [metadata.branch.feature]; + delete metadata.branch.feature; return metadata; }, @@ -120,6 +125,11 @@ ChromeUtils.defineLazyGetter(lazy, "syncDataStore", () => { prefBranch, featureId ); + // We store the enrollment in the pref in a single-feature format, but + // Nimbus only supports multi-featured experiments, so we massage the + // enrollment into a multi-featured one. + metadata.branch.features = [metadata.branch.feature]; + delete metadata.branch.feature; return metadata; }, @@ -198,19 +208,6 @@ ChromeUtils.defineLazyGetter(lazy, "syncDataStore", () => { const DEFAULT_STORE_ID = "ExperimentStoreData"; -function featuresCompat(branch) { - if (!branch || (!branch.feature && !branch.features)) { - return []; - } - let { features } = branch; - // In <=v1.5.0 of the Nimbus API, experiments had single feature - if (!features) { - features = [branch.feature]; - } - - return features; -} - export class ExperimentStore extends SharedDataMap { static SYNC_DATA_PREF_BRANCH = SYNC_DATA_PREF_BRANCH; static SYNC_DEFAULTS_PREF_BRANCH = SYNC_DEFAULTS_PREF_BRANCH; @@ -401,8 +398,7 @@ export class ExperimentStore extends SharedDataMap { * @param {Enrollment} enrollment Experiment or rollout */ _updateSyncStore(enrollment) { - let features = featuresCompat(enrollment.branch); - for (let feature of features) { + for (let feature of enrollment.branch.features) { if (lazy.FeatureManifest[feature.featureId]?.isEarlyStartup) { if (!enrollment.active) { // Remove experiments on un-enroll, no need to check if it exists diff --git a/toolkit/components/nimbus/test/browser/browser.toml b/toolkit/components/nimbus/test/browser/browser.toml index 9d2929d9e26..1f99f9eb37d 100644 --- a/toolkit/components/nimbus/test/browser/browser.toml +++ b/toolkit/components/nimbus/test/browser/browser.toml @@ -10,8 +10,6 @@ skip-if = [ ["browser_experiment_evaluate_jexl.js"] -["browser_experiment_single_feature_enrollment.js"] - ["browser_experimentapi_child.js"] ["browser_nimbus_telemetry.js"] diff --git a/toolkit/components/nimbus/test/browser/browser_experiment_single_feature_enrollment.js b/toolkit/components/nimbus/test/browser/browser_experiment_single_feature_enrollment.js deleted file mode 100644 index 6be0d72e50c..00000000000 --- a/toolkit/components/nimbus/test/browser/browser_experiment_single_feature_enrollment.js +++ /dev/null @@ -1,83 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ */ - -const { NimbusTelemetry } = ChromeUtils.importESModule( - "resource://nimbus/lib/Telemetry.sys.mjs" -); - -("use strict"); - -const SINGLE_FEATURE_RECIPE = { - appId: "firefox-desktop", - appName: "firefox_desktop", - arguments: {}, - branches: [ - { - feature: { - featureId: "urlbar", - value: { - enabled: true, - quickSuggestEnabled: false, - quickSuggestNonSponsoredIndex: -1, - quickSuggestSponsoredIndex: -1, - }, - }, - ratio: 1, - slug: "control", - }, - ], - bucketConfig: { - count: 10000, - namespace: "urlbar-9", - randomizationUnit: "normandy_id", - start: 0, - total: 10000, - }, - channel: "release", - endDate: null, - featureIds: ["urlbar"], - id: "firefox-suggest-history-vs-offline", - isEnrollmentPaused: false, - outcomes: [], - probeSets: [], - proposedDuration: 28, - proposedEnrollment: 7, - referenceBranch: "control", - schemaVersion: "1.5.0", - slug: "firefox-suggest-history-vs-offline", - startDate: "2021-07-21", - targeting: "true", - userFacingDescription: "Smarter suggestions in the AwesomeBar", - userFacingName: "Firefox Suggest - History vs Offline", -}; - -const SYNC_DATA_PREF_BRANCH = "nimbus.syncdatastore."; - -add_task(async function test_TODO() { - let sandbox = sinon.createSandbox(); - sandbox.stub(NimbusTelemetry, "recordExposure"); - - const doExperimentCleanup = await ExperimentFakes.enrollmentHelper( - SINGLE_FEATURE_RECIPE - ); - - Assert.ok( - ExperimentAPI.getExperiment({ featureId: "urlbar" }), - "Should enroll in single feature experiment" - ); - - NimbusFeatures.urlbar.recordExposureEvent(); - - Assert.ok( - NimbusTelemetry.recordExposure.calledOnceWith( - "firefox-suggest-history-vs-offline", - "control", - "urlbar" - ), - "Should be called once by urlbar" - ); - - doExperimentCleanup(); - sandbox.restore(); - NimbusFeatures.urlbar._didSendExposureEvent = false; -}); diff --git a/toolkit/components/nimbus/test/unit/test_ExperimentAPI_NimbusFeatures.js b/toolkit/components/nimbus/test/unit/test_ExperimentAPI_NimbusFeatures.js index 39d822ad3e4..cf7603ad883 100644 --- a/toolkit/components/nimbus/test/unit/test_ExperimentAPI_NimbusFeatures.js +++ b/toolkit/components/nimbus/test/unit/test_ExperimentAPI_NimbusFeatures.js @@ -154,60 +154,6 @@ add_task(async function update_remote_defaults_onUpdate() { cleanup(); }); -add_task(async function test_features_over_feature() { - const { manager, cleanup } = await NimbusTestUtils.setupTest(); - const rollout_features_and_feature = Object.freeze( - ExperimentFakes.rollout("matching-rollout", { - branch: { - slug: "slug", - ratio: 1, - feature: { - featureId: TEST_FEATURE.featureId, - value: { enabled: false }, - }, - features: [ - { - featureId: TEST_FEATURE.featureId, - value: { enabled: true }, - }, - ], - }, - }) - ); - const rollout_just_feature = Object.freeze( - ExperimentFakes.rollout("matching-rollout", { - branch: { - slug: "slug", - ratio: 1, - feature: { - featureId: TEST_FEATURE.featureId, - value: { enabled: false }, - }, - }, - }) - ); - - await manager.store.addEnrollment(rollout_features_and_feature); - Assert.ok( - TEST_FEATURE.getVariable("enabled"), - "Should read from the features property over feature" - ); - - manager.store._deleteForTests("aboutwelcome"); - manager.store._deleteForTests("matching-rollout"); - - await manager.store.addEnrollment(rollout_just_feature); - Assert.ok( - !TEST_FEATURE.getVariable("enabled"), - "Should read from the feature property when features doesn't exist" - ); - - manager.store._deleteForTests("aboutwelcome"); - manager.store._deleteForTests("matching-rollout"); - - cleanup(); -}); - add_task(async function update_remote_defaults_readyPromise() { const { sandbox, manager, cleanup } = await NimbusTestUtils.setupTest(); const feature = new ExperimentFeature("aboutwelcome"); diff --git a/toolkit/components/nimbus/test/unit/test_ExperimentStore.js b/toolkit/components/nimbus/test/unit/test_ExperimentStore.js index c5f53f9903d..8a6bde7db4c 100644 --- a/toolkit/components/nimbus/test/unit/test_ExperimentStore.js +++ b/toolkit/components/nimbus/test/unit/test_ExperimentStore.js @@ -345,7 +345,7 @@ add_task(async function test_sync_access_update() { Assert.deepEqual( // `branch.feature` and not `features` because for sync access (early startup) // experiments we only store the `isEarlyStartup` feature - cachedExperiment.branch.feature.value, + cachedExperiment.branch.features[0].value, { bar: "bar", enabled: true }, "Got updated value" ); @@ -496,7 +496,7 @@ add_task(async function test_getRolloutForFeature_fromSyncCache() { "Should return back the same rollout" ); Assert.deepEqual( - newStore.getRolloutForFeature(rollout.featureIds[0]).branch.feature, + newStore.getRolloutForFeature(rollout.featureIds[0]).branch.features[0], rollout.branch.features[0], "Should return back the same feature" ); @@ -692,7 +692,7 @@ add_task(async function test_storeValuePerPref_returnsSameValue_allTypes() { const newStore = NimbusTestUtils.stubs.store(); Assert.deepEqual( - newStore.getExperimentForFeature("purple").branch.feature.value, + newStore.getExperimentForFeature("purple").branch.features[0].value, experiment.branch.features[0].value, "Returns the same value" ); diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetryControllerShutdown.js b/toolkit/components/telemetry/tests/unit/test_TelemetryControllerShutdown.js index 95ef3789d56..5484ed68b61 100644 --- a/toolkit/components/telemetry/tests/unit/test_TelemetryControllerShutdown.js +++ b/toolkit/components/telemetry/tests/unit/test_TelemetryControllerShutdown.js @@ -34,6 +34,14 @@ add_task(async function test_setup() { "1.9.2" ); finishAddonManagerStartup(); + // Make sure any pending XPIDatabase.saveChanges deferred tasks + // are finalized, needed to prevent intermittent failure when the test + // exits quickly and the saveChanges deferred tasks may end up + // executing too late, and AddonTestUtils.promiseShutdownManager + // will rethrow that error here: + // https://searchfox.org/mozilla-central/rev/60108fa975/toolkit/mozapps/extensions/internal/AddonTestUtils.sys.mjs#968 + await AddonTestUtils.getXPIExports().XPIDatabase.finalize(); + fakeIntlReady(); // Make sure we don't generate unexpected pings due to pref changes. await setEmptyPrefWatchlist(); diff --git a/toolkit/crashreporter/ExtraFileParser.cpp b/toolkit/crashreporter/ExtraFileParser.cpp new file mode 100644 index 00000000000..e344d867c91 --- /dev/null +++ b/toolkit/crashreporter/ExtraFileParser.cpp @@ -0,0 +1,97 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ExtraFileParser.h" + +#include "CrashAnnotations.h" +#include "mozilla/Maybe.h" + +namespace CrashReporter { + +using mozilla::Maybe; +using mozilla::Nothing; +using mozilla::Some; + +bool ExtraFileParser::startObject() { + if (mObject) { + return false; // We expect only one top-level object + } + + mObject = true; + return true; +} + +bool ExtraFileParser::endObject() { + return mObject; // We should end only one object, anything else is wrong. +} + +bool ExtraFileParser::propertyName(const JS::Latin1Char* aName, + size_t aLength) { + nsDependentCSubstring name(reinterpret_cast(aName), aLength); + mLastAnnotation = AnnotationFromString(name); + + return mLastAnnotation.isSome(); +} + +bool ExtraFileParser::propertyName(const char16_t* aName, size_t aLength) { + // We only parse UTF-8 text. + return false; +} + +bool ExtraFileParser::startArray() { + // The .extra file should not contain arrays. + return false; +} + +bool ExtraFileParser::endArray() { + // The .extra file should not contain arrays. + return false; +} + +bool ExtraFileParser::stringValue(const JS::Latin1Char* aStr, size_t aLength) { + nsDependentCSubstring value(reinterpret_cast(aStr), aLength); + mAnnotations[*mLastAnnotation] = value; + + return true; +} + +bool ExtraFileParser::stringValue(const char16_t* aStr, size_t aLength) { + // We only parse UTF-8 text. + return false; +} + +bool ExtraFileParser::numberValue(double aVal) { + // The .extra file should not contain number values. + return false; +} + +bool ExtraFileParser::booleanValue(bool aBoolean) { + // The .extra file should not contain number values. + return false; +} + +bool ExtraFileParser::nullValue() { + // The .extra file should not contain null values. + return false; +} +void ExtraFileParser::error(const char* aMsg, uint32_t aLine, + uint32_t aColumn) {} + +Maybe ExtraFileParser::Parse(const nsACString& aJSON) { + ExtraFileParser handler; + AnnotationTable annotations; + + if (!JS::ParseJSONWithHandler( + reinterpret_cast(aJSON.BeginReading()), + aJSON.Length(), &handler)) { + return Nothing(); + } + + handler.getAnnotations(annotations); + return Some(annotations); +} + +} // namespace CrashReporter diff --git a/toolkit/crashreporter/ExtraFileParser.h b/toolkit/crashreporter/ExtraFileParser.h new file mode 100644 index 00000000000..96f8f1e67c0 --- /dev/null +++ b/toolkit/crashreporter/ExtraFileParser.h @@ -0,0 +1,56 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef ExtraFileParser_h__ +#define ExtraFileParser_h__ + +#include "js/JSON.h" // JS::JSONParseHandler + +#include "mozilla/EnumeratedArray.h" +#include "mozilla/Maybe.h" +#include "nsString.h" + +#include "CrashAnnotations.h" + +namespace CrashReporter { + +using mozilla::Maybe; +using AnnotationTable = + mozilla::EnumeratedArray; + +class ExtraFileParser : public JS::JSONParseHandler { + public: + ExtraFileParser() : mObject(false) {} + + // JSONParseHandler methods. + virtual bool startObject() override; + virtual bool endObject() override; + virtual bool propertyName(const JS::Latin1Char* aName, + size_t aLength) override; + virtual bool propertyName(const char16_t* aName, size_t aLength) override; + virtual bool startArray() override; + virtual bool endArray() override; + virtual bool stringValue(const JS::Latin1Char* aStr, size_t aLength) override; + virtual bool stringValue(const char16_t* aStr, size_t aLength) override; + virtual bool numberValue(double aVal) override; + virtual bool booleanValue(bool aBoolean) override; + virtual bool nullValue() override; + virtual void error(const char* aMsg, uint32_t aLine, + uint32_t aColumn) override; + + void getAnnotations(AnnotationTable& aAnnotations) { + aAnnotations = mAnnotations; + } + + static mozilla::Maybe Parse(const nsACString& aJSON); + + private: + AnnotationTable mAnnotations; + Maybe mLastAnnotation; // Last annotation seen while parsing + bool mObject; // Set to true after we encounter the first object +}; + +} // namespace CrashReporter + +#endif // ExtraFileParser_h__ diff --git a/toolkit/crashreporter/bionic_missing_funcs.cpp b/toolkit/crashreporter/bionic_missing_funcs.cpp index 81e8bef303b..2c9daea50f3 100644 --- a/toolkit/crashreporter/bionic_missing_funcs.cpp +++ b/toolkit/crashreporter/bionic_missing_funcs.cpp @@ -12,14 +12,29 @@ extern "C" { +#if defined(__ANDROID_API__) && (__ANDROID_API__ < 28) + +// Bionic introduced support for syncfs only in version 28 (that is +// Android Pie / 9). Since GeckoView is built with version 21, those functions +// aren't defined, but nix needs them and the crash helper relies on nix. These +// functions should never be called in practice hence we implement them only to +// satisfy nix linking requirements but we crash if we accidentally enter them. + +int syncfs(int fd) { + MOZ_CRASH("syncfs() is not available"); + return EPERM; +} + +#endif // __ANDROID_API__ && (__ANDROID_API__ < 28) + #if defined(__ANDROID_API__) && (__ANDROID_API__ < 24) // Bionic introduced support for getgrgid_r() and getgrnam_r() only in version // 24 (that is Android Nougat / 7.0). Since GeckoView is built with version 21, -// those functions aren't defined, but nix needs them and minidump-writer -// relies on nix. These functions should never be called in practice hence we -// implement them only to satisfy nix linking requirements but we crash if we -// accidentally enter them. +// those functions aren't defined, but the nix crate needs them and +// minidump-writer relies on nix. These functions should never be called in +// practice hence we implement them only to satisfy nix linking requirements +// but we crash if we accidentally enter them. int getgrgid_r(gid_t gid, struct group* grp, char* buf, size_t buflen, struct group** result) { diff --git a/toolkit/crashreporter/breakpad-client/linux/crash_generation/crash_generation_server.cc b/toolkit/crashreporter/breakpad-client/linux/crash_generation/crash_generation_server.cc index 911a7e6ac21..b74c8aeda48 100644 --- a/toolkit/crashreporter/breakpad-client/linux/crash_generation/crash_generation_server.cc +++ b/toolkit/crashreporter/breakpad-client/linux/crash_generation/crash_generation_server.cc @@ -62,17 +62,19 @@ namespace google_breakpad { CrashGenerationServer::CrashGenerationServer( const int listen_fd, #if defined(MOZ_OXIDIZED_BREAKPAD) - std::function get_auxv_info, + std::function get_auxv_info, #endif // defined(MOZ_OXIDIZED_BREAKPAD) std::function dump_callback, + void* dump_context, const string* dump_path) : server_fd_(listen_fd), #if defined(MOZ_OXIDIZED_BREAKPAD) get_auxv_info_(std::move(get_auxv_info)), #endif // defined(MOZ_OXIDIZED_BREAKPAD) dump_callback_(std::move(dump_callback)), - started_(false), - reserved_fds_{-1, -1} + dump_context_(dump_context), + dump_dir_mutex_(PTHREAD_MUTEX_INITIALIZER), + started_(false) { if (dump_path) dump_dir_ = *dump_path; @@ -137,6 +139,14 @@ CrashGenerationServer::Stop() started_ = false; } +void +CrashGenerationServer::SetPath(const char* dump_path) +{ + pthread_mutex_lock(&dump_dir_mutex_); + this->dump_dir_ = string(dump_path); + pthread_mutex_unlock(&dump_dir_mutex_); +} + //static bool CrashGenerationServer::CreateReportChannel(int* server_fd, int* client_fd) @@ -153,8 +163,6 @@ CrashGenerationServer::CreateReportChannel(int* server_fd, int* client_fd) if (fcntl(fds[1], F_SETFL, O_NONBLOCK)) return false; - if (fcntl(fds[1], F_SETFD, FD_CLOEXEC)) - return false; *client_fd = fds[0]; *server_fd = fds[1]; @@ -166,8 +174,6 @@ CrashGenerationServer::CreateReportChannel(int* server_fd, int* client_fd) void CrashGenerationServer::Run() { - ReserveFileDescriptors(); - struct pollfd pollfds[2]; memset(&pollfds, 0, sizeof(pollfds)); @@ -274,11 +280,6 @@ CrashGenerationServer::ClientEvent(short revents) if (!MakeMinidumpFilename(minidump_filename)) return true; - // We won't re-reserve the file descriptors past this point. If we crash more - // than once we'll just accept that we might run out of file descriptors, but - // we don't want to make the situation worse by trying to grab them again. - ReleaseFileDescriptors(); - #if defined(MOZ_OXIDIZED_BREAKPAD) ExceptionHandler::CrashContext* breakpad_cc = reinterpret_cast(crash_context); @@ -338,7 +339,7 @@ CrashGenerationServer::ClientEvent(short revents) } #endif if (dump_callback_) { - dump_callback_(info, minidump_filename); + dump_callback_(dump_context_, info, minidump_filename); } // Send the done signal to the process: it can exit now. @@ -385,36 +386,12 @@ CrashGenerationServer::MakeMinidumpFilename(string& outFilename) return false; char path[PATH_MAX]; + pthread_mutex_lock(&dump_dir_mutex_); snprintf(path, sizeof(path), "%s/%s.dmp", dump_dir_.c_str(), guidString); + pthread_mutex_unlock(&dump_dir_mutex_); outFilename = path; return true; } -void -CrashGenerationServer::ReserveFileDescriptors() { - for (size_t i = 0; i < CrashGenerationServer::RESERVED_FDS_NUM; i++) { - assert(reserved_fds_[i] < 0); - - // This fd is just taking up space in the file table, so it can be - // anything that's self-contained and simple to create. - int fds[2]; - int rv = pipe2(fds, O_CLOEXEC); - if (rv == 0) { - close(fds[0]); - reserved_fds_[i] = fds[1]; - } - } -} - -void -CrashGenerationServer::ReleaseFileDescriptors() { - for (size_t i = 0; i < CrashGenerationServer::RESERVED_FDS_NUM; i++) { - if (reserved_fds_[i] > 0) { - close(reserved_fds_[i]); - reserved_fds_[i] = -1; - } - } -} - } // namespace google_breakpad diff --git a/toolkit/crashreporter/breakpad-client/linux/crash_generation/crash_generation_server.h b/toolkit/crashreporter/breakpad-client/linux/crash_generation/crash_generation_server.h index 04abdeec58c..b96c3c0a3ed 100644 --- a/toolkit/crashreporter/breakpad-client/linux/crash_generation/crash_generation_server.h +++ b/toolkit/crashreporter/breakpad-client/linux/crash_generation/crash_generation_server.h @@ -51,24 +51,31 @@ public: // WARNING: callbacks may be invoked on a different thread // than that which creates the CrashGenerationServer. They must // be thread safe. - using OnClientDumpRequestCallback = void (const ClientInfo& client_info, + using OnClientDumpRequestCallback = void (void* dump_context, + const ClientInfo& client_info, const string& file_path); - #if defined(MOZ_OXIDIZED_BREAKPAD) - using GetAuxvInfo = bool (pid_t pid, DirectAuxvDumpInfo*); + using GetAuxvInfoCallback = bool (pid_t pid, DirectAuxvDumpInfo*); #endif // defined(MOZ_OXIDIZED_BREAKPAD) - // listen_fd: The server fd created by CreateReportChannel() - // get_auxv_info: Callback to retrieve the stored auxiliary vector for the given PID - // dump_callback: Callback for a client crash dump request. - // dump_path: Path for generating dumps - CrashGenerationServer( - const int listen_fd, + // Create an instance with the given parameters. + // + // Parameter listen_fd: The server fd created by CreateReportChannel(). + // Parameter: get_auxv_info: Callback to retrieve the stored auxiliary vector for the given PID. + // Parameter dump_callback: Callback for a client crash dump request. + // Parameter dump_context: Context for client crash dump request callback. + // Client code of this class might want to generate dumps explicitly + // in the crash dump request callback. In that case, false can be + // passed for this parameter. + // Parameter dump_path: Path for generating dumps; required only if true is + // passed for generateDumps parameter; NULL can be passed otherwise. + CrashGenerationServer(const int listen_fd, #if defined(MOZ_OXIDIZED_BREAKPAD) - std::function get_auxv_info, + std::function get_auxv_info, #endif // defined(MOZ_OXIDIZED_BREAKPAD) - std::function dump_callback, - const string* dump_path); + std::function dump_callback, + void* dump_context, + const string* dump_path); ~CrashGenerationServer(); @@ -80,6 +87,9 @@ public: // Stop the server. void Stop(); + // Adjust the path where minidumps are placed, this is thread-safe + void SetPath(const char* dump_path); + // Create a "channel" that can be used by clients to report crashes // to a CrashGenerationServer. |*server_fd| should be passed to // this class's constructor, and |*client_fd| should be passed to @@ -107,19 +117,16 @@ private: // Return a unique filename at which a minidump can be written bool MakeMinidumpFilename(string& outFilename); - // Reserve a handful of file descriptors to make them available when we - // generate a minidump. - void ReserveFileDescriptors(); - void ReleaseFileDescriptors(); - int server_fd_; #if defined(MOZ_OXIDIZED_BREAKPAD) - std::function get_auxv_info_; + std::function get_auxv_info_; #endif // defined(MOZ_OXIDIZED_BREAKPAD) std::function dump_callback_; + void* dump_context_; + pthread_mutex_t dump_dir_mutex_; string dump_dir_; bool started_; @@ -127,9 +134,6 @@ private: pthread_t thread_; int control_pipe_in_; int control_pipe_out_; - - static const size_t RESERVED_FDS_NUM = 2; - std::array reserved_fds_; }; } // namespace google_breakpad diff --git a/toolkit/crashreporter/breakpad-client/mac/crash_generation/crash_generation_server.cc b/toolkit/crashreporter/breakpad-client/mac/crash_generation/crash_generation_server.cc index 8cbc012caa1..c2b7481524c 100644 --- a/toolkit/crashreporter/breakpad-client/mac/crash_generation/crash_generation_server.cc +++ b/toolkit/crashreporter/breakpad-client/mac/crash_generation/crash_generation_server.cc @@ -54,6 +54,7 @@ CrashGenerationServer::CrashGenerationServer( exit_callback_(exit_callback), exit_context_(exit_context), generate_dumps_(generate_dumps), + dump_dir_mutex_(PTHREAD_MUTEX_INITIALIZER), dump_dir_(dump_path.empty() ? "/tmp" : dump_path), started_(false), receive_port_(mach_port_name), @@ -89,6 +90,14 @@ bool CrashGenerationServer::Stop() { return !started_; } +void +CrashGenerationServer::SetPath(const char* dump_path) +{ + pthread_mutex_lock(&dump_dir_mutex_); + this->dump_dir_ = string(dump_path); + pthread_mutex_unlock(&dump_dir_mutex_); +} + // static void *CrashGenerationServer::WaitForMessages(void *server) { pthread_setname_np("Breakpad CrashGenerationServer"); @@ -120,7 +129,9 @@ bool CrashGenerationServer::WaitForOneMessage() { ScopedTaskSuspend suspend(remote_task); MinidumpGenerator generator(remote_task, handler_thread); + pthread_mutex_lock(&dump_dir_mutex_); dump_path = generator.UniqueNameInDirectory(dump_dir_, NULL); + pthread_mutex_unlock(&dump_dir_mutex_); if (info.exception_type && info.exception_code) { generator.SetExceptionInformation(info.exception_type, diff --git a/toolkit/crashreporter/breakpad-client/mac/crash_generation/crash_generation_server.h b/toolkit/crashreporter/breakpad-client/mac/crash_generation/crash_generation_server.h index d0b39f3acf8..339ef21bdab 100644 --- a/toolkit/crashreporter/breakpad-client/mac/crash_generation/crash_generation_server.h +++ b/toolkit/crashreporter/breakpad-client/mac/crash_generation/crash_generation_server.h @@ -104,6 +104,9 @@ class CrashGenerationServer { // Stop the server. bool Stop(); + // Adjust the path where minidumps are placed, this is thread-safe + void SetPath(const char* dump_path); + private: // Return a unique filename at which a minidump can be written. bool MakeMinidumpFilename(std::string &outFilename); @@ -127,6 +130,7 @@ class CrashGenerationServer { bool generate_dumps_; + pthread_mutex_t dump_dir_mutex_; std::string dump_dir_; bool started_; diff --git a/toolkit/crashreporter/breakpad-client/moz.build b/toolkit/crashreporter/breakpad-client/moz.build index 9ee42e41364..89a0a9d98bc 100644 --- a/toolkit/crashreporter/breakpad-client/moz.build +++ b/toolkit/crashreporter/breakpad-client/moz.build @@ -8,8 +8,6 @@ SOURCES += [ 'minidump_file_writer.cc', ] -Library('breakpad_client') - USE_LIBS += [ 'breakpad_common_s', ] @@ -19,15 +17,19 @@ if CONFIG['OS_ARCH'] == 'Darwin': 'breakpad_mac_common_s', ] elif CONFIG['OS_ARCH'] == 'Linux': + UNIFIED_SOURCES += [ + '../linux_utils.cc', + ] + USE_LIBS += [ 'breakpad_linux_common_s', ] -FINAL_LIBRARY = 'xul' - LOCAL_INCLUDES += [ '/toolkit/crashreporter/google-breakpad/src', ] if CONFIG['CC_TYPE'] in ('clang', 'gcc'): CXXFLAGS += ['-Wno-error=stack-protector'] + +Library('breakpad_client') diff --git a/toolkit/crashreporter/breakpad-client/windows/crash_generation/crash_generation_server.cc b/toolkit/crashreporter/breakpad-client/windows/crash_generation/crash_generation_server.cc index 964a42f9db1..c178f7869b4 100644 --- a/toolkit/crashreporter/breakpad-client/windows/crash_generation/crash_generation_server.cc +++ b/toolkit/crashreporter/breakpad-client/windows/crash_generation/crash_generation_server.cc @@ -265,6 +265,17 @@ bool CrashGenerationServer::Start() { return true; } +void CrashGenerationServer::SetPath(const wchar_t* dump_path) { + AutoCriticalSection lock(&sync_); + std::wstring local_path(dump_path); + dump_path_ = local_path; +} + +std::wstring CrashGenerationServer::GetPath() { + AutoCriticalSection lock(&sync_); + return dump_path_; +} + // If the server thread serving clients ever gets into the // ERROR state, reset the event, close the pipe and remain // in the error state forever. Error state means something @@ -953,7 +964,7 @@ bool CrashGenerationServer::GenerateDump(const ClientInfo& client, !include_context_heap_); } - MinidumpGenerator dump_generator(dump_path_, + MinidumpGenerator dump_generator(GetPath(), client.process_handle(), client.pid(), client_thread_id, diff --git a/toolkit/crashreporter/breakpad-client/windows/crash_generation/crash_generation_server.h b/toolkit/crashreporter/breakpad-client/windows/crash_generation/crash_generation_server.h index cc1912cf3c1..f1da86389ac 100644 --- a/toolkit/crashreporter/breakpad-client/windows/crash_generation/crash_generation_server.h +++ b/toolkit/crashreporter/breakpad-client/windows/crash_generation/crash_generation_server.h @@ -108,6 +108,12 @@ class CrashGenerationServer { // Returns true if initialization is successful; false otherwise. bool Start(); + // Adjust the path where minidumps are placed, this is thread-safe. + void SetPath(const wchar_t* dump_path); + + // Read the current dump path, this is thread-safe. + std::wstring GetPath(); + void pre_fetch_custom_info(bool do_pre_fetch) { pre_fetch_custom_info_ = do_pre_fetch; } @@ -228,7 +234,7 @@ class CrashGenerationServer { // asynchronous IO operation. void EnterStateWhenSignaled(IPCServerState state); - // Sync object for thread-safe access to the shared list of clients. + // Sync object for thread-safe access to the shared list of clients and path. CRITICAL_SECTION sync_; // List of clients. @@ -283,7 +289,7 @@ class CrashGenerationServer { bool pre_fetch_custom_info_; // The dump path for the server. - const std::wstring dump_path_; + std::wstring dump_path_; // State of the server in performing the IPC with the client. // Note that since we restrict the pipe to one instance, we diff --git a/toolkit/crashreporter/breakpad_wrapper/breakpad_wrapper.cpp b/toolkit/crashreporter/breakpad_wrapper/breakpad_wrapper.cpp new file mode 100644 index 00000000000..7c51756b7a3 --- /dev/null +++ b/toolkit/crashreporter/breakpad_wrapper/breakpad_wrapper.cpp @@ -0,0 +1,193 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include + +#if defined(XP_LINUX) +# include +# include +# include "linux/crash_generation/client_info.h" +# include "linux/crash_generation/crash_generation_server.h" +using breakpad_char = char; +using breakpad_string = std::string; +using breakpad_init_type = int; +using breakpad_pid = pid_t; +#elif defined(XP_WIN) +# include "windows/crash_generation/client_info.h" +# include "windows/crash_generation/crash_generation_server.h" +using breakpad_char = wchar_t; +using breakpad_string = std::wstring; +using breakpad_init_type = wchar_t*; +using breakpad_pid = DWORD; +#elif defined(XP_MACOSX) +# include +# include +# include "mac/crash_generation/client_info.h" +# include "mac/crash_generation/crash_generation_server.h" +using breakpad_char = char; +using breakpad_string = std::string; +using breakpad_init_type = const char*; +using breakpad_pid = pid_t; +#else +# error "Unsupported platform" +#endif + +#ifdef MOZ_PHC + +# include "PHC.h" + +namespace mozilla::phc { + +// HACK: The breakpad code expects this global variable even though we don't +// use it in the wrapper. +MOZ_RUNINIT mozilla::phc::AddrInfo gAddrInfo; + +} // namespace mozilla::phc + +#endif // defined(MOZ_PHC) + +using google_breakpad::ClientInfo; +using google_breakpad::CrashGenerationServer; + +// This struct and the callback that uses it need to be kept in sync with the +// corresponding Rust code in src/crash_generation.rs. +struct BreakpadProcessId { + breakpad_pid pid; +#if defined(XP_MACOSX) + task_t task; +#elif defined(XP_WIN) + HANDLE handle; +#endif +}; + +using RustDumpCallback = void (*)(BreakpadProcessId, const char*, + const breakpad_char*); +#if defined(XP_LINUX) +using RustAuxvCallback = bool (*)(breakpad_pid, DirectAuxvDumpInfo*); +#endif // defined(XP_LINUX) + +void onClientDumpRequestCallback(void* context, const ClientInfo& client_info, + const breakpad_string& file_path) { + RustDumpCallback callback = reinterpret_cast(context); + BreakpadProcessId process_id = { + .pid = client_info.pid(), +#if defined(XP_MACOSX) + .task = client_info.task(), +#elif defined(XP_WIN) + .handle = client_info.process_handle(), +#endif + }; + const char* error_msg = +#if defined(XP_LINUX) + client_info.error_msg(); +#else + nullptr; +#endif // XP_LINUX + + callback(process_id, error_msg, file_path.c_str()); +} + +#if defined(XP_LINUX) +bool getAuxvDumpInfo(RustAuxvCallback callback, breakpad_pid aPid, + DirectAuxvDumpInfo* aAuxvInfo) { + return callback(aPid, aAuxvInfo); +} +#endif // defined(XP_LINUX) + +#ifdef XP_WIN + +extern "C" void* CrashGenerationServer_init(breakpad_init_type aBreakpadData, + const breakpad_char* aMinidumpPath, + RustDumpCallback aDumpCallback) { + breakpad_string minidumpPath(aMinidumpPath); + breakpad_string breakpadData(aBreakpadData); + + CrashGenerationServer* server = new CrashGenerationServer( + breakpadData, + /* pipe_sec_attrs */ nullptr, + /* connect_callback */ nullptr, + /* connect_context */ nullptr, onClientDumpRequestCallback, + reinterpret_cast(aDumpCallback), + /* written_callback */ nullptr, + /* exit_callback */ nullptr, + /* exit_context */ nullptr, + /* upload_request_callback */ nullptr, + /* upload_context */ nullptr, + /* generate_dumps */ true, &minidumpPath); + + if (!server->Start()) { + delete server; + return nullptr; + } + + return server; +} + +#elif defined(XP_MACOSX) + +extern "C" void* CrashGenerationServer_init(breakpad_init_type aBreakpadData, + const breakpad_char* aMinidumpPath, + RustDumpCallback aDumpCallback) { + breakpad_string minidumpPath(aMinidumpPath); + breakpad_init_type breakpadData = aBreakpadData; + + CrashGenerationServer* server = new CrashGenerationServer( + breakpadData, + /* filter */ nullptr, + /* filter_context */ nullptr, onClientDumpRequestCallback, + reinterpret_cast(aDumpCallback), + /* exit_callback */ nullptr, + /* exit_context */ nullptr, + /* generate_dumps */ true, minidumpPath); + + if (!server->Start()) { + delete server; + return nullptr; + } + + return server; +} + +#elif defined(XP_LINUX) + +extern "C" void* CrashGenerationServer_init(breakpad_init_type aBreakpadData, + const breakpad_char* aMinidumpPath, + RustDumpCallback aDumpCallback, + RustAuxvCallback aAuxvCallback) { + breakpad_string minidumpPath(aMinidumpPath); + breakpad_init_type breakpadData = aBreakpadData; + + CrashGenerationServer* server = new CrashGenerationServer( + breakpadData, + [aAuxvCallback](pid_t aPid, DirectAuxvDumpInfo* aAuxvInfo) { + return getAuxvDumpInfo(aAuxvCallback, aPid, aAuxvInfo); + }, + [aDumpCallback](void* dump_context, const ClientInfo& aClientInfo, + const breakpad_string& aFilePath) { + onClientDumpRequestCallback(reinterpret_cast(aDumpCallback), + aClientInfo, aFilePath); + }, + /* dump_context */ nullptr, &minidumpPath); + + if (!server->Start()) { + delete server; + return nullptr; + } + + return server; +} + +#endif + +extern "C" void CrashGenerationServer_shutdown(void* aServer) { + CrashGenerationServer* server = static_cast(aServer); + delete server; +} + +extern "C" void CrashGenerationServer_set_path( + void* aServer, const breakpad_char* aMinidumpPath) { + CrashGenerationServer* server = static_cast(aServer); + server->SetPath(aMinidumpPath); +} diff --git a/toolkit/crashreporter/breakpad_wrapper/moz.build b/toolkit/crashreporter/breakpad_wrapper/moz.build new file mode 100644 index 00000000000..372c5b4cbba --- /dev/null +++ b/toolkit/crashreporter/breakpad_wrapper/moz.build @@ -0,0 +1,37 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +Library("breakpad_wrapper") + +if CONFIG["MOZ_PHC"]: + DEFINES["MOZ_PHC"] = True + +SOURCES = [ + "breakpad_wrapper.cpp", +] + +LOCAL_INCLUDES += [ + "../breakpad-client/", + "../google-breakpad/src/", +] + +if CONFIG["OS_ARCH"] == "WINNT": + USE_LIBS += [ + "google_breakpad_libxul_s", + ] + + OS_LIBS += [ + "advapi32", + "bcrypt", + "dbghelp", + "ntdll", + "userenv", + "ws2_32", + ] +else: + USE_LIBS += [ + "breakpad_client", + ] diff --git a/toolkit/crashreporter/crash_annotations.rs.in b/toolkit/crashreporter/crash_annotations.rs.in new file mode 100644 index 00000000000..caac4650a57 --- /dev/null +++ b/toolkit/crashreporter/crash_annotations.rs.in @@ -0,0 +1,81 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use num_derive::FromPrimitive; + +// Enumeration representing all crash annotations +#[allow(dead_code, non_camel_case_types, clippy::upper_case_acronyms)] +#[repr(u32)] +#[derive(Clone, Copy, Debug, FromPrimitive, PartialEq)] +pub(crate) enum CrashAnnotation { +${enum} +} + +impl std::fmt::Display for CrashAnnotation { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", CRASH_ANNOTATION_STRINGS[*self as usize]) + } +} + +// Type of each annotation +#[allow(dead_code)] +#[derive(Clone, Copy, PartialEq)] +pub(crate) enum CrashAnnotationType { + String = 0, // Any type of string, const char*, nsCString, etc... + Boolean = 1, // Stored as a byte + U32 = 2, // C/C++'s uint32_t or Rust's u32 + U64 = 3, // C/C++'s uint64_t or Rust's u64 + USize = 4, // C/C++'s size_t or Rust's usize + Object = 5, // Not usable via the Rust API +} + +// Type of each annotation +static CRASH_ANNOTATION_TYPES: &[CrashAnnotationType] = &[ +${types} +]; + +// Stringified representation of each annotation. Most of these will just match +// the corresponding enum values, but for historical reasons some of them are +// different in string form when stored in the .extra file. +static CRASH_ANNOTATION_STRINGS: &[&str] = &[ +${names} +]; + +// Annotations which should be skipped when they have specific values +struct CrashAnnotationSkipValue { + annotation: CrashAnnotation, + value: &'static [u8], +} + +static CRASH_ANNOTATIONS_SKIP_VALUES: &[CrashAnnotationSkipValue] = &[ +${skiplist} +]; + + +/// Returns the type of a crash annotation. +/// +/// # Arguments +/// +/// * `annotation` - a crash annotation +pub(crate) fn type_of_annotation(annotation: CrashAnnotation) -> CrashAnnotationType { + CRASH_ANNOTATION_TYPES[annotation as usize] +} + +/// Checks if the annotation should be included. Some annotations are skipped +/// if their value matches a specific one (like the value 0). +/// +/// # Arguments +/// +/// * `annotation` - the crash annotation to be checked +/// * `value` - the contents of the annotation as a string +pub(crate) fn should_include_annotation(annotation: CrashAnnotation, value: &[u8]) -> bool { + if let Some(skip_value) = CRASH_ANNOTATIONS_SKIP_VALUES + .iter() + .find(|&a| a.annotation == annotation) + { + skip_value.value != value + } else { + true + } +} diff --git a/toolkit/crashreporter/crash_helper/crashhelper.cpp b/toolkit/crashreporter/crash_helper/crashhelper.cpp new file mode 100644 index 00000000000..88f635a9b65 --- /dev/null +++ b/toolkit/crashreporter/crash_helper/crashhelper.cpp @@ -0,0 +1,67 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include + +#if defined(XP_WIN) +# include // for HANDLE +#endif // defined(XP_WIN) + +#if defined(XP_LINUX) +// For DirectAuxvDumpInfo +# include "mozilla/toolkit/crashreporter/rust_minidump_writer_linux_ffi_generated.h" +#endif // defined(XP_LINUX) +#include "mozilla/crash_helper_ffi_generated.h" + +static int parse_int_or_exit(const char* aArg) { + errno = 0; + long value = strtol(aArg, nullptr, 10); + + if ((errno != 0) || (value < 0) || (value > INT_MAX)) { + exit(EXIT_FAILURE); + } + + return static_cast(value); +} + +static BreakpadRawData parse_breakpad_data(const char* aArg) { +#if defined(XP_MACOSX) + return aArg; +#elif defined(XP_WIN) + // This is always an ASCII string so we don't need a proper conversion. + size_t len = strlen(aArg); + uint16_t* data = new uint16_t[len + 1]; + for (size_t i = 0; i < len; i++) { + data[i] = aArg[i]; + } + data[len] = 0; + + return data; +#else // Linux and friends + return parse_int_or_exit(aArg); +#endif +} + +static void free_breakpad_data(BreakpadRawData aData) { +#if defined(XP_WIN) + delete aData; +#endif +} + +int main(int argc, char* argv[]) { + if (argc < 6) { + exit(EXIT_FAILURE); + } + + Pid client_pid = static_cast(parse_int_or_exit(argv[1])); + BreakpadRawData breakpad_data = parse_breakpad_data(argv[2]); + char* minidump_path = argv[3]; + char* listener = argv[4]; + char* connector = argv[5]; + + int res = crash_generator_logic_desktop(client_pid, breakpad_data, + minidump_path, listener, connector); + free_breakpad_data(breakpad_data); + exit(res); +} diff --git a/toolkit/crashreporter/crash_helper/crashhelper_android.cpp b/toolkit/crashreporter/crash_helper/crashhelper_android.cpp new file mode 100644 index 00000000000..e0e79f60c2c --- /dev/null +++ b/toolkit/crashreporter/crash_helper/crashhelper_android.cpp @@ -0,0 +1,51 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include + +#include +#include +#include + +// For DirectAuxvDumpInfo +#include "mozilla/toolkit/crashreporter/rust_minidump_writer_linux_ffi_generated.h" +#include "mozilla/crash_helper_ffi_generated.h" + +#define CRASH_HELPER_LOGTAG "GeckoCrashHelper" + +extern "C" JNIEXPORT void JNICALL +Java_org_mozilla_gecko_crashhelper_CrashHelper_crash_1generator( + JNIEnv* jenv, jclass, jint client_pid, jint breakpad_fd, + jstring minidump_path, jint listen_fd, jint server_fd) { + // Enable passing credentials on the server socket and set it in non-blocking + // mode. We'd love to do it inside CrashHelper.java but the Java methods + // require an Android API version that's too recent for us. + const int val = 1; + int res = setsockopt(breakpad_fd, SOL_SOCKET, SO_PASSCRED, &val, sizeof(val)); + if (res < 0) { + __android_log_print(ANDROID_LOG_FATAL, CRASH_HELPER_LOGTAG, + "Unable to set the Breakpad pipe socket options"); + return; + } + + int flags = fcntl(breakpad_fd, F_GETFL); + if (flags == -1) { + __android_log_print(ANDROID_LOG_FATAL, CRASH_HELPER_LOGTAG, + "Unable to get the Breakpad pipe file options"); + return; + } + + res = fcntl(breakpad_fd, F_SETFL, flags | O_NONBLOCK); + if (res == -1) { + __android_log_print(ANDROID_LOG_FATAL, CRASH_HELPER_LOGTAG, + "Unable to set the Breakpad pipe in non-blocking mode"); + return; + } + + const char* minidump_path_str = + jenv->GetStringUTFChars(minidump_path, nullptr); + crash_generator_logic_android(client_pid, breakpad_fd, minidump_path_str, + listen_fd, server_fd); + jenv->ReleaseStringUTFChars(minidump_path, minidump_path_str); +} diff --git a/toolkit/crashreporter/crash_helper/moz.build b/toolkit/crashreporter/crash_helper/moz.build new file mode 100644 index 00000000000..d5af66b076f --- /dev/null +++ b/toolkit/crashreporter/crash_helper/moz.build @@ -0,0 +1,42 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +LOCAL_INCLUDES = [ + "/toolkit/crashreporter", +] + +USE_LIBS += [ + "breakpad_wrapper", + "crash_helper_server", +] + +if CONFIG["OS_TARGET"] == "Android": + UNIFIED_SOURCES = [ + "../bionic_missing_funcs.cpp", + "crashhelper_android.cpp", + ] + + GeckoSharedLibrary("crashhelper", linkage=None) +else: + if CONFIG["OS_ARCH"] == "Darwin": + OS_LIBS += [ + "-framework Foundation", + ] + elif CONFIG["OS_ARCH"] == "WINNT": + OS_LIBS += [ + "advapi32", + "bcrypt", + "dbghelp", + "ntdll", + "userenv", + "ws2_32", + ] + + UNIFIED_SOURCES += [ + "crashhelper.cpp", + ] + + GeckoProgram("crashhelper", linkage=None) diff --git a/toolkit/crashreporter/crash_helper_client/Cargo.toml b/toolkit/crashreporter/crash_helper_client/Cargo.toml new file mode 100644 index 00000000000..71d46cc6115 --- /dev/null +++ b/toolkit/crashreporter/crash_helper_client/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "crash_helper_client" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1" +crash_helper_common = { path = "../crash_helper_common" } +num-derive = "0.4" +num-traits = "0.2" + +[target."cfg(any(target_os = \"android\", target_os = \"linux\", target_os = \"macos\"))".dependencies] +nix = { version = "0.29", features = ["process"] } + +[target."cfg(any(target_os = \"android\", target_os = \"linux\"))".dependencies] +minidump-writer = "0.10" +rust_minidump_writer_linux = { path = "../rust_minidump_writer_linux" } + +[target."cfg(target_os = \"windows\")".dependencies] +windows-sys = { version = "0.52" } # All features inherited from crash_helper_common diff --git a/toolkit/crashreporter/mozannotation_server/cbindgen.toml b/toolkit/crashreporter/crash_helper_client/cbindgen.toml similarity index 61% rename from toolkit/crashreporter/mozannotation_server/cbindgen.toml rename to toolkit/crashreporter/crash_helper_client/cbindgen.toml index 53291962627..49839c0813d 100644 --- a/toolkit/crashreporter/mozannotation_server/cbindgen.toml +++ b/toolkit/crashreporter/crash_helper_client/cbindgen.toml @@ -8,8 +8,14 @@ braces = "SameLine" line_length = 100 tab_width = 2 language = "C++" -include_guard = "mozannotation_server_ffi_generated_h" -includes = ["nsString.h", "nsTArrayForwardDeclare.h"] +include_guard = "crash_helper_client_ffi_generated_h" -[export.rename] -"ThinVec" = "nsTArray" +[defines] +"target_os = android" = "MOZ_WIDGET_ANDROID" +"target_os = linux" = "XP_LINUX" +"target_os = macos" = "XP_MACOSX" +"target_os = windows" = "XP_WIN" + +[parse] +parse_deps = true +include = ["crash_helper_common"] diff --git a/toolkit/crashreporter/mozannotation_server/moz.build b/toolkit/crashreporter/crash_helper_client/moz.build similarity index 58% rename from toolkit/crashreporter/mozannotation_server/moz.build rename to toolkit/crashreporter/crash_helper_client/moz.build index 21762b95b19..4eb427592e7 100644 --- a/toolkit/crashreporter/mozannotation_server/moz.build +++ b/toolkit/crashreporter/crash_helper_client/moz.build @@ -7,11 +7,11 @@ if CONFIG["COMPILE_ENVIRONMENT"]: # This tells mach to run cbindgen and that this header-file should be created CbindgenHeader( - "mozannotation_server_ffi_generated.h", - inputs=["/toolkit/crashreporter/mozannotation_server"], + "crash_helper_client_ffi_generated.h", + inputs=["/toolkit/crashreporter/crash_helper_client"], ) - # This tells mach to copy that generated file to obj/dist/include/mozilla/toolkit/crashreporter - EXPORTS.mozilla.toolkit.crashreporter += [ - "!mozannotation_server_ffi_generated.h", + # This tells mach to copy that generated file to obj/dist/mozilla + EXPORTS.mozilla += [ + "!crash_helper_client_ffi_generated.h", ] diff --git a/toolkit/crashreporter/crash_helper_client/src/lib.rs b/toolkit/crashreporter/crash_helper_client/src/lib.rs new file mode 100644 index 00000000000..c8cb63461d7 --- /dev/null +++ b/toolkit/crashreporter/crash_helper_client/src/lib.rs @@ -0,0 +1,317 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use anyhow::{bail, Result}; +use crash_helper_common::{ + messages::{self}, + BreakpadString, IPCConnector, +}; +#[cfg(any(target_os = "android", target_os = "linux"))] +use minidump_writer::minidump_writer::{AuxvType, DirectAuxvDumpInfo}; +#[cfg(target_os = "android")] +use std::os::fd::RawFd; +use std::{ + ffi::{c_char, CString, OsString}, + ptr::null_mut, +}; +#[cfg(target_os = "windows")] +use windows_sys::Win32::System::Diagnostics::Debug::{CONTEXT, EXCEPTION_RECORD}; + +extern crate num_traits; + +pub use crash_helper_common::{BreakpadChar, BreakpadRawData, Pid}; + +mod platform; + +pub struct CrashHelperClient { + connector: IPCConnector, + #[cfg(target_os = "linux")] + pid: Pid, +} + +impl CrashHelperClient { + fn set_crash_report_path(&mut self, path: OsString) -> Result<()> { + let message = messages::SetCrashReportPath::new(path); + self.connector.send_message(&message)?; + Ok(()) + } + + #[cfg(any(target_os = "android", target_os = "linux"))] + fn register_auxv_info(&mut self, pid: Pid, auxv_info: DirectAuxvDumpInfo) -> Result<()> { + let message = messages::RegisterAuxvInfo::new(pid, auxv_info); + self.connector.send_message(&message)?; + Ok(()) + } + + #[cfg(any(target_os = "android", target_os = "linux"))] + fn unregister_auxv_info(&mut self, pid: Pid) -> Result<()> { + let message = messages::UnregisterAuxvInfo::new(pid); + self.connector.send_message(&message)?; + Ok(()) + } + + fn transfer_crash_report(&mut self, pid: Pid) -> Result { + let message = messages::TransferMinidump::new(pid); + self.connector.send_message(&message)?; + + // HACK: Workaround for a macOS-specific bug + #[cfg(target_os = "macos")] + self.connector.poll(nix::poll::PollFlags::POLLIN)?; + + let reply = self + .connector + .recv_reply::()?; + + if reply.path.is_empty() { + // TODO: We should return Result> instead of + // this. Semantics would be better once we interact with Rust + bail!("Minidump for pid {pid:} was not found"); + } + + Ok(CrashReport { + path: reply.path.into_raw(), + error: reply.error.map_or(null_mut(), |error| error.into_raw()), + }) + } +} + +/// Launch the crash helper process, initialize it and connect to it. Returns +/// a pointer to the client connection or `null` upon failure. +/// +/// # Safety +/// +/// The `helper_name` and `minidump_path` arguments must point to byte or wide +/// strings where appropriate. The `breakpad_raw_data` argument must either +/// point to a string or must contain a valid file descriptor create via +/// `CrashGenerationServer::CreateReportChannel()` depending on the platform. +#[cfg(not(target_os = "android"))] +#[no_mangle] +pub unsafe extern "C" fn crash_helper_launch( + helper_name: *const BreakpadChar, + breakpad_raw_data: BreakpadRawData, + minidump_path: *const BreakpadChar, +) -> *mut CrashHelperClient { + use crash_helper_common::BreakpadData; + + let breakpad_data = BreakpadData::new(breakpad_raw_data); + + if let Ok(crash_helper) = CrashHelperClient::new(helper_name, breakpad_data, minidump_path) { + let crash_helper_box = Box::new(crash_helper); + + // The object will be owned by the C++ code from now on, until it is + // passed back in `crash_helper_shutdown`. + Box::into_raw(crash_helper_box) + } else { + null_mut() + } +} + +/// Connect to an already launching crash helper process. This is only available +/// on Android where the crash helper is a service. Returns a pointer to the +/// client connection or `null` upon failure. +/// +/// # Safety +/// +/// The `minidump_path` argument must point to a valid nul-terminated C string. +/// The `breakpad_raw_data` and `client_socket` arguments must be valid file +/// descriptors used to connect with Breakpad's crash generator and the crash +/// helper respectively.. +#[cfg(target_os = "android")] +#[no_mangle] +pub unsafe extern "C" fn crash_helper_connect(client_socket: RawFd) -> *mut CrashHelperClient { + if let Ok(crash_helper) = CrashHelperClient::new(client_socket) { + let crash_helper_box = Box::new(crash_helper); + + // The object will be owned by the C++ code from now on, until it is + // passed back in `crash_helper_shutdown`. + Box::into_raw(crash_helper_box) + } else { + null_mut() + } +} + +/// Shutdown the crash helper and dispose of the client object. +/// +/// # Safety +/// +/// The `client` parameter must be a valid pointer to the crash helper client +/// object returned by the [`crash_helper_launch()`] or +/// [`crash_helper_connect()`] functions. +#[no_mangle] +pub unsafe extern "C" fn crash_helper_shutdown(client: *mut CrashHelperClient) { + // The CrashHelperClient object will be automatically destroyed when the + // contents of this box are automatically dropped at the end of the function + let _crash_helper_box = Box::from_raw(client); +} + +/// Return the pid of the crash helper process. +/// +/// # Safety +/// +/// The `client` parameter must be a valid pointer to the crash helper client +/// object returned by the [`crash_helper_launch()`] or +/// [`crash_helper_connect()`] functions. +#[cfg(target_os = "linux")] +#[no_mangle] +pub unsafe extern "C" fn crash_helper_pid(client: *const CrashHelperClient) -> Pid { + (*client).pid +} + +#[repr(C)] +pub struct CrashReport { + path: *mut BreakpadChar, + error: *mut c_char, +} + +/// Changes the path where crash reports are generated. +/// +/// # Safety +/// +/// The `client` parameter must be a valid pointer to the crash helper client +/// object returned by the [`crash_helper_launch()`] or +/// [`crash_helper_connect()`] functions. +#[no_mangle] +pub unsafe extern "C" fn set_crash_report_path( + client: *mut CrashHelperClient, + path: *const BreakpadChar, +) -> bool { + let client = client.as_mut().unwrap(); + let path = ::from_ptr(path); + client.set_crash_report_path(path).is_ok() +} + +/// Request the crash report generated for the process associated with `pid`. +/// If the crash report is found an object holding a pointer to the minidump +/// and a potential error message will be returned. Otherwise the function will +/// return `null`. +/// +/// # Safety +/// +/// The `client` parameter must be a valid pointer to the crash helper client +/// object returned by the [`crash_helper_launch()`] or +/// [`crash_helper_connect()`] functions. +#[no_mangle] +pub unsafe extern "C" fn transfer_crash_report( + client: *mut CrashHelperClient, + pid: Pid, +) -> *mut CrashReport { + let client = client.as_mut().unwrap(); + if let Ok(crash_report) = client.transfer_crash_report(pid) { + // The object will be owned by the C++ code from now on, until it is + // passed back in `release_crash_report`. + Box::into_raw(Box::new(crash_report)) + } else { + null_mut() + } +} + +/// Release an object obtained via [`transfer_crash_report()`] +/// +/// # Safety +/// +/// The `crash_report` argument must be a pointer returned by the +/// [`transfer_crash_report()`] or [`crash_helper_connect()`] functions. +#[no_mangle] +pub unsafe extern "C" fn release_crash_report(crash_report: *mut CrashReport) { + let crash_report = Box::from_raw(crash_report); + + // Release the strings, we just get back ownership of the raw objects and let the drop logic get rid of them + let _path = ::from_raw(crash_report.path); + + if !crash_report.error.is_null() { + let _error = CString::from_raw(crash_report.error); + } +} + +/// Report an exception to the crash manager that has been captured outside of +/// Firefox processes, typically within the Windows Error Reporting runtime +/// exception module. +/// +/// # Safety +/// +/// The `exception_record_ptr` and `context_ptr` parameters must point to valid +/// objects of the corresponding types. +#[cfg(target_os = "windows")] +pub unsafe fn report_external_exception( + main_process_pid: Pid, + pid: Pid, + thread: Pid, // TODO: This should be a different type, but it's the same on Windows + exception_record_ptr: *mut EXCEPTION_RECORD, + context_ptr: *mut CONTEXT, +) { + let exception_records = collect_exception_records(exception_record_ptr); + let context = unsafe { context_ptr.read() }; + let message = + messages::WindowsErrorReportingMinidump::new(pid, thread, exception_records, context); + + // In the code below we connect to the crash helper, send our message and wait for a reply before returning, but we ignore errors because we can't do anything about them in the calling code + if let Ok(connector) = IPCConnector::connect(main_process_pid) { + let _ = connector + .send_message(&message) + .and_then(|_| connector.recv_reply::()); + } +} + +// Collect a linked-list of exception records and turn it into a vector +#[cfg(target_os = "windows")] +fn collect_exception_records( + mut exception_record_ptr: *mut EXCEPTION_RECORD, +) -> Vec { + let mut exception_records = Vec::::with_capacity(1); + loop { + if exception_record_ptr.is_null() { + return exception_records; + } + + let mut exception_record = unsafe { exception_record_ptr.read() }; + exception_record_ptr = exception_record.ExceptionRecord; + exception_record.ExceptionRecord = null_mut(); + exception_records.push(exception_record); + } +} + +/// Send the auxiliary vector information for the process identified by `pid` +/// to the crash helper. +/// +/// # Safety +/// +/// The `client` parameter must be a valid pointer to the crash helper client +/// object returned by the [`crash_helper_launch()`] or +/// [`crash_helper_connect()`] functions. The `auxv_info` pointer must be +/// non-null and point to a properly populated `DirectAuxvDumpInfo` structure. +#[cfg(any(target_os = "android", target_os = "linux"))] +#[no_mangle] +pub unsafe extern "C" fn register_child_auxv_info( + client: *mut CrashHelperClient, + pid: Pid, + auxv_info_ptr: *const rust_minidump_writer_linux::DirectAuxvDumpInfo, +) -> bool { + let client = client.as_mut().unwrap(); + let auxv_info = DirectAuxvDumpInfo { + program_header_count: (*auxv_info_ptr).program_header_count as AuxvType, + program_header_address: (*auxv_info_ptr).program_header_address as AuxvType, + linux_gate_address: (*auxv_info_ptr).linux_gate_address as AuxvType, + entry_address: (*auxv_info_ptr).entry_address as AuxvType, + }; + + client.register_auxv_info(pid, auxv_info).is_ok() +} + +/// Deregister previously sent auxiliary vector information for the process +/// identified by `pid`. +/// +/// # Safety +/// +/// The `client` parameter must be a valid pointer to the crash helper client +/// object returned by the [`crash_helper_launch()`] or +/// [`crash_helper_connect()`] functions. +#[cfg(any(target_os = "android", target_os = "linux"))] +#[no_mangle] +pub unsafe extern "C" fn unregister_child_auxv_info( + client: *mut CrashHelperClient, + pid: Pid, +) -> bool { + let client = client.as_mut().unwrap(); + client.unregister_auxv_info(pid).is_ok() +} diff --git a/toolkit/crashreporter/crash_helper_client/src/platform.rs b/toolkit/crashreporter/crash_helper_client/src/platform.rs new file mode 100644 index 00000000000..a5f387457e3 --- /dev/null +++ b/toolkit/crashreporter/crash_helper_client/src/platform.rs @@ -0,0 +1,12 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#[cfg(target_os = "windows")] +pub(crate) mod windows; + +#[cfg(any(target_os = "linux", target_os = "macos"))] +pub(crate) mod unix; + +#[cfg(any(target_os = "android"))] +pub(crate) mod android; diff --git a/toolkit/crashreporter/crash_helper_client/src/platform/android.rs b/toolkit/crashreporter/crash_helper_client/src/platform/android.rs new file mode 100644 index 00000000000..65f003f5704 --- /dev/null +++ b/toolkit/crashreporter/crash_helper_client/src/platform/android.rs @@ -0,0 +1,19 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use anyhow::Result; +use crash_helper_common::IPCConnector; +use std::os::fd::{FromRawFd, OwnedFd, RawFd}; + +use crate::CrashHelperClient; + +impl CrashHelperClient { + pub(crate) fn new(server_socket: RawFd) -> Result { + // SAFETY: The `server_socket` passed in from the application is valid + let server_socket = unsafe { OwnedFd::from_raw_fd(server_socket) }; + let connector = IPCConnector::from_fd(server_socket)?; + + Ok(CrashHelperClient { connector }) + } +} diff --git a/toolkit/crashreporter/crash_helper_client/src/platform/unix.rs b/toolkit/crashreporter/crash_helper_client/src/platform/unix.rs new file mode 100644 index 00000000000..d40723fa890 --- /dev/null +++ b/toolkit/crashreporter/crash_helper_client/src/platform/unix.rs @@ -0,0 +1,74 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use anyhow::Result; +use crash_helper_common::{BreakpadChar, BreakpadData, IPCChannel, IPCConnector, IPCListener}; +use nix::unistd::{execv, fork, getpid, ForkResult}; +use std::ffi::{CStr, CString}; + +use crate::CrashHelperClient; + +impl CrashHelperClient { + pub(crate) fn new( + program: *const BreakpadChar, + breakpad_data: BreakpadData, + minidump_path: *const BreakpadChar, + ) -> Result { + let channel = IPCChannel::new()?; + let (listener, server_endpoint, client_endpoint) = channel.deconstruct(); + let _pid = CrashHelperClient::spawn_crash_helper( + program, + breakpad_data, + minidump_path, + listener, + server_endpoint, + )?; + + Ok(CrashHelperClient { + connector: client_endpoint, + #[cfg(target_os = "linux")] + pid: _pid, + }) + } + + fn spawn_crash_helper( + program: *const BreakpadChar, + breakpad_data: BreakpadData, + minidump_path: *const BreakpadChar, + listener: IPCListener, + endpoint: IPCConnector, + ) -> Result { + let parent_pid = getpid().to_string(); + let parent_pid_arg = unsafe { CString::from_vec_unchecked(parent_pid.into_bytes()) }; + let pid = unsafe { fork() }?; + + // TODO: daemonize the helper by double fork()'ing and waiting on the child + match pid { + ForkResult::Child => { + let program = unsafe { CStr::from_ptr(program) }; + let breakpad_data_arg = + unsafe { CString::from_vec_unchecked(breakpad_data.to_string().into_bytes()) }; + let minidump_path = unsafe { CStr::from_ptr(minidump_path) }; + let listener_arg = listener.serialize(); + let endpoint_arg = endpoint.serialize(); + + let _ = execv( + program, + &[ + program, + &parent_pid_arg, + &breakpad_data_arg, + minidump_path, + &listener_arg, + &endpoint_arg, + ], + ); + + // This point should be unreachable, but let's play it safe + unsafe { nix::libc::_exit(1) }; + } + ForkResult::Parent { child } => Ok(child.as_raw()), + } + } +} diff --git a/toolkit/crashreporter/crash_helper_client/src/platform/windows.rs b/toolkit/crashreporter/crash_helper_client/src/platform/windows.rs new file mode 100644 index 00000000000..62a0616f542 --- /dev/null +++ b/toolkit/crashreporter/crash_helper_client/src/platform/windows.rs @@ -0,0 +1,160 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use anyhow::{bail, Result}; +use crash_helper_common::{ + BreakpadChar, BreakpadData, BreakpadString, IPCChannel, IPCConnector, IPCListener, +}; +use std::{ + ffi::{OsStr, OsString}, + mem::{size_of, zeroed}, + os::windows::ffi::{OsStrExt, OsStringExt}, + ptr::{null, null_mut}, +}; +use windows_sys::Win32::{ + Foundation::{CloseHandle, FALSE, TRUE}, + System::Threading::{ + CreateProcessW, GetCurrentProcessId, CREATE_UNICODE_ENVIRONMENT, DETACHED_PROCESS, + PROCESS_INFORMATION, STARTUPINFOW, + }, +}; + +use crate::CrashHelperClient; + +impl CrashHelperClient { + pub(crate) fn new( + program: *const BreakpadChar, + breakpad_data: BreakpadData, + minidump_path: *const BreakpadChar, + ) -> Result { + // SAFETY: `program` points to a valid string passed in by Firefox + let program = unsafe { ::from_ptr(program) }; + // SAFETY: `minidump_path` points to a valid string passed in by Firefox + let minidump_path = unsafe { ::from_ptr(minidump_path) }; + + let channel = IPCChannel::new()?; + let (listener, server_endpoint, client_endpoint) = channel.deconstruct(); + + let _ = std::thread::spawn(move || { + // If this fails we have no way to tell, but the IPC won't work so + // it's fine to ignore the return value. + let _ = CrashHelperClient::spawn_crash_helper( + program, + breakpad_data, + minidump_path, + listener, + server_endpoint, + ); + }); + + Ok(CrashHelperClient { + connector: client_endpoint, + }) + } + + fn spawn_crash_helper( + program: OsString, + breakpad_data: BreakpadData, + minidump_path: OsString, + listener: IPCListener, + endpoint: IPCConnector, + ) -> Result<()> { + // SAFETY: `GetCurrentProcessId()` takes no arguments and should always work + let pid = OsString::from(unsafe { GetCurrentProcessId() }.to_string()); + + let mut cmd_line = escape_cmd_line_arg(&program); + cmd_line.push(" "); + cmd_line.push(escape_cmd_line_arg(&pid)); + cmd_line.push(" "); + cmd_line.push(escape_cmd_line_arg(breakpad_data.as_ref())); + cmd_line.push(" "); + cmd_line.push(escape_cmd_line_arg(&minidump_path)); + cmd_line.push(" "); + cmd_line.push(escape_cmd_line_arg(&listener.serialize())); + cmd_line.push(" "); + cmd_line.push(escape_cmd_line_arg(&endpoint.serialize())); + cmd_line.push("\0"); + let mut cmd_line: Vec = cmd_line.encode_wide().collect(); + + let mut pi = unsafe { zeroed::() }; + let si = STARTUPINFOW { + cb: size_of::().try_into().unwrap(), + ..unsafe { zeroed() } + }; + + let res = unsafe { + CreateProcessW( + /* lpApplicationName */ null(), + cmd_line.as_mut_ptr(), + /* lpProcessAttributes */ null_mut(), + /* lpThreadAttributes */ null_mut(), + /* bInheritHandles */ TRUE, + CREATE_UNICODE_ENVIRONMENT | DETACHED_PROCESS, + /* lpEnvironment */ null_mut(), + /* lpCurrentDirectory */ null_mut(), + &si, + &mut pi, + ) + }; + + if res == FALSE { + bail!("Could not create the crash helper process"); + } + + // SAFETY: We just successfully populated the `PROCESS_INFORMATION` + // structure and the `hProcess` field contains a valid handle. + unsafe { CloseHandle(pi.hProcess) }; + Ok(()) + } +} + +/// Escape an argument so that it is suitable for use in the command line +/// parameter of a CreateProcess() call. This involves escaping all inner +/// double-quote characters as well as trailing backslashes. The algorithm +/// used is described in this article: +/// https://learn.microsoft.com/it-it/archive/blogs/twistylittlepassagesallalike/everyone-quotes-command-line-arguments-the-wrong-way +fn escape_cmd_line_arg(arg: &OsStr) -> OsString { + const DOUBLE_QUOTES: u16 = '"' as u16; + const BACKSLASH: u16 = '\\' as u16; + + let encoded_arg: Vec = arg.encode_wide().collect(); + let mut escaped_arg = Vec::::new(); + escaped_arg.push(DOUBLE_QUOTES as u16); + + let mut it = encoded_arg.iter().peekable(); + loop { + let mut backslash_num = 0; + + while let Some(&&_c @ BACKSLASH) = it.peek() { + it.next(); + backslash_num += 1; + } + + match it.peek() { + None => { + for _ in 0..backslash_num { + escaped_arg.extend([BACKSLASH, BACKSLASH]); + } + break; + } + Some(&&_c @ DOUBLE_QUOTES) => { + for _ in 0..backslash_num { + escaped_arg.extend([BACKSLASH, BACKSLASH]); + } + escaped_arg.extend([BACKSLASH, DOUBLE_QUOTES]); + } + Some(&&c) => { + for _ in 0..backslash_num { + escaped_arg.push(BACKSLASH); + } + escaped_arg.push(c) + } + } + + it.next(); + } + + escaped_arg.push(DOUBLE_QUOTES); + OsString::from_wide(&escaped_arg) +} diff --git a/toolkit/crashreporter/crash_helper_common/Cargo.toml b/toolkit/crashreporter/crash_helper_common/Cargo.toml new file mode 100644 index 00000000000..47220478660 --- /dev/null +++ b/toolkit/crashreporter/crash_helper_common/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "crash_helper_common" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +num-derive = "0.4" +num-traits = "0.2" +thiserror = "2" + +[target."cfg(any(target_os = \"android\", target_os = \"linux\", target_os = \"macos\"))".dependencies] +nix = { version = "0.29", features = ["fs", "poll", "socket", "uio"] } + +[target."cfg(any(target_os = \"android\", target_os = \"linux\"))".dependencies] +minidump-writer = "0.10" + +[target."cfg(target_os = \"windows\")".dependencies] +windows-sys = { version = "0.52", features = [ + "Win32_Foundation", + "Win32_Security", + "Win32_Storage_FileSystem", + "Win32_System_Diagnostics_Debug", + "Win32_System_IO", + "Win32_System_Kernel", + "Win32_System_Pipes", + "Win32_System_SystemServices", + "Win32_System_Threading", +] } diff --git a/toolkit/crashreporter/crash_helper_common/src/breakpad.rs b/toolkit/crashreporter/crash_helper_common/src/breakpad.rs new file mode 100644 index 00000000000..17c96145dc1 --- /dev/null +++ b/toolkit/crashreporter/crash_helper_common/src/breakpad.rs @@ -0,0 +1,23 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#[cfg(target_os = "windows")] +pub use windows::{AncillaryData, BreakpadChar, BreakpadData, BreakpadRawData, Pid}; +#[cfg(target_os = "windows")] +pub(crate) mod windows; + +#[cfg(any(target_os = "android", target_os = "linux"))] +pub use linux::{AncillaryData, BreakpadChar, BreakpadData, BreakpadRawData, Pid}; +#[cfg(any(target_os = "android", target_os = "linux"))] +pub(crate) mod linux; + +#[cfg(target_os = "macos")] +pub use macos::{AncillaryData, BreakpadChar, BreakpadData, BreakpadRawData, Pid}; +#[cfg(target_os = "macos")] +pub(crate) mod macos; + +#[cfg(any(target_os = "android", target_os = "linux", target_os = "macos"))] +mod unix_strings; +#[cfg(target_os = "windows")] +mod windows_strings; diff --git a/toolkit/crashreporter/crash_helper_common/src/breakpad/linux.rs b/toolkit/crashreporter/crash_helper_common/src/breakpad/linux.rs new file mode 100644 index 00000000000..59ee57b36b1 --- /dev/null +++ b/toolkit/crashreporter/crash_helper_common/src/breakpad/linux.rs @@ -0,0 +1,35 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +// On Linux breakpad uses a pipe to communicate with the exception handler, so +// we need to pass a file descriptor between the client and the crash generator +// and regular C strings for paths and names. + +use nix::libc::pid_t; +use std::{ffi::c_char, fmt, os::fd::RawFd}; + +pub type Pid = pid_t; +pub type BreakpadChar = c_char; +pub type AncillaryData = RawFd; +pub type BreakpadRawData = RawFd; + +pub struct BreakpadData { + data: RawFd, +} + +impl BreakpadData { + pub fn new(raw: BreakpadRawData) -> BreakpadData { + BreakpadData { data: raw } + } + + pub fn into_raw(self) -> BreakpadRawData { + self.data + } +} + +impl fmt::Display for BreakpadData { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.data) + } +} diff --git a/toolkit/crashreporter/crash_helper_common/src/breakpad/macos.rs b/toolkit/crashreporter/crash_helper_common/src/breakpad/macos.rs new file mode 100644 index 00000000000..80ad74d00bb --- /dev/null +++ b/toolkit/crashreporter/crash_helper_common/src/breakpad/macos.rs @@ -0,0 +1,48 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +// On macOS breakpad uses a mach port to communicate with the exception +// handler and passes around its name as a string, additionally the crash +// generator uses regular C strings for paths and names. + +use nix::libc::pid_t; +use std::{ + ffi::{c_char, OsString}, + fmt, +}; + +use crate::BreakpadString; + +pub type Pid = pid_t; +pub type BreakpadChar = c_char; +pub type AncillaryData = (); +pub type BreakpadRawData = *const c_char; + +pub struct BreakpadData { + data: OsString, +} + +impl BreakpadData { + pub unsafe fn new(raw: BreakpadRawData) -> BreakpadData { + BreakpadData { + data: ::from_ptr(raw), + } + } + + pub fn into_raw(self) -> BreakpadRawData { + self.data.into_raw() + } +} + +impl fmt::Display for BreakpadData { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{}", + self.data + .to_str() + .expect("Breakpad data must be a valid UTF-8 string") + ) + } +} diff --git a/toolkit/crashreporter/crash_helper_common/src/breakpad/unix_strings.rs b/toolkit/crashreporter/crash_helper_common/src/breakpad/unix_strings.rs new file mode 100644 index 00000000000..d8c7dcf6a57 --- /dev/null +++ b/toolkit/crashreporter/crash_helper_common/src/breakpad/unix_strings.rs @@ -0,0 +1,63 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use std::{ + alloc::{alloc, dealloc, Layout}, + ffi::{c_char, OsString}, + mem::align_of, + os::unix::ffi::OsStringExt, +}; + +use crate::{errors::MessageError, BreakpadString}; + +use super::BreakpadChar; + +// BreakpadString implementation for regular 8-byte per character strings + +impl BreakpadString for OsString { + fn serialize(&self) -> Vec { + ::clone(self).into_vec() + } + + fn deserialize(bytes: &[u8]) -> Result { + Ok(OsString::from_vec(bytes.to_owned())) + } + + unsafe fn from_ptr(ptr: *const BreakpadChar) -> OsString { + let chars = array_from_c_char_string(ptr); + OsString::from_vec(chars) + } + + fn into_raw(self) -> *mut BreakpadChar { + let chars: Vec = self.into_vec(); + let layout = Layout::from_size_align(chars.len(), align_of::()) + .expect("Impossible layout for raw string"); + unsafe { + let raw_chars = alloc(layout); + chars + .as_ptr() + .copy_to_nonoverlapping(raw_chars, chars.len()); + raw_chars as *mut BreakpadChar + } + } + + unsafe fn from_raw(ptr: *mut BreakpadChar) -> OsString { + let chars = array_from_c_char_string(ptr); + let layout = Layout::from_size_align(chars.len(), align_of::()) + .expect("Impossible layout for raw string"); + dealloc(ptr as *mut u8, layout); + + OsString::from_vec(chars) + } +} + +/// Read a nul-terminated C string pointed to by `ptr` and store its +/// characters into an array, including the trailing nul character. +/// +/// # Safety +/// +/// The `ptr` argument must point to a valid nul-terminated C string. +unsafe fn array_from_c_char_string(ptr: *const c_char) -> Vec { + std::ffi::CStr::from_ptr(ptr).to_bytes_with_nul().to_owned() +} diff --git a/toolkit/crashreporter/crash_helper_common/src/breakpad/windows.rs b/toolkit/crashreporter/crash_helper_common/src/breakpad/windows.rs new file mode 100644 index 00000000000..9d95939696c --- /dev/null +++ b/toolkit/crashreporter/crash_helper_common/src/breakpad/windows.rs @@ -0,0 +1,44 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +// On Windows Breakpad uses a named pipe for communication between the +// exception handlers and the crash generator, and Windows UTF-16 strings for +// name and paths, so all we need to pass between processes are +// 2-bytes-per-character strings. + +use std::ffi::{OsStr, OsString}; + +use crate::BreakpadString; + +pub type Pid = u32; +pub type BreakpadChar = u16; +pub type AncillaryData = (); +pub type BreakpadRawData = *const u16; + +pub struct BreakpadData { + data: OsString, +} + +impl BreakpadData { + /// Create a new instance of the BreakpadData object from a Windows wide-char nul-terminated string + /// + /// # Safety + /// + /// `raw` must point to a valid Windows wide-char nul-terminated string + pub unsafe fn new(raw: BreakpadRawData) -> BreakpadData { + BreakpadData { + data: ::from_ptr(raw), + } + } + + pub fn into_raw(self) -> BreakpadRawData { + self.data.into_raw() + } +} + +impl AsRef for BreakpadData { + fn as_ref(&self) -> &OsStr { + &self.data + } +} diff --git a/toolkit/crashreporter/crash_helper_common/src/breakpad/windows_strings.rs b/toolkit/crashreporter/crash_helper_common/src/breakpad/windows_strings.rs new file mode 100644 index 00000000000..8f3952f01aa --- /dev/null +++ b/toolkit/crashreporter/crash_helper_common/src/breakpad/windows_strings.rs @@ -0,0 +1,86 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use std::{ + alloc::{alloc, dealloc, Layout}, + ffi::OsString, + mem::{align_of, size_of}, + os::windows::ffi::{OsStrExt, OsStringExt}, +}; + +use crate::{errors::MessageError, BreakpadChar, BreakpadString}; + +// BreakpadString trait implementation for Windows native UTF-16 strings +impl BreakpadString for OsString { + fn serialize(&self) -> Vec { + self.encode_wide().flat_map(|c| c.to_ne_bytes()).collect() + } + + fn deserialize(bytes: &[u8]) -> Result { + if (bytes.len() % 2) != 0 { + return Err(MessageError::InvalidData); + } + + let wchars: Vec = bytes + .chunks_exact(2) + .map(|chunk| { + // SAFETY: We're splitting into exact 2 bytes chunks + let chunk: [u8; 2] = unsafe { chunk.try_into().unwrap_unchecked() }; + u16::from_ne_bytes(chunk) + }) + .collect(); + + Ok(OsString::from_wide(&wchars)) + } + + unsafe fn from_ptr(ptr: *const BreakpadChar) -> OsString { + let wchars = array_from_win_string(ptr); + OsString::from_wide(&wchars) + } + + fn into_raw(self) -> *mut BreakpadChar { + let wide_chars: Vec = self.encode_wide().chain(std::iter::once(0)).collect(); + let layout = + Layout::from_size_align((wide_chars.len() + 1) * size_of::(), align_of::()) + .expect("Impossible layout for raw Windows string"); + unsafe { + let raw_chars = alloc(layout) as *mut u16; + wide_chars + .as_ptr() + .copy_to_nonoverlapping(raw_chars, wide_chars.len()); + + raw_chars + } + } + + unsafe fn from_raw(ptr: *mut BreakpadChar) -> OsString { + let wide_chars = array_from_win_string(ptr); + let layout = + Layout::from_size_align((wide_chars.len() + 1) * size_of::(), align_of::()) + .expect("Impossible layout for raw Windows string"); + dealloc(ptr as *mut u8, layout); + + OsString::from_wide(&wide_chars) + } +} + +// Read a Windows wide string pointed to by `ptr` and store its characters into +// an array, excluding the trailing nul character. +fn array_from_win_string(ptr: *const u16) -> Vec { + let mut wide_chars = Vec::::new(); + let mut ptr = ptr; + + loop { + let c = unsafe { ptr.read() }; + + if c == 0 { + break; + } + + wide_chars.push(c); + ptr = unsafe { ptr.offset(1) }; + } + + wide_chars +} diff --git a/toolkit/crashreporter/crash_helper_common/src/errors.rs b/toolkit/crashreporter/crash_helper_common/src/errors.rs new file mode 100644 index 00000000000..e30d473d3fd --- /dev/null +++ b/toolkit/crashreporter/crash_helper_common/src/errors.rs @@ -0,0 +1,56 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::{ + array::TryFromSliceError, + ffi::{FromBytesWithNulError, NulError}, +}; +use thiserror::Error; + +#[cfg(not(target_os = "windows"))] +use nix::errno::Errno as SystemError; +#[cfg(target_os = "windows")] +use windows_sys::Win32::Foundation::WIN32_ERROR as SystemError; + +#[derive(Debug, Error)] +pub enum IPCError { + #[error("Message error")] + BadMessage(#[from] MessageError), + #[error("Generic system error: {0}")] + System(SystemError), + #[error("Could not bind socket to an address, error: {0}")] + BindFailed(SystemError), + #[error("Could not listen on a socket, error: {0}")] + ListenFailed(SystemError), + #[error("Could not accept an incoming connection, error: {0}")] + AcceptFailed(SystemError), + #[error("Could not connect to a socket, error: {0}")] + ConnectionFailure(SystemError), + #[error("Could not send data, error: {0}")] + TransmissionFailure(SystemError), + #[error("Could not receive data, error: {0}")] + ReceptionFailure(SystemError), + #[error("Error while waiting for events, error: {0:?}")] + WaitingFailure(Option), + #[error("Error while parsing a file descriptor string")] + ParseError, +} + +#[derive(Debug, Error)] +pub enum MessageError { + #[error("Truncated message")] + Truncated, + #[error("Message kind is invalid")] + InvalidKind, + #[error("The message contained an invalid payload")] + InvalidData, + #[error("Missing ancillary data")] + MissingAncillary, + #[error("Invalid message size")] + InvalidSize(#[from] TryFromSliceError), + #[error("Missing nul terminator")] + MissingNul(#[from] FromBytesWithNulError), + #[error("Missing nul terminator")] + InteriorNul(#[from] NulError), +} diff --git a/toolkit/crashreporter/crash_helper_common/src/ipc_channel.rs b/toolkit/crashreporter/crash_helper_common/src/ipc_channel.rs new file mode 100644 index 00000000000..946eeab7b28 --- /dev/null +++ b/toolkit/crashreporter/crash_helper_common/src/ipc_channel.rs @@ -0,0 +1,23 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +/***************************************************************************** + * Windows * + *****************************************************************************/ + +#[cfg(target_os = "windows")] +pub use windows::IPCChannel; + +#[cfg(target_os = "windows")] +pub(crate) mod windows; + +/***************************************************************************** + * Android, macOS & Linux * +*****************************************************************************/ + +#[cfg(any(target_os = "android", target_os = "linux", target_os = "macos"))] +pub use unix::IPCChannel; + +#[cfg(any(target_os = "android", target_os = "linux", target_os = "macos"))] +pub(crate) mod unix; diff --git a/toolkit/crashreporter/crash_helper_common/src/ipc_channel/unix.rs b/toolkit/crashreporter/crash_helper_common/src/ipc_channel/unix.rs new file mode 100644 index 00000000000..0859efaa6ab --- /dev/null +++ b/toolkit/crashreporter/crash_helper_common/src/ipc_channel/unix.rs @@ -0,0 +1,45 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use std::process; + +#[cfg(any(target_os = "android", target_os = "linux"))] +use crate::platform::linux::unix_socketpair; +#[cfg(target_os = "macos")] +use crate::platform::macos::unix_socketpair; +use crate::{errors::IPCError, IPCConnector, IPCListener, Pid}; + +pub struct IPCChannel { + listener: IPCListener, + client_endpoint: IPCConnector, + server_endpoint: IPCConnector, +} + +impl IPCChannel { + /// Create a new IPCChannel, this includes a listening endpoint that + /// will use the current process PID as part of its address and two + /// connected endpoints. The listener and the server-side endpoint can be + /// inherited by a child process, the client-side endpoint cannot. + pub fn new() -> Result { + let listener = IPCListener::new(process::id() as Pid)?; + + // Only the server-side socket will be left open after an exec(). + let pair = unix_socketpair().map_err(IPCError::System)?; + let client_endpoint = IPCConnector::from_fd(pair.0)?; + let server_endpoint = IPCConnector::from_fd_inheritable(pair.1)?; + + Ok(IPCChannel { + listener, + client_endpoint, + server_endpoint, + }) + } + + /// Deconstruct the IPC channel, returning the listening endpoint, + /// the connected server-side endpoint and the connected client-side + /// endpoint. + pub fn deconstruct(self) -> (IPCListener, IPCConnector, IPCConnector) { + (self.listener, self.server_endpoint, self.client_endpoint) + } +} diff --git a/toolkit/crashreporter/crash_helper_common/src/ipc_channel/windows.rs b/toolkit/crashreporter/crash_helper_common/src/ipc_channel/windows.rs new file mode 100644 index 00000000000..d54a60585a5 --- /dev/null +++ b/toolkit/crashreporter/crash_helper_common/src/ipc_channel/windows.rs @@ -0,0 +1,38 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use std::process; + +use crate::{errors::IPCError, IPCConnector, IPCListener, Pid}; + +pub struct IPCChannel { + listener: IPCListener, + client_endpoint: IPCConnector, + server_endpoint: IPCConnector, +} + +impl IPCChannel { + /// Create a new IPCChannel, this includes a listening endpoint that + /// will use the current process PID as part of its address and two + /// connected endpoints. + pub fn new() -> Result { + let pid = process::id() as Pid; + let mut listener = IPCListener::new(pid)?; + let client_endpoint = IPCConnector::connect(pid)?; + let server_endpoint = listener.accept()?; + + Ok(IPCChannel { + listener, + client_endpoint, + server_endpoint, + }) + } + + /// Deconstruct the IPC channel, returning the listening endpoint, + /// the connected server-side endpoint and the connected client-side + /// endpoint. + pub fn deconstruct(self) -> (IPCListener, IPCConnector, IPCConnector) { + (self.listener, self.server_endpoint, self.client_endpoint) + } +} diff --git a/toolkit/crashreporter/crash_helper_common/src/ipc_connector.rs b/toolkit/crashreporter/crash_helper_common/src/ipc_connector.rs new file mode 100644 index 00000000000..be5e64323c2 --- /dev/null +++ b/toolkit/crashreporter/crash_helper_common/src/ipc_connector.rs @@ -0,0 +1,31 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::messages::Header; + +pub enum IPCEvent { + Connect(IPCConnector), + Header(usize, Header), + Disconnect(usize), +} + +/***************************************************************************** + * Windows * + *****************************************************************************/ + +#[cfg(target_os = "windows")] +pub use windows::IPCConnector; + +#[cfg(target_os = "windows")] +pub(crate) mod windows; + +/***************************************************************************** + * Android, macOS & Linux * + *****************************************************************************/ + +#[cfg(any(target_os = "android", target_os = "linux", target_os = "macos"))] +pub use unix::IPCConnector; + +#[cfg(any(target_os = "android", target_os = "linux", target_os = "macos"))] +pub(crate) mod unix; diff --git a/toolkit/crashreporter/crash_helper_common/src/ipc_connector/unix.rs b/toolkit/crashreporter/crash_helper_common/src/ipc_connector/unix.rs new file mode 100644 index 00000000000..d8265a1387f --- /dev/null +++ b/toolkit/crashreporter/crash_helper_common/src/ipc_connector/unix.rs @@ -0,0 +1,195 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#[cfg(any(target_os = "android", target_os = "linux"))] +use crate::platform::linux::{ + connected_process_pid, recv_nonblock, send_nonblock, server_addr, set_socket_cloexec, + set_socket_default_flags, unix_socket, +}; +#[cfg(target_os = "macos")] +use crate::platform::macos::{ + connected_process_pid, recv_nonblock, send_nonblock, server_addr, set_socket_cloexec, + set_socket_default_flags, unix_socket, +}; +use crate::{ignore_eintr, AncillaryData, Pid, IO_TIMEOUT}; + +use nix::{ + errno::Errno, + poll::{poll, PollFd, PollFlags, PollTimeout}, + sys::socket::connect, +}; +use std::{ + ffi::{CStr, CString}, + os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd}, + str::FromStr, +}; + +use crate::{ + errors::IPCError, + messages::{self, Message}, +}; + +pub struct IPCConnector { + socket: OwnedFd, + pid: Pid, +} + +impl IPCConnector { + /// Create a new connector from an already connected socket. The + /// `FD_CLOEXEC` flag will be set on the underlying socket and thus it + /// will not be possible to inerhit this connector in a child process. + pub fn from_fd(socket: OwnedFd) -> Result { + let connector = IPCConnector::from_fd_inheritable(socket)?; + set_socket_cloexec(connector.socket.as_fd()).map_err(IPCError::System)?; + Ok(connector) + } + + /// Create a new connector from an already connected socket. The + /// `FD_CLOEXEC` flag will not be set on the underlying socket and thus it + /// will be possible to inherit this connector in a child process. + pub fn from_fd_inheritable(socket: OwnedFd) -> Result { + let pid = connected_process_pid(socket.as_fd()).map_err(IPCError::System)?; + set_socket_default_flags(socket.as_fd()).map_err(IPCError::System)?; + Ok(IPCConnector { socket, pid }) + } + + /// Create a new connector by connecting it to the process specified by + /// `pid`. The `FD_CLOEXEC` flag will be set on the underlying socket and + /// thus it will not be possible to inerhit this connector in a child + /// process. + pub fn connect(pid: Pid) -> Result { + let socket = unix_socket().map_err(IPCError::ConnectionFailure)?; + set_socket_default_flags(socket.as_fd()).map_err(IPCError::ConnectionFailure)?; + set_socket_cloexec(socket.as_fd()).map_err(IPCError::ConnectionFailure)?; + + let server_addr = server_addr(pid).map_err(IPCError::ConnectionFailure)?; + + loop { + let timeout = PollTimeout::from(IO_TIMEOUT); + let pollfd = PollFd::new(socket.as_fd(), PollFlags::POLLOUT); + let res = ignore_eintr!(poll(&mut [pollfd], timeout)); + match res { + Err(e) => return Err(IPCError::ConnectionFailure(e)), + Ok(_res @ 0) => return Err(IPCError::ConnectionFailure(Errno::ETIMEDOUT)), + Ok(_) => {} + } + + let res = ignore_eintr!(connect(socket.as_raw_fd(), &server_addr)); + match res { + Ok(_) => break, + Err(_e @ Errno::EAGAIN) => continue, // Retry, the helper might not be ready yet + Err(e) => return Err(IPCError::ConnectionFailure(e)), + } + } + + let pid = connected_process_pid(socket.as_fd()).map_err(IPCError::ConnectionFailure)?; + + Ok(IPCConnector { socket, pid }) + } + + /// Serialize this connector into a string that can be passed on the + /// command-line to a child process. This only works for newly + /// created connectors because they are explicitly created as inheritable. + pub fn serialize(&self) -> CString { + CString::new(self.socket.as_raw_fd().to_string()).unwrap() + } + + /// Deserialize a connector from an argument passed on the command-line. + pub fn deserialize(string: &CStr) -> Result { + let string = string.to_str().map_err(|_e| IPCError::ParseError)?; + let fd = RawFd::from_str(string).map_err(|_e| IPCError::ParseError)?; + // SAFETY: This is a file descriptor we passed in ourselves. + let socket = unsafe { OwnedFd::from_raw_fd(fd) }; + let pid = connected_process_pid(socket.as_fd()).map_err(IPCError::System)?; + Ok(IPCConnector { socket, pid }) + } + + fn raw_fd(&self) -> RawFd { + self.socket.as_raw_fd() + } + + pub fn as_raw_ref(&self) -> BorrowedFd { + self.socket.as_fd() + } + + pub fn poll(&self, flags: PollFlags) -> Result<(), Errno> { + let timeout = PollTimeout::from(IO_TIMEOUT); + let pollfd = PollFd::new(self.socket.as_fd(), flags); + let res = ignore_eintr!(poll(&mut [pollfd], timeout)); + match res { + Err(e) => Err(e), + Ok(_res @ 0) => Err(Errno::EAGAIN), + Ok(_) => Ok(()), + } + } + + pub fn send_message(&self, message: &dyn Message) -> Result<(), IPCError> { + self.send(&message.header(), None) + .map_err(IPCError::TransmissionFailure)?; + self.send(&message.payload(), message.ancillary_payload()) + .map_err(IPCError::TransmissionFailure) + } + + pub fn recv_reply(&self) -> Result + where + T: Message, + { + let header = self.recv_header()?; + + if header.kind != T::kind() { + return Err(IPCError::ReceptionFailure(Errno::EBADMSG)); + } + + let (data, _) = self.recv(header.size).map_err(IPCError::ReceptionFailure)?; + T::decode(&data, None).map_err(IPCError::from) + } + + fn send_nonblock(&self, buff: &[u8], fd: Option) -> Result<(), Errno> { + send_nonblock(self.raw_fd(), buff, fd) + } + + fn send(&self, buff: &[u8], fd: Option) -> Result<(), Errno> { + let res = self.send_nonblock(buff, fd); + match res { + Err(_code @ Errno::EAGAIN) => { + // If the socket was not ready to send data wait for it to + // become unblocked then retry sending just once. + self.poll(PollFlags::POLLOUT)?; + self.send_nonblock(buff, fd) + } + _ => res, + } + } + + pub fn recv_header(&self) -> Result { + let (header, _) = self + .recv(messages::HEADER_SIZE) + .map_err(IPCError::ReceptionFailure)?; + messages::Header::decode(&header).map_err(IPCError::BadMessage) + } + + fn recv_nonblock( + &self, + expected_size: usize, + ) -> Result<(Vec, Option), Errno> { + recv_nonblock(self.raw_fd(), expected_size) + } + + pub fn recv(&self, expected_size: usize) -> Result<(Vec, Option), Errno> { + let res = self.recv_nonblock(expected_size); + match res { + Err(_code @ Errno::EAGAIN) => { + // If the socket was not ready to receive data wait for it to + // become unblocked then retry receiving just once. + self.poll(PollFlags::POLLIN)?; + self.recv_nonblock(expected_size) + } + _ => res, + } + } + + pub fn endpoint_pid(&self) -> Pid { + self.pid + } +} diff --git a/toolkit/crashreporter/crash_helper_common/src/ipc_connector/windows.rs b/toolkit/crashreporter/crash_helper_common/src/ipc_connector/windows.rs new file mode 100644 index 00000000000..80fdb63b173 --- /dev/null +++ b/toolkit/crashreporter/crash_helper_common/src/ipc_connector/windows.rs @@ -0,0 +1,391 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::{ + errors::{IPCError, MessageError}, + messages::{self, Message, HEADER_SIZE}, + platform::windows::{create_manual_reset_event, reset_event, server_name}, + AncillaryData, Pid, IO_TIMEOUT, +}; + +use std::{ + ffi::{c_void, CStr, OsString}, + mem::zeroed, + os::windows::io::{AsHandle, AsRawHandle, FromRawHandle, OwnedHandle, RawHandle}, + ptr::{addr_of, addr_of_mut, null_mut}, + str::FromStr, + time::{Duration, Instant}, +}; +use windows_sys::Win32::{ + Foundation::{ + GetLastError, BOOL, ERROR_FILE_NOT_FOUND, ERROR_INVALID_MESSAGE, ERROR_IO_PENDING, + ERROR_PIPE_BUSY, FALSE, HANDLE, INVALID_HANDLE_VALUE, WAIT_TIMEOUT, + }, + Security::SECURITY_ATTRIBUTES, + Storage::FileSystem::{ + CreateFileA, ReadFile, WriteFile, FILE_FLAG_OVERLAPPED, FILE_READ_DATA, FILE_SHARE_READ, + FILE_SHARE_WRITE, FILE_WRITE_ATTRIBUTES, FILE_WRITE_DATA, OPEN_EXISTING, + }, + System::{ + Pipes::{ + GetNamedPipeClientProcessId, SetNamedPipeHandleState, WaitNamedPipeA, + PIPE_READMODE_MESSAGE, + }, + IO::{GetOverlappedResult, GetOverlappedResultEx, OVERLAPPED}, + }, +}; + +pub struct IPCPendingRequest { + pub overlapped: OVERLAPPED, + pub header_buffer: [u8; HEADER_SIZE], + pub completed: bool, +} + +impl IPCPendingRequest { + fn new(event: HANDLE) -> IPCPendingRequest { + let overlapped = OVERLAPPED { + hEvent: event, + ..unsafe { zeroed() } + }; + + IPCPendingRequest { + overlapped, + header_buffer: unsafe { zeroed() }, + completed: false, + } + } +} + +pub struct IPCConnector { + handle: OwnedHandle, + event: OwnedHandle, + pending: Option>, + pid: Pid, +} + +impl IPCConnector { + pub fn new(handle: OwnedHandle) -> Result { + let event = create_manual_reset_event()?; + let mut pid: Pid = 0; + // SAFETY: The `pid` pointer is taken from the stack and thus always valid. + let res = + unsafe { GetNamedPipeClientProcessId(handle.as_raw_handle() as HANDLE, &mut pid) }; + if res == FALSE { + return Err(IPCError::System(unsafe { GetLastError() })); + } + + Ok(IPCConnector { + handle, + event, + pending: None, + pid, + }) + } + + pub fn as_raw(&self) -> HANDLE { + self.handle.as_raw_handle() as HANDLE + } + + pub fn event_raw_handle(&self) -> HANDLE { + self.event.as_raw_handle() as HANDLE + } + + pub fn connect(pid: Pid) -> Result { + let server_name = server_name(pid); + let now = Instant::now(); + let timeout = Duration::from_millis(IO_TIMEOUT.into()); + let mut pipe; + loop { + // Connectors must not be inherited + let security_attributes = SECURITY_ATTRIBUTES { + nLength: size_of::() as u32, + lpSecurityDescriptor: null_mut(), + bInheritHandle: FALSE, + }; + + // SAFETY: The `server_name` is guaranteed to be valid, all other + // pointer arguments are null. + pipe = unsafe { + CreateFileA( + server_name.as_ptr(), + FILE_READ_DATA | FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES, + FILE_SHARE_READ | FILE_SHARE_WRITE, + &security_attributes, + OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, + /* hTemplateFile */ 0 as HANDLE, + ) + }; + + if pipe != INVALID_HANDLE_VALUE { + break; + } + + let elapsed = now.elapsed(); + + if elapsed >= timeout { + return Err(IPCError::System(WAIT_TIMEOUT)); // TODO: We need a dedicated error + } + + let error = unsafe { GetLastError() }; + + // The pipe might have not been created yet or it might be busy. + if (error == ERROR_FILE_NOT_FOUND) || (error == ERROR_PIPE_BUSY) { + // SAFETY: The `server_name` pointer is guaranteed to be valid. + let res = unsafe { + WaitNamedPipeA(server_name.as_ptr(), (timeout - elapsed).as_millis() as u32) + }; + let error = unsafe { GetLastError() }; + + // If the pipe hasn't been created yet loop over and try again + if (res == FALSE) && (error != ERROR_FILE_NOT_FOUND) { + return Err(IPCError::System(error)); + } + } else { + return Err(IPCError::System(error)); + } + } + + // Change to message-read mode + let pipe_mode: u32 = PIPE_READMODE_MESSAGE; + // SAFETY: We pass a pointer to a local variable which guarantees it + // is valid, we use null for all the other pointer parameters. + let res = unsafe { + SetNamedPipeHandleState( + pipe, + &pipe_mode, + /* lpMaxCollectionCount */ null_mut(), + /* lpCollectDataTimeout */ null_mut(), + ) + }; + if res == FALSE { + return Err(IPCError::System(unsafe { GetLastError() })); + } + + // SAFETY: The raw pipe handle is guaranteed to be open at this point + let handle = unsafe { OwnedHandle::from_raw_handle(pipe as RawHandle) }; + IPCConnector::new(handle) + } + + /// Serialize this connector into a string that can be passed on the + /// command-line to a child process. This only works for newly + /// created connectors because they are explicitly created as inheritable. + pub fn serialize(&self) -> OsString { + let raw_handle = self.handle.as_raw_handle() as usize; + OsString::from_str(raw_handle.to_string().as_ref()).unwrap() + } + + /// Deserialize a connector from an argument passed on the command-line. + pub fn deserialize(string: &CStr) -> Result { + let string = string.to_str().map_err(|_e| IPCError::ParseError)?; + let handle = usize::from_str(string).map_err(|_e| IPCError::ParseError)?; + let handle = handle as *mut c_void; + // SAFETY: This is a handle we passed in ourselves. + let handle = unsafe { OwnedHandle::from_raw_handle(handle) }; + IPCConnector::new(handle) + } + + pub fn send_message(&self, message: &dyn Message) -> Result<(), IPCError> { + // Send the message header + self.send(&message.header())?; + + // Send the message payload, ancillary payloads are not used on Windows. + debug_assert!( + message.ancillary_payload().is_none(), + "Windows doesn't transfer ancillary data" + ); + self.send(&message.payload())?; + + Ok(()) + } + + pub fn recv_reply(&self) -> Result + where + T: Message, + { + let header = self.recv_header()?; + + if header.kind != T::kind() { + return Err(IPCError::ReceptionFailure(ERROR_INVALID_MESSAGE)); + } + + let (data, _) = self.recv(header.size)?; + T::decode(&data, None).map_err(IPCError::from) + } + + fn recv_header(&self) -> Result { + let (header, _) = self.recv(messages::HEADER_SIZE)?; + messages::Header::decode(&header).map_err(IPCError::BadMessage) + } + + pub fn sched_recv_header(&mut self) -> Result<(), IPCError> { + if self.pending.is_some() { + // We're already waiting for a header. + return Ok(()); + } + + let bytes_to_transfer: u32 = messages::HEADER_SIZE.try_into().unwrap(); + let mut number_of_bytes_transferred: u32 = 0; + + reset_event(self.event.as_handle())?; + let mut pending_request = Box::new(IPCPendingRequest::new(self.event_raw_handle())); + + let res = unsafe { + ReadFile( + self.as_raw(), + addr_of_mut!(pending_request.header_buffer) as *mut _, + bytes_to_transfer, + &mut number_of_bytes_transferred, + addr_of_mut!(pending_request.overlapped), + ) + }; + let error = unsafe { GetLastError() }; + + if res == FALSE { + if error != ERROR_IO_PENDING { + return Err(IPCError::System(error)); + } + } else { + if number_of_bytes_transferred != bytes_to_transfer { + return Err(IPCError::BadMessage(MessageError::InvalidData)); + } + + pending_request.completed = true; + } + + self.pending = Some(pending_request); + + Ok(()) + } + + pub fn collect_header(&mut self) -> Result { + let pending_request = std::mem::take(&mut self.pending) + .expect("No pending request should be present on this IPC connector"); + + if !pending_request.completed { + let mut number_of_bytes_transferred: u32 = 0; + + let res = unsafe { + GetOverlappedResult( + self.as_raw(), + addr_of!(pending_request.overlapped), + &mut number_of_bytes_transferred, + /* bWait */ FALSE, + ) + }; + let error = unsafe { GetLastError() }; + + // TODO: Treat broken pipe errors differently + if (res == FALSE) || (number_of_bytes_transferred != messages::HEADER_SIZE as u32) { + return Err(IPCError::System(error)); + } + } + + messages::Header::decode(&pending_request.header_buffer).map_err(IPCError::BadMessage) + } + + fn check_completion( + &self, + res: BOOL, + overlapped: &OVERLAPPED, + number_of_bytes_transferred: &mut u32, + bytes_to_transfer: usize, + ) -> Result<(), IPCError> { + if res == FALSE { + let error = unsafe { GetLastError() }; + if error == ERROR_IO_PENDING { + let res = unsafe { + GetOverlappedResultEx( + self.as_raw(), + overlapped, + number_of_bytes_transferred, + IO_TIMEOUT as u32, + /* bAlertable */ FALSE, + ) + }; + + let error = unsafe { GetLastError() }; + if res == FALSE { + return Err(IPCError::System(error)); + } + } else { + return Err(IPCError::System(error)); + } + } + + if *number_of_bytes_transferred as usize != bytes_to_transfer { + return Err(IPCError::BadMessage(MessageError::InvalidData)); + } + + Ok(()) + } + + pub fn send(&self, buff: &[u8]) -> Result<(), IPCError> { + let bytes_to_transfer: u32 = buff.len().try_into().unwrap(); // TODO: Make this an error + let mut number_of_bytes_transferred: u32 = 0; + + reset_event(self.event.as_handle())?; + let mut overlapped = OVERLAPPED { + hEvent: self.event_raw_handle(), + ..unsafe { zeroed() } + }; + + let res = unsafe { + WriteFile( + self.as_raw(), + buff.as_ptr(), + bytes_to_transfer, + &mut number_of_bytes_transferred, + addr_of_mut!(overlapped), + ) + }; + + self.check_completion( + res, + &overlapped, + &mut number_of_bytes_transferred, + bytes_to_transfer as usize, + ) + } + + pub fn recv(&self, expected_size: usize) -> Result<(Vec, Option), IPCError> { + let mut buff: Vec = vec![0; expected_size]; + let bytes_to_transfer: u32 = expected_size.try_into().unwrap(); + let mut number_of_bytes_transferred: u32 = 0; + + reset_event(self.event.as_handle())?; + let mut overlapped = OVERLAPPED { + hEvent: self.event_raw_handle(), + ..unsafe { zeroed() } + }; + + let res = unsafe { + ReadFile( + self.as_raw(), + buff.as_mut_ptr(), + bytes_to_transfer, + &mut number_of_bytes_transferred, + addr_of_mut!(overlapped), + ) + }; + + self.check_completion( + res, + &overlapped, + &mut number_of_bytes_transferred, + bytes_to_transfer as usize, + )?; + + Ok((buff, None)) + } + + pub fn endpoint_pid(&self) -> Pid { + self.pid + } +} + +// SAFETY: The connector can be transferred across threads in spite of the +// raw pointer contained in the OVERLAPPED structure because it is only +// used internally and never visible externally. +unsafe impl Send for IPCConnector {} diff --git a/toolkit/crashreporter/crash_helper_common/src/ipc_listener.rs b/toolkit/crashreporter/crash_helper_common/src/ipc_listener.rs new file mode 100644 index 00000000000..ae86e5d930e --- /dev/null +++ b/toolkit/crashreporter/crash_helper_common/src/ipc_listener.rs @@ -0,0 +1,23 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +/***************************************************************************** + * Windows * + *****************************************************************************/ + +#[cfg(target_os = "windows")] +pub use windows::IPCListener; + +#[cfg(target_os = "windows")] +pub(crate) mod windows; + +/***************************************************************************** + * Android, macOS & Linux * + *****************************************************************************/ + +#[cfg(any(target_os = "android", target_os = "linux", target_os = "macos"))] +pub use unix::IPCListener; + +#[cfg(any(target_os = "android", target_os = "linux", target_os = "macos"))] +pub(crate) mod unix; diff --git a/toolkit/crashreporter/crash_helper_common/src/ipc_listener/unix.rs b/toolkit/crashreporter/crash_helper_common/src/ipc_listener/unix.rs new file mode 100644 index 00000000000..3f7bbaac100 --- /dev/null +++ b/toolkit/crashreporter/crash_helper_common/src/ipc_listener/unix.rs @@ -0,0 +1,78 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#[cfg(any(target_os = "android", target_os = "linux"))] +use crate::platform::linux::{ + server_addr, set_socket_cloexec, set_socket_default_flags, unix_socket, +}; +#[cfg(target_os = "macos")] +use crate::platform::macos::{ + server_addr, set_socket_cloexec, set_socket_default_flags, unix_socket, +}; +use crate::{errors::IPCError, IPCConnector, Pid}; + +use nix::sys::socket::{accept, bind, listen, Backlog}; +use std::{ + ffi::{CStr, CString}, + os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd}, + str::FromStr, +}; + +pub struct IPCListener { + socket: OwnedFd, +} + +impl IPCListener { + /// Create a new listener with an address based on `pid`. The underlying + /// socket will not have the `FD_CLOEXEC` flag set and thus can be + /// inherited by child processes. + pub fn new(pid: Pid) -> Result { + let socket = unix_socket().map_err(IPCError::System)?; + set_socket_default_flags(socket.as_fd()).map_err(IPCError::System)?; + + let server_addr = server_addr(pid).map_err(IPCError::System)?; + bind(socket.as_fd().as_raw_fd(), &server_addr).map_err(IPCError::System)?; + listen(&socket, Backlog::new(1).unwrap()).map_err(IPCError::ListenFailed)?; + + Ok(IPCListener { socket }) + } + + /// Create a new listener using an already prepared socket. The listener + /// must have been bound to the appropriate address and should already be + /// listening on incoming connections. This will set the `FD_CLOEXEC` flag + /// on the underlying socket and thus will make this litener not inheritable + /// by child processes. + pub fn from_fd(_pid: Pid, socket: OwnedFd) -> Result { + set_socket_cloexec(socket.as_fd()).map_err(IPCError::System)?; + + Ok(IPCListener { socket }) + } + + /// Serialize this listener into a string that can be passed on the + /// command-line to a child process. This only works for newly + /// created listeners because they are explicitly created as inheritable. + pub fn serialize(&self) -> CString { + CString::new(self.socket.as_raw_fd().to_string()).unwrap() + } + + /// Deserialize a listener from an argument passed on the command-line. + /// The resulting listener is ready to accept new connections. + pub fn deserialize(string: &CStr, _pid: Pid) -> Result { + let string = string.to_str().map_err(|_e| IPCError::ParseError)?; + let fd = RawFd::from_str(string).map_err(|_e| IPCError::ParseError)?; + // SAFETY: This is a file descriptor we passed in ourselves. + let socket = unsafe { OwnedFd::from_raw_fd(fd) }; + Ok(IPCListener { socket }) + } + + pub fn accept(&self) -> Result { + let socket = accept(self.socket.as_fd().as_raw_fd()).map_err(IPCError::AcceptFailed)?; + // SAFETY: `socket` is guaranteed to be valid at this point. + IPCConnector::from_fd(unsafe { OwnedFd::from_raw_fd(socket) }) + } + + pub fn as_raw_ref(&self) -> BorrowedFd { + self.socket.as_fd() + } +} diff --git a/toolkit/crashreporter/crash_helper_common/src/ipc_listener/windows.rs b/toolkit/crashreporter/crash_helper_common/src/ipc_listener/windows.rs new file mode 100644 index 00000000000..1b0f35c5bc4 --- /dev/null +++ b/toolkit/crashreporter/crash_helper_common/src/ipc_listener/windows.rs @@ -0,0 +1,205 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::{ + errors::IPCError, + platform::windows::{create_manual_reset_event, reset_event, server_name, set_event}, + IPCConnector, Pid, +}; + +use std::{ + ffi::{c_void, CStr, OsString}, + mem::zeroed, + os::windows::io::{AsHandle, AsRawHandle, FromRawHandle, OwnedHandle, RawHandle}, + ptr::null_mut, + str::FromStr, +}; +use windows_sys::Win32::{ + Foundation::{ + GetLastError, ERROR_IO_PENDING, ERROR_PIPE_CONNECTED, FALSE, HANDLE, INVALID_HANDLE_VALUE, + TRUE, + }, + Security::SECURITY_ATTRIBUTES, + Storage::FileSystem::{ + FILE_FLAG_FIRST_PIPE_INSTANCE, FILE_FLAG_OVERLAPPED, PIPE_ACCESS_DUPLEX, + }, + System::{ + Pipes::{ + ConnectNamedPipe, CreateNamedPipeA, PIPE_READMODE_MESSAGE, PIPE_TYPE_MESSAGE, + PIPE_UNLIMITED_INSTANCES, PIPE_WAIT, + }, + IO::{GetOverlappedResult, OVERLAPPED}, + }, +}; + +pub struct IPCListener { + server_name: String, + handle: OwnedHandle, + overlapped: Box, + event: OwnedHandle, + connected: bool, +} + +impl IPCListener { + pub fn new(pid: Pid) -> Result { + let server_name = server_name(pid); + let pipe = create_named_pipe(&server_name, /* first_instance */ true)?; + let event = create_manual_reset_event()?; + + Ok(IPCListener { + server_name, + handle: pipe, + overlapped: Box::new(unsafe { zeroed() }), + event, + connected: false, + }) + } + + pub fn event_raw_handle(&self) -> HANDLE { + self.event.as_raw_handle() as HANDLE + } + + pub fn listen(&mut self) -> Result<(), IPCError> { + reset_event(self.event.as_handle())?; + *self.overlapped.as_mut() = OVERLAPPED { + hEvent: self.event.as_raw_handle() as HANDLE, + ..unsafe { zeroed() } + }; + + // SAFETY: We guarantee that the handle and OVERLAPPED object are both + // valid and remain so while used by this function. + let res = unsafe { + ConnectNamedPipe( + self.handle.as_raw_handle() as HANDLE, + self.overlapped.as_mut(), + ) + }; + let error = unsafe { GetLastError() }; + + if res != FALSE { + // According to Microsoft's documentation this should never happen, + // we check out of an abundance of caution. + return Err(IPCError::System(error)); + } + + match error { + ERROR_IO_PENDING => Ok(()), + ERROR_PIPE_CONNECTED => { + set_event(self.event.as_handle())?; + self.connected = true; + + Ok(()) + } + _ => Err(IPCError::System(error)), + } + } + + pub fn accept(&mut self) -> Result { + if !self.connected { + let mut _number_of_bytes_transferred: u32 = 0; + let res = unsafe { + GetOverlappedResult( + self.handle.as_raw_handle() as HANDLE, + self.overlapped.as_ref(), + &mut _number_of_bytes_transferred, + /* bWait */ FALSE, + ) + }; + let error = unsafe { GetLastError() }; + if res == FALSE { + return Err(IPCError::System(error)); + } + } + + self.connected = false; + let new_pipe = create_named_pipe(&self.server_name, /* first_instance */ false)?; + let connected_pipe = std::mem::replace(&mut self.handle, new_pipe); + + // Once we've accepted a new connection and replaced the listener's + // pipe we need to listen again before we return, so that we're ready + // for the next iteration. + self.listen()?; + + IPCConnector::new(connected_pipe) + } + + /// Serialize this listener into a string that can be passed on the + /// command-line to a child process. This only works for newly + /// created listeners because they are explicitly created as inheritable. + pub fn serialize(&self) -> OsString { + let raw_handle = self.handle.as_raw_handle() as usize; + OsString::from_str(raw_handle.to_string().as_ref()).unwrap() + } + + /// Deserialize a listener from an argument passed on the command-line. + /// The resulting listener is ready to accept new connections. + pub fn deserialize(string: &CStr, pid: Pid) -> Result { + let server_name = server_name(pid); + let string = string.to_str().map_err(|_e| IPCError::ParseError)?; + let handle = usize::from_str(string).map_err(|_e| IPCError::ParseError)?; + let handle = handle as *mut c_void; + // SAFETY: This is a handle we passed in ourselves. + let handle = unsafe { OwnedHandle::from_raw_handle(handle) }; + let event = create_manual_reset_event()?; + + let mut listener = IPCListener { + server_name, + handle, + overlapped: Box::new(unsafe { zeroed() }), + event, + connected: false, + }; + + // Since we've inherited this handler we need to start a new + // asynchronous operation to listen for incoming connections. + listener.listen()?; + + return Ok(listener); + } +} + +// SAFETY: The listener can be transferred across threads in spite of the +// raw pointer contained in the OVERLAPPED structure because it is only +// used internally and never visible externally. +unsafe impl Send for IPCListener {} + +fn create_named_pipe(server_name: &str, first_instance: bool) -> Result { + const PIPE_BUFFER_SIZE: u32 = 4096; + + let open_mode = PIPE_ACCESS_DUPLEX + | FILE_FLAG_OVERLAPPED + | if first_instance { + FILE_FLAG_FIRST_PIPE_INSTANCE + } else { + 0 + }; + + let security_attributes = SECURITY_ATTRIBUTES { + nLength: size_of::() as u32, + lpSecurityDescriptor: null_mut(), + bInheritHandle: TRUE, + }; + + // SAFETY: We pass a pointer to the server name which we guarantee to be + // valid, and null for all the other pointer arguments. + let pipe = unsafe { + CreateNamedPipeA( + server_name.as_ptr(), + open_mode, + PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, + PIPE_UNLIMITED_INSTANCES, + PIPE_BUFFER_SIZE, + PIPE_BUFFER_SIZE, + 0, // nDefaultTimeout, default is 50ms + &security_attributes, + ) + }; + + if pipe == INVALID_HANDLE_VALUE { + return Err(IPCError::System(unsafe { GetLastError() })); + } + + // SAFETY: We just verified that the handle is valid. + Ok(unsafe { OwnedHandle::from_raw_handle(pipe as RawHandle) }) +} diff --git a/toolkit/crashreporter/crash_helper_common/src/ipc_poller.rs b/toolkit/crashreporter/crash_helper_common/src/ipc_poller.rs new file mode 100644 index 00000000000..2dd0f523e09 --- /dev/null +++ b/toolkit/crashreporter/crash_helper_common/src/ipc_poller.rs @@ -0,0 +1,23 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +/***************************************************************************** + * Windows * + *****************************************************************************/ + +#[cfg(target_os = "windows")] +pub use windows::wait_for_events; + +#[cfg(target_os = "windows")] +pub(crate) mod windows; + +/***************************************************************************** + * Android, macOS & Linux * + *****************************************************************************/ + +#[cfg(any(target_os = "android", target_os = "linux", target_os = "macos"))] +pub use unix::wait_for_events; + +#[cfg(any(target_os = "android", target_os = "linux", target_os = "macos"))] +pub(crate) mod unix; diff --git a/toolkit/crashreporter/crash_helper_common/src/ipc_poller/unix.rs b/toolkit/crashreporter/crash_helper_common/src/ipc_poller/unix.rs new file mode 100644 index 00000000000..07758291b07 --- /dev/null +++ b/toolkit/crashreporter/crash_helper_common/src/ipc_poller/unix.rs @@ -0,0 +1,65 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use nix::poll::{poll, PollFd, PollFlags, PollTimeout}; + +use crate::{errors::IPCError, ignore_eintr, IPCConnector, IPCEvent, IPCListener}; + +pub fn wait_for_events( + listener: &mut IPCListener, + connectors: &mut [IPCConnector], +) -> Result, IPCError> { + let mut pollfds = Vec::with_capacity(1 + connectors.len()); + pollfds.push(PollFd::new(listener.as_raw_ref(), PollFlags::POLLIN)); + pollfds.extend( + connectors + .iter() + .map(|connector| PollFd::new(connector.as_raw_ref(), PollFlags::POLLIN)), + ); + + let mut events = Vec::::new(); + let mut num_events = + ignore_eintr!(poll(&mut pollfds, PollTimeout::NONE)).map_err(IPCError::System)?; + + for (index, pollfd) in pollfds.iter().enumerate() { + // revents() returns None only if the kernel sends back data + // that nix does not understand, we can safely assume this + // never happens in practice hence the unwrap(). + let revents = pollfd.revents().unwrap(); + + if revents.contains(PollFlags::POLLIN) { + if index == 0 { + if let Ok(connector) = listener.accept() { + events.push(IPCEvent::Connect(connector)); + } + } else { + // SAFETY: The index is guaranteed to be >0 and within + // the bounds of the client_connectors array. + let connector = unsafe { connectors.get_unchecked(index - 1) }; + let header = connector.recv_header(); + if let Ok(header) = header { + // Note that if we encounter a failure we don't propagate + // it, when the socket gets disconnected we'll get a + // POLLHUP event anyway so deal with disconnections there + // instead of here. + events.push(IPCEvent::Header(index - 1, header)); + } + } + } + + if revents.contains(PollFlags::POLLHUP) && (index > 0) { + events.push(IPCEvent::Disconnect(index - 1)); + } + + if !revents.is_empty() { + num_events -= 1; + + if num_events == 0 { + break; + } + } + } + + Ok(events) +} diff --git a/toolkit/crashreporter/crash_helper_common/src/ipc_poller/windows.rs b/toolkit/crashreporter/crash_helper_common/src/ipc_poller/windows.rs new file mode 100644 index 00000000000..50999a48ad4 --- /dev/null +++ b/toolkit/crashreporter/crash_helper_common/src/ipc_poller/windows.rs @@ -0,0 +1,85 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use windows_sys::Win32::{ + Foundation::{ERROR_BROKEN_PIPE, ERROR_INVALID_PARAMETER, FALSE, HANDLE, WAIT_OBJECT_0}, + System::{ + SystemServices::MAXIMUM_WAIT_OBJECTS, + Threading::{WaitForMultipleObjects, INFINITE}, + }, +}; + +use crate::{errors::IPCError, IPCConnector, IPCEvent, IPCListener}; + +pub fn wait_for_events( + listener: &mut IPCListener, + connectors: &mut [IPCConnector], +) -> Result, IPCError> { + for connector in connectors.iter_mut() { + // TODO: We might get a broken pipe error here which would cause us to + // fail instead of just dropping the disconnected connector. + connector.sched_recv_header()?; + } + + let native_events = collect_events(listener, connectors); + + if native_events.len() > MAXIMUM_WAIT_OBJECTS as usize { + return Err(IPCError::WaitingFailure(Some(ERROR_INVALID_PARAMETER))); + } + + // SAFETY: This is less than MAXIMUM_WAIT_OBJECTS + let native_events_len: u32 = unsafe { native_events.len().try_into().unwrap_unchecked() }; + + let res = unsafe { + WaitForMultipleObjects( + native_events_len, + native_events.as_ptr(), + FALSE, // bWaitAll + INFINITE, + ) + }; + + if res >= (WAIT_OBJECT_0 + native_events_len) { + return Err(IPCError::WaitingFailure(None)); + } + + let index = (res - WAIT_OBJECT_0) as usize; + + let mut events = Vec::::new(); + if index == 0 { + if let Ok(connector) = listener.accept() { + events.push(IPCEvent::Connect(connector)); + } + } else { + let index = index - 1; + // SAFETY: The index is guaranteed to be within the bounds of the client_connectors array. + let connector = unsafe { connectors.get_unchecked_mut(index) }; + let header = connector.collect_header(); + + match header { + Ok(header) => { + events.push(IPCEvent::Header(index, header)); + } + Err(error) => match error { + IPCError::System(_code @ ERROR_BROKEN_PIPE) => { + events.push(IPCEvent::Disconnect(index)); + } + _ => return Err(error), + }, + } + } + + Ok(events) +} + +fn collect_events(listener: &IPCListener, connectors: &[IPCConnector]) -> Vec { + let mut events = Vec::with_capacity(1 + connectors.len()); + + events.push(listener.event_raw_handle()); + for connector in connectors { + events.push(connector.event_raw_handle()); + } + + events +} diff --git a/toolkit/crashreporter/crash_helper_common/src/lib.rs b/toolkit/crashreporter/crash_helper_common/src/lib.rs new file mode 100644 index 00000000000..98824bfa4ac --- /dev/null +++ b/toolkit/crashreporter/crash_helper_common/src/lib.rs @@ -0,0 +1,59 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::ffi::OsString; + +pub mod errors; +pub mod messages; + +mod breakpad; +mod ipc_channel; +mod ipc_connector; +mod ipc_listener; +mod ipc_poller; +mod platform; + +use errors::MessageError; + +// Re-export the platform-specific types and functions +pub use crate::breakpad::{AncillaryData, BreakpadChar, BreakpadData, BreakpadRawData, Pid}; +pub use crate::ipc_channel::IPCChannel; +pub use crate::ipc_connector::{IPCConnector, IPCEvent}; +pub use crate::ipc_listener::IPCListener; +pub use crate::ipc_poller::wait_for_events; + +/// OsString extensions to convert from/to C strings. The strings will be +/// regular nul-terminated byte strings on most platforms but will use wide +/// characters instead on Windows. +pub trait BreakpadString { + /// Turn an `OsString` into a vector of bytes + fn serialize(&self) -> Vec; + + /// Reconstruct an `OsString` from a vector of bytes obtained by calling + /// the `BreakpadString::serialize()` function. + fn deserialize(bytes: &[u8]) -> Result; + + /// Create an OsString from a C nul-terminated string. + /// + /// # Safety + /// + /// The `ptr` argument must point to a valid nul-terminated C string. + unsafe fn from_ptr(ptr: *const BreakpadChar) -> OsString; + + /// Create a nul-terminated C string holding the contents of this + /// `OsString` object. The resulting pointer must be freed by retaking + /// ownership of its memory via `BreakpadString::from_raw()`. + fn into_raw(self) -> *mut BreakpadChar; + + /// Retake ownership of a nul-terminated C string created via a call to + /// `BreakpadString::from_raw()`. + /// + /// # Safety + /// + /// The `ptr` argument must have been created via a call to the + /// `BreakpadString::from_raw()` function. + unsafe fn from_raw(ptr: *mut BreakpadChar) -> OsString; +} + +pub const IO_TIMEOUT: u16 = 2 * 1000; diff --git a/toolkit/crashreporter/crash_helper_common/src/messages.rs b/toolkit/crashreporter/crash_helper_common/src/messages.rs new file mode 100644 index 00000000000..935a251b546 --- /dev/null +++ b/toolkit/crashreporter/crash_helper_common/src/messages.rs @@ -0,0 +1,622 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#[cfg(any(target_os = "android", target_os = "linux"))] +use minidump_writer::minidump_writer::{AuxvType, DirectAuxvDumpInfo}; +use num_derive::{FromPrimitive, ToPrimitive}; +use num_traits::FromPrimitive; +use std::{ + ffi::{CString, OsString}, + mem::size_of, +}; +#[cfg(target_os = "windows")] +use windows_sys::Win32::System::Diagnostics::Debug::{CONTEXT, EXCEPTION_RECORD}; + +use crate::{ + breakpad::{AncillaryData, Pid}, + errors::MessageError, + BreakpadString, +}; + +#[repr(u8)] +#[derive(Copy, Clone, Debug, FromPrimitive, ToPrimitive, PartialEq)] +pub enum Kind { + /// Changes the folder where crash reports are generated + SetCrashReportPath = 1, + /// Request the transfer of an already generated minidump for the specified + /// PID back to the client. The message type is followed by a 32-bit + /// integer containing the PID. + TransferMinidump = 2, + TransferMinidumpReply = 3, + /// Request the generation of a minidump of the specified process. + GenerateMinidump = 4, + GenerateMinidumpReply = 5, + /// Request the generation of a minidump based on data obtained via the + /// Windows Error Reporting runtime exception module. The reply is empty + /// and only used to inform the WER module that it's time to shut down the + /// crashed process. This is only enabled on Windows. + #[cfg(target_os = "windows")] + WindowsErrorReporting = 6, + #[cfg(target_os = "windows")] + WindowsErrorReportingReply = 7, + /// Register and unregister additional information for the auxiliary + /// vector of a process. + #[cfg(any(target_os = "android", target_os = "linux"))] + RegisterAuxvInfo = 8, + #[cfg(any(target_os = "android", target_os = "linux"))] + UnregisterAuxvInfo = 9, +} + +pub trait Message { + fn kind() -> Kind + where + Self: Sized; + fn header(&self) -> Vec; + fn payload(&self) -> Vec; + fn ancillary_payload(&self) -> Option; + fn decode(data: &[u8], ancillary_data: Option) -> Result + where + Self: Sized; +} + +/* Message header, all messages are prefixed with this. The header is sent as + * a single message over the underlying transport and contains the size of the + * message payload as well as the type of the message. This allows the receiver + * to validate and prepare for the reception of the payload. */ + +pub const HEADER_SIZE: usize = size_of::() + size_of::(); + +pub struct Header { + pub kind: Kind, + pub size: usize, +} + +impl Header { + fn encode(&self) -> Vec { + let mut buffer = Vec::with_capacity(HEADER_SIZE); + buffer.push(self.kind as u8); + buffer.extend(&self.size.to_ne_bytes()); + debug_assert!(buffer.len() == HEADER_SIZE, "Header size mismatch"); + buffer + } + + pub fn decode(buffer: &[u8]) -> Result { + let kind = buffer.first().ok_or(MessageError::Truncated)?; + let kind = Kind::from_u8(*kind).ok_or(MessageError::InvalidKind)?; + let size_bytes: [u8; size_of::()] = + buffer[size_of::()..size_of::() + size_of::()].try_into()?; + let size = usize::from_ne_bytes(size_bytes); + + Ok(Header { kind, size }) + } +} + +/* Message used to change the path where crash reports are generated. */ + +pub struct SetCrashReportPath { + pub path: OsString, +} + +impl SetCrashReportPath { + pub fn new(path: OsString) -> SetCrashReportPath { + SetCrashReportPath { path } + } + + fn payload_size(&self) -> usize { + let path_len = self.path.serialize().len(); + size_of::() + path_len + } +} + +impl Message for SetCrashReportPath { + fn kind() -> Kind { + Kind::SetCrashReportPath + } + + fn header(&self) -> Vec { + Header { + kind: Self::kind(), + size: self.payload_size(), + } + .encode() + } + + fn payload(&self) -> Vec { + let mut payload = Vec::with_capacity(self.payload_size()); + let path = self.path.serialize(); + payload.extend(path.len().to_ne_bytes()); + payload.extend(self.path.serialize()); + payload + } + + fn ancillary_payload(&self) -> Option { + None + } + + fn decode( + data: &[u8], + ancillary_data: Option, + ) -> Result { + debug_assert!( + ancillary_data.is_none(), + "SetCrashReportPath messages cannot carry ancillary data" + ); + + let path_len_bytes: [u8; size_of::()] = data[0..size_of::()].try_into()?; + let path_len = usize::from_ne_bytes(path_len_bytes); + let offset = size_of::(); + + let path = ::deserialize(&data[offset..offset + path_len]) + .map_err(|_| MessageError::InvalidData)?; + + Ok(SetCrashReportPath { path }) + } +} + +/* Transfer minidump message, used to request the minidump which has been + * generated for the specified pid. */ + +pub struct TransferMinidump { + pub pid: Pid, +} + +impl TransferMinidump { + pub fn new(pid: Pid) -> TransferMinidump { + TransferMinidump { pid } + } +} + +impl Message for TransferMinidump { + fn kind() -> Kind { + Kind::TransferMinidump + } + + fn header(&self) -> Vec { + Header { + kind: Self::kind(), + size: size_of::(), + } + .encode() + } + + fn payload(&self) -> Vec { + self.pid.to_ne_bytes().to_vec() + } + + fn ancillary_payload(&self) -> Option { + None + } + + fn decode( + data: &[u8], + ancillary_data: Option, + ) -> Result { + debug_assert!( + ancillary_data.is_none(), + "TransferMinidump messages cannot carry ancillary data" + ); + let bytes: [u8; size_of::()] = data[0..size_of::()].try_into()?; + let pid = Pid::from_ne_bytes(bytes); + + Ok(TransferMinidump { pid }) + } +} + +/* Transfer minidump reply, received from the server after having sent a + * TransferMinidump message. */ + +pub struct TransferMinidumpReply { + pub path: OsString, + pub error: Option, +} + +impl TransferMinidumpReply { + pub fn new(path: OsString, error: Option) -> TransferMinidumpReply { + TransferMinidumpReply { path, error } + } + + fn payload_size(&self) -> usize { + let path_len = self.path.serialize().len(); + // TODO: We should use checked arithmetic here + (size_of::() * 2) + + path_len + + self + .error + .as_ref() + .map_or(0, |error| error.as_bytes().len()) + } +} + +impl Message for TransferMinidumpReply { + fn kind() -> Kind { + Kind::TransferMinidumpReply + } + + fn header(&self) -> Vec { + Header { + kind: Self::kind(), + size: self.payload_size(), + } + .encode() + } + + fn payload(&self) -> Vec { + let path_bytes = self.path.serialize(); + let mut buffer = Vec::with_capacity(self.payload_size()); + buffer.extend(path_bytes.len().to_ne_bytes()); + buffer.extend( + (self + .error + .as_ref() + .map_or(0, |error| error.as_bytes().len())) + .to_ne_bytes(), + ); + buffer.extend(path_bytes); + buffer.extend( + self.error + .as_ref() + .map_or(Vec::new(), |error| Vec::from(error.as_bytes())), + ); + buffer + } + + fn ancillary_payload(&self) -> Option { + None + } + + fn decode( + data: &[u8], + ancillary_data: Option, + ) -> Result { + debug_assert!( + ancillary_data.is_none(), + "TransferMinidumpReply messages cannot carry ancillary data" + ); + let path_len_bytes: [u8; size_of::()] = data[0..size_of::()].try_into()?; + let path_len = usize::from_ne_bytes(path_len_bytes); + let offset = size_of::(); + + let error_len_bytes: [u8; size_of::()] = + data[offset..offset + size_of::()].try_into()?; + let error_len = usize::from_ne_bytes(error_len_bytes); + let offset = offset + size_of::(); + + let path = ::deserialize(&data[offset..offset + path_len]) + .map_err(|_| MessageError::InvalidData)?; + let offset = offset + path_len; + + let error = if error_len > 0 { + Some(CString::new(&data[offset..offset + error_len])?) + } else { + None + }; + + Ok(TransferMinidumpReply::new(path, error)) + } +} + +/* Generate a minidump based on information captured by the Windows Error Reporting runtime exception module. */ + +#[cfg(target_os = "windows")] +pub struct WindowsErrorReportingMinidump { + pub pid: Pid, + pub tid: Pid, // TODO: This should be a different type + pub exception_records: Vec, + pub context: CONTEXT, +} + +#[cfg(target_os = "windows")] +impl WindowsErrorReportingMinidump { + pub fn new( + pid: Pid, + tid: Pid, + exception_records: Vec, + context: CONTEXT, + ) -> WindowsErrorReportingMinidump { + WindowsErrorReportingMinidump { + pid, + tid, + exception_records, + context, + } + } + + fn payload_size(&self) -> usize { + (size_of::() * 2) + + size_of::() + + (size_of::() * self.exception_records.len()) + + size_of::() + } +} + +#[cfg(target_os = "windows")] +impl Message for WindowsErrorReportingMinidump { + fn kind() -> Kind { + Kind::WindowsErrorReporting + } + + fn header(&self) -> Vec { + Header { + kind: Self::kind(), + size: self.payload_size(), + } + .encode() + } + + fn payload(&self) -> Vec { + let mut buffer = Vec::::with_capacity(self.payload_size()); + buffer.extend(self.pid.to_ne_bytes()); + buffer.extend(self.tid.to_ne_bytes()); + buffer.extend(self.exception_records.len().to_ne_bytes()); + for exception_record in self.exception_records.iter() { + let bytes: [u8; size_of::()] = + unsafe { std::mem::transmute(*exception_record) }; + buffer.extend(bytes); + } + let bytes: [u8; size_of::()] = unsafe { std::mem::transmute(self.context) }; + buffer.extend(bytes); + buffer + } + + fn ancillary_payload(&self) -> Option { + None + } + + fn decode( + data: &[u8], + ancillary_data: Option, + ) -> Result { + debug_assert!( + ancillary_data.is_none(), + "WindowsErrorReportingMinidump messages cannot carry ancillary data" + ); + let bytes: [u8; size_of::()] = data[0..size_of::()].try_into()?; + let pid = Pid::from_ne_bytes(bytes); + let offset = size_of::(); + + let bytes: [u8; size_of::()] = data[offset..(offset + size_of::())].try_into()?; + let tid = Pid::from_ne_bytes(bytes); + let offset = offset + size_of::(); + + let bytes: [u8; size_of::()] = + data[offset..(offset + size_of::())].try_into()?; + let exception_records_n = usize::from_ne_bytes(bytes); + let offset = offset + size_of::(); + + let mut exception_records = Vec::::with_capacity(exception_records_n); + for i in 0..exception_records_n { + let element_offset = offset + (i * size_of::()); + let bytes: [u8; size_of::()] = data + [element_offset..(element_offset + size_of::())] + .try_into()?; + let exception_record = unsafe { + std::mem::transmute::<[u8; size_of::()], EXCEPTION_RECORD>(bytes) + }; + exception_records.push(exception_record); + } + + let bytes: [u8; size_of::()] = + data[offset..(offset + size_of::())].try_into()?; + let context = unsafe { std::mem::transmute::<[u8; size_of::()], CONTEXT>(bytes) }; + + Ok(WindowsErrorReportingMinidump { + pid, + tid, + exception_records, + context, + }) + } +} + +/* Windows Error Reporting minidump reply, received from the server after + * having sent a WindowsErrorReportingMinidumpReply. Informs the client that + * it can tear down the crashed process. */ + +#[cfg(target_os = "windows")] +pub struct WindowsErrorReportingMinidumpReply {} + +#[cfg(target_os = "windows")] +impl Default for WindowsErrorReportingMinidumpReply { + fn default() -> Self { + Self::new() + } +} + +#[cfg(target_os = "windows")] +impl WindowsErrorReportingMinidumpReply { + pub fn new() -> WindowsErrorReportingMinidumpReply { + WindowsErrorReportingMinidumpReply {} + } + + fn payload_size(&self) -> usize { + 0 + } +} + +#[cfg(target_os = "windows")] +impl Message for WindowsErrorReportingMinidumpReply { + fn kind() -> Kind { + Kind::WindowsErrorReportingReply + } + + fn header(&self) -> Vec { + Header { + kind: Self::kind(), + size: self.payload_size(), + } + .encode() + } + + fn payload(&self) -> Vec { + Vec::::new() + } + + fn ancillary_payload(&self) -> Option { + None + } + + fn decode( + data: &[u8], + ancillary_data: Option, + ) -> Result { + if ancillary_data.is_some() || !data.is_empty() { + return Err(MessageError::InvalidData); + } + + Ok(WindowsErrorReportingMinidumpReply::new()) + } +} + +/* Message used to send information about a process' auxiliary vector. */ + +#[cfg(any(target_os = "android", target_os = "linux"))] +pub struct RegisterAuxvInfo { + pub pid: Pid, + pub auxv_info: DirectAuxvDumpInfo, +} + +#[cfg(any(target_os = "android", target_os = "linux"))] +impl RegisterAuxvInfo { + pub fn new(pid: Pid, auxv_info: DirectAuxvDumpInfo) -> RegisterAuxvInfo { + RegisterAuxvInfo { pid, auxv_info } + } + + fn payload_size(&self) -> usize { + // A bit hacky but we'll change this when we make + // serialization/deserialization later. + size_of::() + (size_of::() * 4) + } +} + +#[cfg(any(target_os = "android", target_os = "linux"))] +impl Message for RegisterAuxvInfo { + fn kind() -> Kind { + Kind::RegisterAuxvInfo + } + + fn header(&self) -> Vec { + Header { + kind: Self::kind(), + size: self.payload_size(), + } + .encode() + } + + fn payload(&self) -> Vec { + let mut payload = Vec::with_capacity(self.payload_size()); + payload.extend(self.pid.to_ne_bytes()); + payload.extend(self.auxv_info.program_header_count.to_ne_bytes()); + payload.extend(self.auxv_info.program_header_address.to_ne_bytes()); + payload.extend(self.auxv_info.linux_gate_address.to_ne_bytes()); + payload.extend(self.auxv_info.entry_address.to_ne_bytes()); + debug_assert!(self.payload_size() == payload.len()); + payload + } + + fn ancillary_payload(&self) -> Option { + None + } + + fn decode( + data: &[u8], + ancillary_data: Option, + ) -> Result { + debug_assert!( + ancillary_data.is_none(), + "RegisterAuxvInfo messages cannot carry ancillary data" + ); + + let bytes: [u8; size_of::()] = data[0..size_of::()].try_into()?; + let pid = Pid::from_ne_bytes(bytes); + let offset = size_of::(); + + let bytes: [u8; size_of::()] = + data[offset..(offset + size_of::())].try_into()?; + let program_header_count = AuxvType::from_ne_bytes(bytes); + let offset = offset + size_of::(); + + let bytes: [u8; size_of::()] = + data[offset..(offset + size_of::())].try_into()?; + let program_header_address = AuxvType::from_ne_bytes(bytes); + let offset = offset + size_of::(); + + let bytes: [u8; size_of::()] = + data[offset..(offset + size_of::())].try_into()?; + let linux_gate_address = AuxvType::from_ne_bytes(bytes); + let offset = offset + size_of::(); + + let bytes: [u8; size_of::()] = + data[offset..(offset + size_of::())].try_into()?; + let entry_address = AuxvType::from_ne_bytes(bytes); + + let auxv_info = DirectAuxvDumpInfo { + program_header_count, + program_header_address, + entry_address, + linux_gate_address, + }; + + Ok(RegisterAuxvInfo { pid, auxv_info }) + } +} + +/* Message used to inform the crash helper that a process' auxiliary vector + * information is not needed anymore. */ + +#[cfg(any(target_os = "android", target_os = "linux"))] +pub struct UnregisterAuxvInfo { + pub pid: Pid, +} + +#[cfg(any(target_os = "android", target_os = "linux"))] +impl UnregisterAuxvInfo { + pub fn new(pid: Pid) -> UnregisterAuxvInfo { + UnregisterAuxvInfo { pid } + } + + fn payload_size(&self) -> usize { + size_of::() + } +} + +#[cfg(any(target_os = "android", target_os = "linux"))] +impl Message for UnregisterAuxvInfo { + fn kind() -> Kind { + Kind::UnregisterAuxvInfo + } + + fn header(&self) -> Vec { + Header { + kind: Self::kind(), + size: self.payload_size(), + } + .encode() + } + + fn payload(&self) -> Vec { + let mut payload = Vec::with_capacity(self.payload_size()); + payload.extend(self.pid.to_ne_bytes()); + debug_assert!(self.payload_size() == payload.len()); + payload + } + + fn ancillary_payload(&self) -> Option { + None + } + + fn decode( + data: &[u8], + ancillary_data: Option, + ) -> Result { + debug_assert!( + ancillary_data.is_none(), + "UnregisterAuxvInfo messages cannot carry ancillary data" + ); + + let bytes: [u8; size_of::()] = data[0..size_of::()].try_into()?; + let pid = Pid::from_ne_bytes(bytes); + + Ok(UnregisterAuxvInfo { pid }) + } +} diff --git a/toolkit/crashreporter/crash_helper_common/src/platform.rs b/toolkit/crashreporter/crash_helper_common/src/platform.rs new file mode 100644 index 00000000000..d80d48564cb --- /dev/null +++ b/toolkit/crashreporter/crash_helper_common/src/platform.rs @@ -0,0 +1,25 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#[cfg(target_os = "windows")] +pub(crate) mod windows; + +#[cfg(any(target_os = "android", target_os = "linux"))] +pub(crate) mod linux; + +#[cfg(target_os = "macos")] +pub(crate) mod macos; + +#[cfg(any(target_os = "android", target_os = "linux", target_os = "macos"))] +#[macro_export] +macro_rules! ignore_eintr { + ($c:expr) => { + loop { + match $c { + Err(nix::errno::Errno::EINTR) => continue, + res => break res, + } + } + }; +} diff --git a/toolkit/crashreporter/crash_helper_common/src/platform/linux.rs b/toolkit/crashreporter/crash_helper_common/src/platform/linux.rs new file mode 100644 index 00000000000..eab643eae6c --- /dev/null +++ b/toolkit/crashreporter/crash_helper_common/src/platform/linux.rs @@ -0,0 +1,120 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::{ignore_eintr, AncillaryData, Pid}; + +use nix::{ + cmsg_space, + errno::Errno, + fcntl::{ + fcntl, + FcntlArg::{F_GETFL, F_SETFD, F_SETFL}, + FdFlag, OFlag, + }, + sys::socket::{ + getsockopt, recvmsg, sendmsg, socket, socketpair, sockopt::PeerCredentials, AddressFamily, + ControlMessage, ControlMessageOwned, MsgFlags, SockFlag, SockType, UnixAddr, + }, + Result, +}; +use std::{ + io::{IoSlice, IoSliceMut}, + os::fd::{AsRawFd, BorrowedFd, OwnedFd, RawFd}, +}; + +pub(crate) fn unix_socket() -> Result { + socket( + AddressFamily::Unix, + SockType::SeqPacket, + SockFlag::empty(), + None, + ) +} + +pub(crate) fn unix_socketpair() -> Result<(OwnedFd, OwnedFd)> { + socketpair( + AddressFamily::Unix, + SockType::SeqPacket, + None, + SockFlag::empty(), + ) +} + +pub(crate) fn set_socket_default_flags(socket: BorrowedFd) -> Result<()> { + // All our sockets are in non-blocking mode. + let fd = socket.as_raw_fd(); + let flags = OFlag::from_bits_retain(fcntl(fd, F_GETFL)?); + fcntl(fd, F_SETFL(flags.union(OFlag::O_NONBLOCK))).map(|_res| ()) +} + +pub(crate) fn set_socket_cloexec(socket: BorrowedFd) -> Result<()> { + fcntl(socket.as_raw_fd(), F_SETFD(FdFlag::FD_CLOEXEC)).map(|_res| ()) +} + +pub(crate) fn server_addr(pid: Pid) -> Result { + let server_name = format!("gecko-crash-helper-pipe.{pid:}"); + UnixAddr::new_abstract(server_name.as_bytes()) +} + +// Return the pid of the process connected to this socket. +pub(crate) fn connected_process_pid(socket: BorrowedFd) -> Result { + let pid = getsockopt(&socket, PeerCredentials)?.pid(); + + Ok(pid) +} + +pub(crate) fn send_nonblock(socket: RawFd, buff: &[u8], fd: Option) -> Result<()> { + let iov = [IoSlice::new(buff)]; + let scm_fds: Vec = fd.map_or(vec![], |fd| vec![fd]); + let scm = ControlMessage::ScmRights(&scm_fds); + + let res = ignore_eintr!(sendmsg::<()>(socket, &iov, &[scm], MsgFlags::empty(), None)); + + match res { + Ok(bytes_sent) => { + if bytes_sent == buff.len() { + Ok(()) + } else { + // TODO: This should never happen but we might want to put a + // better error message here. + Err(Errno::EMSGSIZE) + } + } + Err(code) => Err(code), + } +} + +pub(crate) fn recv_nonblock( + socket: RawFd, + expected_size: usize, +) -> Result<(Vec, Option)> { + let mut buff: Vec = vec![0; expected_size]; + let mut cmsg_buffer = cmsg_space!(RawFd); + let mut iov = [IoSliceMut::new(&mut buff)]; + + let res = ignore_eintr!(recvmsg::<()>( + socket, + &mut iov, + Some(&mut cmsg_buffer), + MsgFlags::empty(), + ))?; + + let fd = if let Some(cmsg) = res.cmsgs()?.next() { + if let ControlMessageOwned::ScmRights(fds) = cmsg { + fds.first().copied() + } else { + return Err(Errno::EBADMSG); + } + } else { + None + }; + + if res.bytes != expected_size { + // TODO: This should only ever happen if the other side has gone rogue, + // we need a better error message here. + return Err(Errno::EBADMSG); + } + + Ok((buff, fd)) +} diff --git a/toolkit/crashreporter/crash_helper_common/src/platform/macos.rs b/toolkit/crashreporter/crash_helper_common/src/platform/macos.rs new file mode 100644 index 00000000000..97a1bb8c13b --- /dev/null +++ b/toolkit/crashreporter/crash_helper_common/src/platform/macos.rs @@ -0,0 +1,147 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::{ignore_eintr, AncillaryData, Pid}; + +use nix::{ + errno::Errno, + fcntl::{ + fcntl, + FcntlArg::{F_GETFL, F_SETFD, F_SETFL}, + FdFlag, OFlag, + }, + libc::{setsockopt, SOL_SOCKET, SO_NOSIGPIPE}, + sys::socket::{ + getsockopt, recv, send, socket, socketpair, sockopt::LocalPeerPid, AddressFamily, MsgFlags, + SockFlag, SockType, UnixAddr, + }, + Result, +}; +use std::{ + mem::size_of, + os::fd::{AsRawFd, BorrowedFd, OwnedFd, RawFd}, + path::PathBuf, + str::FromStr, +}; + +pub(crate) fn unix_socket() -> Result { + socket( + AddressFamily::Unix, + SockType::Stream, + SockFlag::empty(), + None, + ) +} + +pub(crate) fn unix_socketpair() -> Result<(OwnedFd, OwnedFd)> { + socketpair( + AddressFamily::Unix, + SockType::Stream, + None, + SockFlag::empty(), + ) +} + +pub(crate) fn set_socket_default_flags(socket: BorrowedFd) -> Result<()> { + // All our sockets are in non-blocking mode. + let fd = socket.as_raw_fd(); + let flags = OFlag::from_bits_retain(fcntl(fd, F_GETFL)?); + fcntl(fd, F_SETFL(flags.union(OFlag::O_NONBLOCK)))?; + + // TODO: nix doesn't have a safe wrapper for SO_NOSIGPIPE yet, but we need + // to set this flag because we're using stream sockets unlike Linux, where + // we use sequential packets that don't raise SIGPIPE. + let res = unsafe { + setsockopt( + fd, + SOL_SOCKET, + SO_NOSIGPIPE, + (&1 as *const i32).cast(), + size_of::() as _, + ) + }; + + if res < 0 { + return Err(Errno::last()); + } + + Ok(()) +} + +pub(crate) fn set_socket_cloexec(socket: BorrowedFd) -> Result<()> { + fcntl(socket.as_raw_fd(), F_SETFD(FdFlag::FD_CLOEXEC)).map(|_res| ()) +} + +pub(crate) fn server_addr(pid: Pid) -> Result { + // macOS doesn't seem to support abstract paths as addresses for Unix + // protocol sockets, so this needs to be the path of an actual file. + let server_name = format!("/tmp/gecko-crash-helper-pipe.{pid:}"); + let server_path = PathBuf::from_str(&server_name).unwrap(); + UnixAddr::new(&server_path) +} + +// Return the pid of the process connected to this socket. +pub(crate) fn connected_process_pid(socket: BorrowedFd) -> Result { + let pid = getsockopt(&socket, LocalPeerPid)?; + + Ok(pid) +} + +// We're using plain recv()/send() calls here and we're calling them in a loop +// until all the data expected to be in a message gets retrieved or sent. Note +// however we'll fail whenever we hit an EAGAIN condition. That's because IPC +// calls are essentially symmetric, so if we're hitting EAGAIN it means one of +// the sides isn't responding so better to fail right away. + +pub(crate) fn send_nonblock(socket: RawFd, buff: &[u8], fd: Option) -> Result<()> { + // We don't send file descriptors on macOS + if fd.is_some() { + return Err(Errno::EINVAL); + } + + let mut bytes_sent = 0; + + while bytes_sent != buff.len() { + let res = ignore_eintr!(send(socket, &buff[bytes_sent..], MsgFlags::empty())); + + match res { + Ok(size) => { + bytes_sent += size; + } + Err(error) => { + return Err(error); + } + } + } + + Ok(()) +} + +pub(crate) fn recv_nonblock( + socket: RawFd, + expected_size: usize, +) -> Result<(Vec, Option)> { + let mut buff: Vec = vec![0; expected_size]; + let mut bytes_received = 0; + + while bytes_received != expected_size { + let res = ignore_eintr!(recv(socket, &mut buff[bytes_received..], MsgFlags::empty())); + + match res { + Ok(size) => { + bytes_received += size; + + if size == 0 { + // This means the other end was disconnected + return Err(Errno::EBADMSG); + } + } + Err(error) => { + return Err(error); + } + } + } + + Ok((buff, None)) +} diff --git a/toolkit/crashreporter/crash_helper_common/src/platform/windows.rs b/toolkit/crashreporter/crash_helper_common/src/platform/windows.rs new file mode 100644 index 00000000000..8e953ecbb2f --- /dev/null +++ b/toolkit/crashreporter/crash_helper_common/src/platform/windows.rs @@ -0,0 +1,57 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use crate::{errors::IPCError, Pid}; +use std::{ + os::windows::io::{AsRawHandle, BorrowedHandle, FromRawHandle, OwnedHandle, RawHandle}, + ptr::null, +}; +use windows_sys::Win32::{ + Foundation::{GetLastError, FALSE, HANDLE}, + System::Threading::{CreateEventA, ResetEvent, SetEvent}, +}; + +pub(crate) fn server_name(pid: Pid) -> String { + // We'll be passing this to CreateNamedPipeA() so we nul-terminate it. + format!("\\\\.\\pipe\\gecko-crash-helper-pipe.{pid:}\0") +} + +pub(crate) fn create_manual_reset_event() -> Result { + // SAFETY: We pass null pointers for all the pointer arguments. + let raw_handle = unsafe { + CreateEventA( + /* lpEventAttributes */ null(), + /* bManualReset */ FALSE, + /* bInitialState */ FALSE, + /* lpName */ null(), + ) + } as RawHandle; + + if raw_handle.is_null() { + return Err(IPCError::System(unsafe { GetLastError() })); + } + + // SAFETY: We just verified that `raw_handle` is valid. + Ok(unsafe { OwnedHandle::from_raw_handle(raw_handle) }) +} + +pub(crate) fn reset_event(handle: BorrowedHandle) -> Result<(), IPCError> { + // SAFETY: The handle we pass is guaranteed to be valid. + let res = unsafe { ResetEvent(handle.as_raw_handle() as HANDLE) }; + + match res { + FALSE => Err(IPCError::System(unsafe { GetLastError() })), + _ => Ok(()), + } +} + +pub(crate) fn set_event(handle: BorrowedHandle) -> Result<(), IPCError> { + // SAFETY: The handle we pass is guaranteed to be valid. + let res = unsafe { SetEvent(handle.as_raw_handle() as HANDLE) }; + + match res { + FALSE => Err(IPCError::System(unsafe { GetLastError() })), + _ => Ok(()), + } +} diff --git a/toolkit/crashreporter/crash_helper_server/Cargo.toml b/toolkit/crashreporter/crash_helper_server/Cargo.toml new file mode 100644 index 00000000000..434bec8b723 --- /dev/null +++ b/toolkit/crashreporter/crash_helper_server/Cargo.toml @@ -0,0 +1,56 @@ +[package] +name = "crash_helper_server" +version = "0.1.0" +authors = ["Gabriele Svelto "] +edition = "2018" + +[dependencies] +anyhow = "1" +cfg-if = "1" +crash_helper_common = { path = "../crash_helper_common" } +dirs = "4" +log = "0.4" +mozannotation_server = { path = "../mozannotation_server" } +mozbuild = "0.1" +mozilla-central-workspace-hack = { version = "0.1", features = [ + "crash_helper_server", +], optional = true } +num-derive = "0.4" +num-traits = "0.2" +once_cell = "1" +thiserror = "2" +uuid = { version = "1.0", features = ["v4"] } + +# Use android_logger on Android, env_logger everywhere else +[target.'cfg(not(target_os = "android"))'.dependencies] +env_logger = { version = "0.10", default-features = false } +[target.'cfg(target_os = "android")'.dependencies] +android_logger = "0.12" + +[target."cfg(any(target_os = \"android\", target_os = \"linux\"))".dependencies] +nix = { version = "0.29", features = ["poll", "socket", "uio"] } +minidump-writer = { version = "0.10" } +rust_minidump_writer_linux = { path = "../rust_minidump_writer_linux" } + +[target."cfg(target_os = \"windows\")".dependencies] +windows-sys = { version = "0.52", features = [ + "Win32_Foundation", + "Win32_Storage_FileSystem", + "Win32_System_Kernel", + "Win32_System_Memory", + "Win32_System_SystemInformation", + "Win32_System_SystemServices", +] } + +[target."cfg(target_os = \"macos\")".dependencies] +nix = { version = "0.29", features = ["fs", "poll", "socket", "uio"] } + +[build-dependencies] +cc = "1" +linked-hash-map = "0.5" +yaml-rust = "0.4" + +[lib] +name = "crash_helper_server" +crate-type = ["staticlib"] +path = "src/lib.rs" diff --git a/toolkit/crashreporter/crash_helper_server/build.rs b/toolkit/crashreporter/crash_helper_server/build.rs new file mode 100644 index 00000000000..82957d588db --- /dev/null +++ b/toolkit/crashreporter/crash_helper_server/build.rs @@ -0,0 +1,168 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::env; +use std::fs::{self, File}; +use std::io::{Error, Write}; +use std::path::Path; +use std::vec::Vec; + +use yaml_rust::{Yaml, YamlLoader}; + +fn main() -> Result<(), Error> { + generate_annotations()?; + Ok(()) +} + +struct Annotation { + name: String, + type_string: String, + altname: Option, + skip_if: Option, +} + +impl Annotation { + fn new(key: &Yaml, value: &Yaml) -> Result { + let name = key.as_str().ok_or(Error::other("Not a string"))?; + let raw_type = value["type"].as_str().ok_or(Error::other("Missing type"))?; + let type_string = Annotation::type_str_to_value(raw_type)?; + let altname = value["altname"].as_str().map(str::to_owned); + // We don't care about the contents of the `ping` field for the time being + let skip_if = value["skip_if"].as_str().map(str::to_owned); + + Ok(Annotation { + name: name.to_owned(), + type_string, + altname, + skip_if, + }) + } + + fn type_str_to_value(raw_type: &str) -> Result { + match raw_type { + "string" => Ok("String"), + "boolean" => Ok("Boolean"), + "u32" => Ok("U32"), + "u64" => Ok("U64"), + "usize" => Ok("USize"), + "object" => Ok("Object"), + _ => Err(Error::other("Invalid type")), + } + .map(str::to_owned) + } +} + +fn read_annotations(doc: &Yaml) -> Result, Error> { + let raw_annotations = doc.as_hash().ok_or(Error::other( + "Invalid data in YAML file, expected a hash".to_string(), + ))?; + + let mut annotations = Vec::::new(); + for annotation in raw_annotations { + let annotation = Annotation::new(annotation.0, annotation.1)?; + annotations.push(annotation); + } + + annotations.sort_by(|a, b| { + let a_lower = a.name.to_ascii_lowercase(); + let b_lower = b.name.to_ascii_lowercase(); + a_lower.cmp(&b_lower) + }); + + Ok(annotations) +} + +fn generate_annotation_enum(annotations: &[Annotation]) -> Result { + let mut annotations_enum = String::new(); + + for (index, value) in annotations.iter().enumerate() { + let entry = format!(" {} = {index:},\n", value.name); + annotations_enum.push_str(&entry); + } + + let count = format!(" Count = {},", annotations.len()); + annotations_enum.push_str(&count); + + Ok(annotations_enum) +} + +fn generate_annotation_types(annotations: &[Annotation]) -> Result { + let mut types_array = String::new(); + for value in annotations.iter() { + let entry = format!(" CrashAnnotationType::{},\n", value.type_string); + types_array.push_str(&entry); + } + + // Pop the last newline + types_array.pop(); + + Ok(types_array) +} + +fn generate_annotation_names(annotations: &[Annotation]) -> Result { + let mut names_array = String::new(); + for value in annotations.iter() { + let name = value.altname.as_ref().unwrap_or(&value.name); + let entry = format!(" \"{}\",\n", name); + names_array.push_str(&entry); + } + + // Pop the last newline + names_array.pop(); + + Ok(names_array) +} + +fn generate_annotation_skiplist(annotations: &[Annotation]) -> Result { + let mut skiplist = String::new(); + for annotation in annotations.iter() { + if let Some(skip_if) = &annotation.skip_if { + let entry = format!( + " CrashAnnotationSkipValue {{ annotation: CrashAnnotation::{}, value: b\"{}\" }},\n", + &annotation.name, skip_if + ); + skiplist.push_str(&entry); + } + } + + // Pop the last newline + skiplist.pop(); + + Ok(skiplist) +} + +// Generate Rust code to manipulate crash annotations +fn generate_annotations() -> Result<(), Error> { + const CRASH_ANNOTATIONS_YAML: &str = "../CrashAnnotations.yaml"; + const CRASH_ANNOTATIONS_TEMPLATE: &str = "../crash_annotations.rs.in"; + + let out_dir = env::var("OUT_DIR").unwrap(); + let annotations_path = Path::new(&out_dir).join("crash_annotations.rs"); + let mut annotations_file = File::create(annotations_path)?; + + let template = fs::read_to_string(CRASH_ANNOTATIONS_TEMPLATE)?; + let yaml_str = fs::read_to_string(CRASH_ANNOTATIONS_YAML)?; + let yaml_doc = YamlLoader::load_from_str(&yaml_str) + .map_err(|e| Error::other(format!("Failed to parse YAML file: {}", e)))?; + + let doc = &yaml_doc[0]; + let annotations = read_annotations(doc)?; + + let annotations_enum = generate_annotation_enum(&annotations)?; + let annotations_types = generate_annotation_types(&annotations)?; + let annotations_names = generate_annotation_names(&annotations)?; + let skiplist = generate_annotation_skiplist(&annotations)?; + + let compiled_template = template + .replace("${enum}", &annotations_enum) + .replace("${types}", &annotations_types) + .replace("${names}", &annotations_names) + .replace("${skiplist}", &skiplist); + write!(&mut annotations_file, "{}", compiled_template)?; + + println!("cargo:rerun-if-changed={CRASH_ANNOTATIONS_YAML}"); + println!("cargo:rerun-if-changed={CRASH_ANNOTATIONS_TEMPLATE}"); + + Ok(()) +} diff --git a/toolkit/crashreporter/crash_helper_server/cbindgen.toml b/toolkit/crashreporter/crash_helper_server/cbindgen.toml new file mode 100644 index 00000000000..5fd914767a0 --- /dev/null +++ b/toolkit/crashreporter/crash_helper_server/cbindgen.toml @@ -0,0 +1,28 @@ +header = """/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */""" +autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen. See RunCbindgen.py */ +""" +include_version = true +braces = "SameLine" +line_length = 100 +tab_width = 2 +language = "C++" +include_guard = "crash_helper_ffi_generated_h" + +[defines] +"target_os = android" = "MOZ_WIDGET_ANDROID" +"target_os = linux" = "XP_LINUX" +"target_os = macos" = "XP_MACOSX" +"target_os = windows" = "XP_WIN" + +[export] +exclude = [ + "CrashGenerationServer_init", + "CrashGenerationServer_shutdown", + "CrashGenerationServer_set_path", +] + +[parse] +parse_deps = true +include = ["crash_helper_common"] diff --git a/toolkit/crashreporter/crash_helper_server/moz.build b/toolkit/crashreporter/crash_helper_server/moz.build new file mode 100644 index 00000000000..798a6ed812c --- /dev/null +++ b/toolkit/crashreporter/crash_helper_server/moz.build @@ -0,0 +1,25 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +RustLibrary("crash_helper_server") + +if CONFIG["OS_TARGET"] == "WINNT": + OS_LIBS += [ + "ole32", + "shell32", + ] + +if CONFIG["COMPILE_ENVIRONMENT"]: + # This tells mach to run cbindgen and that this header-file should be created + CbindgenHeader( + "crash_helper_ffi_generated.h", + inputs=["/toolkit/crashreporter/crash_helper_server"], + ) + + # This tells mach to copy that generated file to obj/dist/mozilla + EXPORTS.mozilla += [ + "!crash_helper_ffi_generated.h", + ] diff --git a/toolkit/crashreporter/crash_helper_server/src/breakpad_crash_generator.rs b/toolkit/crashreporter/crash_helper_server/src/breakpad_crash_generator.rs new file mode 100644 index 00000000000..793c40e8f85 --- /dev/null +++ b/toolkit/crashreporter/crash_helper_server/src/breakpad_crash_generator.rs @@ -0,0 +1,135 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/****************************************************************************** + * Wrappers used to call into Breakpad code * + ******************************************************************************/ + +use std::{ + ffi::{c_char, c_void, OsString}, + ptr::NonNull, +}; + +use anyhow::{bail, Result}; +use cfg_if::cfg_if; +use crash_helper_common::{BreakpadChar, BreakpadData, BreakpadString}; +#[cfg(any(target_os = "android", target_os = "linux"))] +use minidump_writer::minidump_writer::DirectAuxvDumpInfo; + +use crate::crash_generation::BreakpadProcessId; + +#[cfg(target_os = "windows")] +type BreakpadInitType = *const u16; +#[cfg(target_os = "macos")] +type BreakpadInitType = *const c_char; +#[cfg(any(target_os = "linux", target_os = "android"))] +type BreakpadInitType = std::os::fd::RawFd; +#[cfg(any(target_os = "linux", target_os = "android"))] +use std::os::fd::{FromRawFd, OwnedFd}; + +extern "C" { + fn CrashGenerationServer_init( + breakpad_data: BreakpadInitType, + minidump_path: *const BreakpadChar, + cb: extern "C" fn(BreakpadProcessId, *const c_char, *const BreakpadChar), + #[cfg(any(target_os = "android", target_os = "linux"))] auxv_cb: extern "C" fn( + crash_helper_common::Pid, + *mut DirectAuxvDumpInfo, + ) + -> bool, + ) -> *mut c_void; + fn CrashGenerationServer_shutdown(server: *mut c_void); + fn CrashGenerationServer_set_path(server: *mut c_void, path: *const BreakpadChar); +} + +pub(crate) struct BreakpadCrashGenerator { + ptr: NonNull, + path: NonNull, + #[allow( + dead_code, + reason = "This socket is used by Breakpad so it must be closed on Drop() as we own it" + )] + #[cfg(any(target_os = "linux", target_os = "android"))] + breakpad_socket: OwnedFd, +} + +// Safety: We own the pointer to the Breakpad C++ CrashGeneration server object +// so we can safely transfer this object to another thread. +unsafe impl Send for BreakpadCrashGenerator {} + +// Safety: All mutations to the pointer to the Breakpad C++ CrashGeneration +// server happen within this object meaning it's safe to read it from different +// threads. +unsafe impl Sync for BreakpadCrashGenerator {} + +impl BreakpadCrashGenerator { + pub(crate) fn new( + breakpad_data: BreakpadData, + path: OsString, + finalize_callback: extern "C" fn(BreakpadProcessId, *const c_char, *const BreakpadChar), + #[cfg(any(target_os = "android", target_os = "linux"))] + auxv_callback: extern "C" fn( + crash_helper_common::Pid, + *mut DirectAuxvDumpInfo, + ) -> bool, + ) -> Result { + let breakpad_raw_data = breakpad_data.into_raw(); + let path_ptr = path.into_raw(); + + // SAFETY: Calling into breakpad code with parameters that have been previously validated. + let breakpad_server = unsafe { + CrashGenerationServer_init( + breakpad_raw_data, + path_ptr, + finalize_callback, + #[cfg(any(target_os = "android", target_os = "linux"))] + auxv_callback, + ) + }; + + // Retake ownership of the raw data & strings so we don't leak them. + cfg_if! { + if #[cfg(any(target_os = "macos", target_os = "windows"))] { + // SAFETY: We've allocated this object within this same block. + let _breakpad_data = unsafe { BreakpadData::new(breakpad_raw_data) }; + } + } + + if breakpad_server.is_null() { + bail!("Could not initialize Breakpad crash generator"); + } + + // SAFETY: We already verified that the pointers are non-null. On Linux + // and Android the breakpad socket is also a valid file descriptor. We + // store it in an owned file descriptor because we're taking ownership + // of it and we want it closed when we shut down the crash generation + // server. + Ok(unsafe { + BreakpadCrashGenerator { + ptr: NonNull::new(breakpad_server).unwrap_unchecked(), + path: NonNull::new(path_ptr).unwrap_unchecked(), + #[cfg(any(target_os = "linux", target_os = "android"))] + breakpad_socket: OwnedFd::from_raw_fd(breakpad_raw_data), + } + }) + } + + pub(crate) fn set_path(&self, path: OsString) { + unsafe { + let path = path.into_raw(); + CrashGenerationServer_set_path(self.ptr.as_ptr(), path); + }; + } +} + +impl Drop for BreakpadCrashGenerator { + fn drop(&mut self) { + // SAFETY: The pointers we're passing are guaranteed to be non-null and + // valid since we created them ourselves during construction. + unsafe { + CrashGenerationServer_shutdown(self.ptr.as_ptr()); + let _path = ::from_raw(self.path.as_ptr()); + } + } +} diff --git a/toolkit/crashreporter/crash_helper_server/src/crash_generation.rs b/toolkit/crashreporter/crash_helper_server/src/crash_generation.rs new file mode 100644 index 00000000000..6a3d7770fa5 --- /dev/null +++ b/toolkit/crashreporter/crash_helper_server/src/crash_generation.rs @@ -0,0 +1,454 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +pub mod crash_annotations { + include!(concat!(env!("OUT_DIR"), "/crash_annotations.rs")); +} + +#[cfg(target_os = "windows")] +mod windows; + +use anyhow::{bail, Result}; +use crash_annotations::{ + should_include_annotation, type_of_annotation, CrashAnnotation, CrashAnnotationType, +}; +use crash_helper_common::{ + messages::{self, Message}, + AncillaryData, BreakpadChar, BreakpadData, BreakpadString, Pid, +}; +#[cfg(any(target_os = "android", target_os = "linux"))] +use minidump_writer::minidump_writer::DirectAuxvDumpInfo; +use mozannotation_server::{AnnotationData, CAnnotation}; +use num_traits::FromPrimitive; +use once_cell::sync::Lazy; +use std::{ + collections::HashMap, + convert::TryInto, + ffi::{c_char, CStr, CString, OsStr, OsString}, + fs::File, + io::{Seek, SeekFrom, Write}, + mem::size_of, + path::{Path, PathBuf}, + sync::Mutex, +}; +#[cfg(target_os = "windows")] +use windows_sys::Win32::Foundation::HANDLE; + +use crate::{ + breakpad_crash_generator::BreakpadCrashGenerator, + phc::{self, StackTrace}, +}; + +struct CrashReport { + path: OsString, + error: Option, +} + +impl CrashReport { + fn new(path: &OsStr, error: &Option) -> CrashReport { + CrashReport { + path: path.to_owned(), + error: error.to_owned(), + } + } +} + +// Table holding all the crash reports we've generated. It's indexed by PID and +// new crash reports are insterted in the corresponding vector in order of +// arrival. When crashes are retrieved they're similarly pulled out in the +// order they've arrived. +static CRASH_REPORTS: Lazy>>> = Lazy::new(Default::default); + +// Table holding the information about the auxiliary vector of potentially +// every process registered with the crash helper. +#[cfg(any(target_os = "android", target_os = "linux"))] +static AUXV_INFO_MAP: Lazy>> = Lazy::new(Default::default); + +/****************************************************************************** + * Crash generator * + ******************************************************************************/ + +#[derive(PartialEq)] +enum MinidumpOrigin { + Breakpad, + WindowsErrorReporting, +} + +pub(crate) struct CrashGenerator { + // This will be used for generating hangs + _minidump_path: OsString, + breakpad_server: BreakpadCrashGenerator, + client_pid: Pid, +} + +impl CrashGenerator { + pub(crate) fn new( + client_pid: Pid, + breakpad_data: BreakpadData, + minidump_path: OsString, + ) -> Result { + let breakpad_server = BreakpadCrashGenerator::new( + breakpad_data, + minidump_path.clone(), + finalize_breakpad_minidump, + #[cfg(any(target_os = "android", target_os = "linux"))] + get_auxv_info, + )?; + + Ok(CrashGenerator { + _minidump_path: minidump_path, + breakpad_server, + client_pid, + }) + } + + // Process a message received from the client. Return an optional reply + // that will be sent back to the client. + pub(crate) fn client_message( + &mut self, + kind: messages::Kind, + data: &[u8], + ancillary_data: Option, + pid: Pid, + ) -> Result>> { + match kind { + messages::Kind::SetCrashReportPath => { + if pid != self.client_pid { + panic!("Not connected or attempting to set the path from the wrong process"); + } + + let message = messages::SetCrashReportPath::decode(data, ancillary_data)?; + self.set_path(message.path); + Ok(None) + } + messages::Kind::TransferMinidump => { + if pid != self.client_pid { + panic!( + "Not connected or attempting to request a minidump from a child process" + ); + } + + let message = messages::TransferMinidump::decode(data, ancillary_data)?; + Ok(Some(Box::new(self.transfer_minidump(message.pid)))) + } + messages::Kind::GenerateMinidump => { + todo!("Implement all messages"); + } + #[cfg(target_os = "windows")] + messages::Kind::WindowsErrorReporting => { + let message = + messages::WindowsErrorReportingMinidump::decode(data, ancillary_data)?; + let _ = self.generate_wer_minidump(message); + Ok(Some(Box::new( + messages::WindowsErrorReportingMinidumpReply::new(), + ))) + } + #[cfg(any(target_os = "android", target_os = "linux"))] + messages::Kind::RegisterAuxvInfo => { + if pid != self.client_pid { + panic!( + "Attempting to register some auxiliary information from the wrong process" + ); + } + + let message = messages::RegisterAuxvInfo::decode(data, ancillary_data)?; + let map = &mut AUXV_INFO_MAP.lock().unwrap(); + map.insert(message.pid, message.auxv_info); + + Ok(None) + } + #[cfg(any(target_os = "android", target_os = "linux"))] + messages::Kind::UnregisterAuxvInfo => { + if pid != self.client_pid { + panic!("Attempting to unregister auxiliary information from the wrong process"); + } + + let message = messages::UnregisterAuxvInfo::decode(data, ancillary_data)?; + let map = &mut AUXV_INFO_MAP.lock().unwrap(); + map.remove(&message.pid); + + Ok(None) + } + kind => { + bail!("Unexpected message {:?}", kind); + } + } + } + + fn set_path(&mut self, path: OsString) { + self.breakpad_server.set_path(path); + } + + fn transfer_minidump(&self, pid: Pid) -> messages::TransferMinidumpReply { + let mut map = CRASH_REPORTS.lock().unwrap(); + if let Some(mut entry) = map.remove(&pid) { + let crash_report = entry.remove(0); + + if !entry.is_empty() { + map.insert(pid, entry); + } + + messages::TransferMinidumpReply::new(crash_report.path, crash_report.error) + } else { + // Report not found, reply with a zero length path + messages::TransferMinidumpReply::new(OsString::new(), None) + } + } +} + +/****************************************************************************** + * Crash annotations * + ******************************************************************************/ + +macro_rules! read_numeric_annotation { + ($t:ty,$d:expr) => { + if let AnnotationData::ByteBuffer(buff) = $d { + if buff.len() == size_of::<$t>() { + let value = buff.get(0..size_of::<$t>()).map(|bytes| { + let bytes: [u8; size_of::<$t>()] = bytes.try_into().unwrap(); + <$t>::from_ne_bytes(bytes) + }); + value.map(|value| value.to_string().into_bytes()) + } else { + None + } + } else { + None + } + }; +} + +fn write_phc_annotations(file: &mut File, buff: &[u8]) -> Result<()> { + let addr_info = phc::AddrInfo::from_bytes(buff)?; + if addr_info.kind == phc::Kind::Unknown { + return Ok(()); + } + + write!( + file, + "\"PHCKind\":\"{}\",\ + \"PHCBaseAddress\":\"{}\",\ + \"PHCUsableSize\":\"{}\",", + addr_info.kind_as_str(), + addr_info.base_addr as usize, + addr_info.usable_size, + )?; + + if addr_info.alloc_stack.has_stack != 0 { + write!( + file, + "\"PHCAllocStack\":\"{}\",", + serialize_phc_stack(&addr_info.alloc_stack) + )?; + } + + if addr_info.free_stack.has_stack != 0 { + write!( + file, + "\"PHCFreeStack\":\"{}\",", + serialize_phc_stack(&addr_info.free_stack) + )?; + } + + Ok(()) +} + +fn serialize_phc_stack(stack_trace: &StackTrace) -> String { + let mut string = String::new(); + for i in 0..stack_trace.length { + string.push_str(&(stack_trace.pcs[i] as usize).to_string()); + string.push(','); + } + + string.pop(); + string +} + +#[repr(C)] +pub struct BreakpadProcessId { + pub pid: Pid, + #[cfg(target_os = "macos")] + pub task: u32, + #[cfg(target_os = "windows")] + pub handle: HANDLE, +} + +/// This reads the crash annotations, writes them to the .extra file and +/// finally stores the resulting minidump in the global hash table. +extern "C" fn finalize_breakpad_minidump( + process_id: BreakpadProcessId, + error_ptr: *const c_char, + minidump_path_ptr: *const BreakpadChar, +) { + let minidump_path = + PathBuf::from(unsafe { ::from_ptr(minidump_path_ptr) }); + let error = if !error_ptr.is_null() { + // SAFETY: The string is a valid C string we passed in ourselves. + Some(unsafe { CStr::from_ptr(error_ptr) }.to_owned()) + } else { + None + }; + + finalize_crash_report(process_id, error, &minidump_path, MinidumpOrigin::Breakpad); +} + +fn finalize_crash_report( + process_id: BreakpadProcessId, + error: Option, + minidump_path: &Path, + origin: MinidumpOrigin, +) { + let mut extra_path = PathBuf::from(minidump_path); + extra_path.set_extension("extra"); + + let annotations = retrieve_annotations(&process_id, origin); + let extra_file_written = annotations + .map(|annotations| write_extra_file(&annotations, &extra_path)) + .is_ok(); + + let path = minidump_path.as_os_str(); + let error = if !extra_file_written { + Some(CString::new("MissingAnnotations").unwrap()) + } else { + error + }; + + let map = &mut CRASH_REPORTS.lock().unwrap(); + let entry = map.entry(process_id.pid); + entry + .and_modify(|entry| entry.push(CrashReport::new(path, &error))) + .or_insert_with(|| vec![CrashReport::new(path, &error)]); +} + +#[cfg(any(target_os = "android", target_os = "linux"))] +extern "C" fn get_auxv_info(pid: Pid, auxv_info_ptr: *mut DirectAuxvDumpInfo) -> bool { + let map = &mut AUXV_INFO_MAP.lock().unwrap(); + + if let Some(auxv_info) = map.get(&pid) { + // SAFETY: The auxv_info_ptr is guaranteed to be valid by the caller. + unsafe { auxv_info_ptr.write(auxv_info.to_owned()) }; + true + } else { + false + } +} + +fn retrieve_annotations( + process_id: &BreakpadProcessId, + origin: MinidumpOrigin, +) -> Result> { + #[cfg(target_os = "windows")] + let res = mozannotation_server::retrieve_annotations( + process_id.handle, + CrashAnnotation::Count as usize, + ); + #[cfg(any(target_os = "linux", target_os = "android"))] + let res = + mozannotation_server::retrieve_annotations(process_id.pid, CrashAnnotation::Count as usize); + #[cfg(target_os = "macos")] + let res = mozannotation_server::retrieve_annotations( + process_id.task, + CrashAnnotation::Count as usize, + ); + + let mut annotations = res?; + if origin == MinidumpOrigin::WindowsErrorReporting { + annotations.push(CAnnotation { + id: CrashAnnotation::WindowsErrorReporting as u32, + data: AnnotationData::ByteBuffer(vec![1]), + }); + } + + Ok(annotations) +} + +fn write_extra_file(annotations: &Vec, path: &Path) -> Result<()> { + let mut annotations_written: usize = 0; + let mut file = File::create(path)?; + write!(&mut file, "{{")?; + + for annotation in annotations { + if let Some(annotation_id) = CrashAnnotation::from_u32(annotation.id) { + if annotation_id == CrashAnnotation::PHCBaseAddress { + if let AnnotationData::ByteBuffer(buff) = &annotation.data { + write_phc_annotations(&mut file, buff)?; + } + + continue; + } + + let value = match type_of_annotation(annotation_id) { + CrashAnnotationType::String => match &annotation.data { + AnnotationData::String(string) => Some(escape_value(string.as_bytes())), + AnnotationData::ByteBuffer(buffer) => Some(escape_value(buffer)), + _ => None, + }, + CrashAnnotationType::Boolean => { + if let AnnotationData::ByteBuffer(buff) = &annotation.data { + if buff.len() == 1 { + Some(vec![if buff[0] != 0 { b'1' } else { b'0' }]) + } else { + None + } + } else { + None + } + } + CrashAnnotationType::U32 => { + read_numeric_annotation!(u32, &annotation.data) + } + CrashAnnotationType::U64 => { + read_numeric_annotation!(u64, &annotation.data) + } + CrashAnnotationType::USize => { + read_numeric_annotation!(usize, &annotation.data) + } + CrashAnnotationType::Object => None, // This cannot be found in memory + }; + + if let Some(value) = value { + if !value.is_empty() && should_include_annotation(annotation_id, &value) { + write!(&mut file, "\"{annotation_id:}\":\"")?; + file.write_all(&value)?; + write!(&mut file, "\",")?; + annotations_written += 1; + } + } + } + } + + if annotations_written > 0 { + // Drop the last comma + file.seek(SeekFrom::Current(-1))?; + } + writeln!(&mut file, "}}")?; + Ok(()) +} + +// Escapes the characters of a crash annotation so that they appear correctly +// within the JSON output, escaping non-visible characters and the like. This +// does not try to make the output valid UTF-8 because the input might be +// corrupted so there's no point in that. +fn escape_value(input: &[u8]) -> Vec { + let mut escaped = Vec::::with_capacity(input.len() + 2); + for &c in input { + if c <= 0x1f || c == b'\\' || c == b'"' { + escaped.extend(b"\\u00"); + escaped.push(hex_digit_as_ascii_char((c & 0x00f0) >> 4)); + escaped.push(hex_digit_as_ascii_char(c & 0x000f)); + } else { + escaped.push(c) + } + } + + escaped +} + +fn hex_digit_as_ascii_char(value: u8) -> u8 { + if value < 10 { + b'0' + value + } else { + b'a' + (value - 10) + } +} diff --git a/toolkit/crashreporter/crash_helper_server/src/crash_generation/windows.rs b/toolkit/crashreporter/crash_helper_server/src/crash_generation/windows.rs new file mode 100644 index 00000000000..391412551f0 --- /dev/null +++ b/toolkit/crashreporter/crash_helper_server/src/crash_generation/windows.rs @@ -0,0 +1,166 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use super::{finalize_crash_report, BreakpadProcessId, CrashGenerator}; + +use crash_helper_common::{messages, Pid}; +use std::{ + convert::TryInto, + fs::{create_dir_all, File}, + mem::{size_of, zeroed}, + os::windows::io::AsRawHandle, + path::PathBuf, + ptr::{null, null_mut}, +}; +use uuid::Uuid; +use windows_sys::Win32::{ + Foundation::{FALSE, HANDLE}, + System::{ + Diagnostics::Debug::{ + MiniDumpWithFullMemoryInfo, MiniDumpWithIndirectlyReferencedMemory, + MiniDumpWithProcessThreadData, MiniDumpWithUnloadedModules, MiniDumpWriteDump, + EXCEPTION_POINTERS, EXCEPTION_RECORD, MINIDUMP_EXCEPTION_INFORMATION, MINIDUMP_TYPE, + }, + SystemInformation::{ + VerSetConditionMask, VerifyVersionInfoW, OSVERSIONINFOEXW, VER_MAJORVERSION, + VER_MINORVERSION, VER_SERVICEPACKMAJOR, VER_SERVICEPACKMINOR, + }, + SystemServices::VER_GREATER_EQUAL, + Threading::{OpenProcess, PROCESS_ALL_ACCESS}, + }, +}; + +impl CrashGenerator { + pub(super) fn generate_wer_minidump( + &self, + message: messages::WindowsErrorReportingMinidump, + ) -> Result<(), ()> { + let (minidump_file, path) = self.create_minidump_file()?; + + let minidump_type: MINIDUMP_TYPE = self.get_minidump_type(); + let mut context = message.context; + let mut exception_records = message.exception_records; + let exception_records_ptr = link_exception_records(&mut exception_records); + + let handle = open_process(message.pid)?; + let mut exception_pointers = EXCEPTION_POINTERS { + ExceptionRecord: exception_records_ptr, + ContextRecord: &mut context as *mut _, + }; + + let exception = MINIDUMP_EXCEPTION_INFORMATION { + ThreadId: message.tid, + ExceptionPointers: &mut exception_pointers, + ClientPointers: FALSE, + }; + + let res = unsafe { + MiniDumpWriteDump( + handle, + message.pid, + minidump_file.as_raw_handle() as _, + minidump_type, + &exception, + /* UserStreamParam */ null(), + /* CallbackParam */ null(), + ) + }; + + if res != FALSE { + let process_id = BreakpadProcessId { + pid: message.pid, + handle, + }; + + finalize_crash_report( + process_id, + None, + &path, + super::MinidumpOrigin::WindowsErrorReporting, + ); + } + + Ok(()) + } + + fn get_minidump_type(&self) -> MINIDUMP_TYPE { + let mut minidump_type = MiniDumpWithFullMemoryInfo | MiniDumpWithUnloadedModules; + if mozbuild::config::NIGHTLY_BUILD { + // This is Nightly only because this doubles the size of minidumps based + // on the experimental data. + minidump_type |= MiniDumpWithProcessThreadData; + + // dbghelp.dll on Win7 can't handle overlapping memory regions so we only + // enable this feature on Win8 or later. + if is_windows8_or_later() { + // This allows us to examine heap objects referenced from stack objects + // at the cost of further doubling the size of minidumps. + minidump_type |= MiniDumpWithIndirectlyReferencedMemory; + } + } + minidump_type + } + + fn create_minidump_file(&self) -> Result<(File, PathBuf), ()> { + // Make sure that the target directory is present + create_dir_all(&self._minidump_path).map_err(|_| ())?; + + let uuid = Uuid::new_v4() + .as_hyphenated() + .encode_lower(&mut Uuid::encode_buffer()) + .to_string(); + let path = PathBuf::from(self._minidump_path.clone()).join(uuid + ".dmp"); + let file = File::create(&path).map_err(|_| ())?; + Ok((file, path)) + } +} + +fn link_exception_records(exception_records: &mut Vec) -> *mut EXCEPTION_RECORD { + let mut iter = exception_records.iter_mut().peekable(); + while let Some(exception_record) = iter.next() { + exception_record.ExceptionRecord = null_mut(); + + if let Some(next) = iter.peek_mut() { + exception_record.ExceptionRecord = *next as *mut _; + } + } + + if exception_records.is_empty() { + null_mut() + } else { + exception_records.as_mut_ptr() + } +} + +fn is_windows8_or_later() -> bool { + let mut info = OSVERSIONINFOEXW { + dwOSVersionInfoSize: size_of::().try_into().unwrap(), + dwMajorVersion: 6, + dwMinorVersion: 2, + ..unsafe { zeroed() } + }; + + unsafe { + let mut mask: u64 = 0; + let ge: u8 = VER_GREATER_EQUAL.try_into().unwrap(); + mask = VerSetConditionMask(mask, VER_MAJORVERSION, ge); + mask = VerSetConditionMask(mask, VER_MINORVERSION, ge); + mask = VerSetConditionMask(mask, VER_SERVICEPACKMAJOR, ge); + mask = VerSetConditionMask(mask, VER_SERVICEPACKMINOR, ge); + + VerifyVersionInfoW( + &mut info, + VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR, + mask, + ) != 0 + } +} + +fn open_process(pid: Pid) -> Result { + // SAFETY: No pointers involved, worst case we get an error + match unsafe { OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid) } { + 0 => Err(()), + handle => Ok(handle), + } +} diff --git a/toolkit/crashreporter/crash_helper_server/src/ipc_server.rs b/toolkit/crashreporter/crash_helper_server/src/ipc_server.rs new file mode 100644 index 00000000000..3b0c368f904 --- /dev/null +++ b/toolkit/crashreporter/crash_helper_server/src/ipc_server.rs @@ -0,0 +1,100 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use anyhow::Result; +use crash_helper_common::{ + errors::IPCError, messages, wait_for_events, IPCConnector, IPCEvent, IPCListener, Pid, +}; + +use crate::crash_generation::CrashGenerator; + +#[derive(PartialEq)] +pub enum IPCServerState { + Running, + ClientDisconnected, +} + +pub(crate) struct IPCServer { + listener: IPCListener, + connectors: Vec, + client_pid: Pid, +} + +impl IPCServer { + pub(crate) fn new( + client_pid: Pid, + listener: IPCListener, + connector: IPCConnector, + ) -> IPCServer { + IPCServer { + listener, + connectors: vec![connector], + client_pid, + } + } + + pub(crate) fn run( + &mut self, + generator: &mut CrashGenerator, + ) -> Result { + let events = wait_for_events(&mut self.listener, &mut self.connectors)?; + + for event in events { + match event { + IPCEvent::Connect(connector) => { + self.connectors.push(connector); + } + IPCEvent::Header(index, header) => { + let connector = self + .connectors + .get_mut(index) + .expect("Invalid connector index"); + let res = Self::handle_message(connector, &header, generator); + if let Err(error) = res { + log::error!( + "Error {error} while handling a message of {:?} kind", + header.kind + ); + } + } + IPCEvent::Disconnect(index) => { + let connector = self + .connectors + .get_mut(index) + .expect("Invalid connector index"); + if connector.endpoint_pid() == self.client_pid { + // The main process disconnected, leave + return Ok(IPCServerState::ClientDisconnected); + } else { + // This closes the connection + let _ = self.connectors.remove(index); + } + } + } + } + + Ok(IPCServerState::Running) + } + + fn handle_message( + connector: &mut IPCConnector, + header: &messages::Header, + generator: &mut CrashGenerator, + ) -> Result<()> { + let (data, ancillary_data) = connector.recv(header.size)?; + + let reply = generator.client_message( + header.kind, + &data, + ancillary_data, + connector.endpoint_pid(), + )?; + + if let Some(reply) = reply { + connector.send_message(reply.as_ref())?; + } + + Ok(()) + } +} diff --git a/toolkit/crashreporter/crash_helper_server/src/lib.rs b/toolkit/crashreporter/crash_helper_server/src/lib.rs new file mode 100644 index 00000000000..31f0fb429f4 --- /dev/null +++ b/toolkit/crashreporter/crash_helper_server/src/lib.rs @@ -0,0 +1,144 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#[cfg(any(target_os = "linux", target_os = "android"))] +extern crate rust_minidump_writer_linux; + +mod breakpad_crash_generator; +mod crash_generation; +mod ipc_server; +mod logging; +mod phc; + +use crash_helper_common::{BreakpadData, BreakpadRawData, IPCConnector, IPCListener, Pid}; +use std::ffi::{c_char, CStr, OsString}; + +use crash_generation::CrashGenerator; +use ipc_server::{IPCServer, IPCServerState}; +#[cfg(target_os = "android")] +use std::os::fd::{FromRawFd, OwnedFd, RawFd}; + +/// Runs the crash generator process logic, this includes the IPC used by +/// processes to signal that they crashed, the IPC used to retrieve crash +/// reports from the crash helper process and the logic used to generate the +/// actual minidumps. This function will return when the main process has +/// disconnected from the crash helper. +/// +/// # Safety +/// +/// `minidump_data`, `listener` and `pipe` must point to valid, +/// nul-terminated C strings. `breakpad_data` must be a valid file descriptor +/// (Linux) or point to a nul-terminated C string using either byte (macOS) +/// or wide characters (Windows). +#[cfg(not(target_os = "android"))] +#[no_mangle] +pub unsafe extern "C" fn crash_generator_logic_desktop( + client_pid: Pid, + breakpad_data: BreakpadRawData, + minidump_path: *const c_char, + listener: *const c_char, + pipe: *const c_char, +) -> i32 { + logging::init(); + + let breakpad_data = BreakpadData::new(breakpad_data); + let minidump_path = unsafe { CStr::from_ptr(minidump_path) } + .to_owned() + .into_string() + .unwrap(); + let minidump_path = OsString::from(minidump_path); + let listener = unsafe { CStr::from_ptr(listener) }; + let listener = IPCListener::deserialize(listener, client_pid) + .map_err(|error| { + log::error!("Could not parse the crash generator's listener (error: {error})"); + }) + .unwrap(); + let pipe = unsafe { CStr::from_ptr(pipe) }; + let connector = IPCConnector::deserialize(pipe) + .map_err(|error| { + log::error!("Could not parse the crash generator's connector (error: {error})"); + }) + .unwrap(); + + let crash_generator = CrashGenerator::new(client_pid, breakpad_data, minidump_path) + .map_err(|error| { + log::error!("Could not create the crash generator (error: {error})"); + error + }) + .unwrap(); + + let ipc_server = IPCServer::new(client_pid, listener, connector); + + main_loop(ipc_server, crash_generator) +} + +/// Runs the crash generator process logic, this includes the IPC used by +/// processes to signal that they crashed, the IPC used to retrieve crash +/// reports from the crash helper process and the logic used to generate the +/// actual minidumps. The logic will run in a separate thread and this +/// function will return immediately after launching it. +/// +/// # Safety +/// +/// `minidump_data` must point to valid, nul-terminated C strings. `listener` +/// and `server_pipe` must be valid file descriptors and `breakpad_data` must +/// also be a valid file descriptor compatible with Breakpad's crash generation +/// server. +#[cfg(target_os = "android")] +#[no_mangle] +pub unsafe extern "C" fn crash_generator_logic_android( + client_pid: Pid, + breakpad_data: BreakpadRawData, + minidump_path: *const c_char, + listener: RawFd, + pipe: RawFd, +) { + logging::init(); + + let breakpad_data = BreakpadData::new(breakpad_data); + let minidump_path = unsafe { CStr::from_ptr(minidump_path) } + .to_owned() + .into_string() + .unwrap(); + let minidump_path = OsString::from(minidump_path); + let crash_generator = CrashGenerator::new(client_pid, breakpad_data, minidump_path) + .map_err(|error| { + log::error!("Could not create the crash generator (error: {error})"); + error + }) + .unwrap(); + + let listener = unsafe { OwnedFd::from_raw_fd(listener) }; + let listener = IPCListener::from_fd(client_pid, listener) + .map_err(|error| { + log::error!("Could not use the listener (error: {error})"); + }) + .unwrap(); + let pipe = unsafe { OwnedFd::from_raw_fd(pipe) }; + let connector = IPCConnector::from_fd(pipe) + .map_err(|error| { + log::error!("Could not use the pipe (error: {error})"); + }) + .unwrap(); + let ipc_server = IPCServer::new(client_pid, listener, connector); + + // On Android the main thread is used to respond to the intents so we + // can't block it. Run the crash generation loop in a separate thread. + let _ = std::thread::spawn(move || main_loop(ipc_server, crash_generator)); +} + +fn main_loop(mut ipc_server: IPCServer, mut crash_generator: CrashGenerator) -> i32 { + loop { + match ipc_server.run(&mut crash_generator) { + Ok(_result @ IPCServerState::ClientDisconnected) => { + return 0; + } + Err(error) => { + log::error!("The crashhelper encountered an error, exiting (error: {error})"); + return -1; + } + _ => {} // Go on + } + } +} diff --git a/toolkit/crashreporter/crash_helper_server/src/logging.rs b/toolkit/crashreporter/crash_helper_server/src/logging.rs new file mode 100644 index 00000000000..13e2c3e198e --- /dev/null +++ b/toolkit/crashreporter/crash_helper_server/src/logging.rs @@ -0,0 +1,15 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#[cfg(target_os = "android")] +pub(crate) use android::init; + +#[cfg(target_os = "android")] +pub(crate) mod android; + +#[cfg(not(target_os = "android"))] +pub(crate) use env::init; + +#[cfg(not(target_os = "android"))] +pub(crate) mod env; diff --git a/toolkit/crashreporter/crash_helper_server/src/logging/android.rs b/toolkit/crashreporter/crash_helper_server/src/logging/android.rs new file mode 100644 index 00000000000..e62af5f5e88 --- /dev/null +++ b/toolkit/crashreporter/crash_helper_server/src/logging/android.rs @@ -0,0 +1,12 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +/// Initialize logging and place the logging file in +pub(crate) fn init() { + android_logger::init_once( + android_logger::Config::default() + .with_max_level(log::LevelFilter::Trace) + .with_tag("GeckoCrashHelper"), + ); +} diff --git a/toolkit/crashreporter/crash_helper_server/src/logging/env.rs b/toolkit/crashreporter/crash_helper_server/src/logging/env.rs new file mode 100644 index 00000000000..8c9a19109ee --- /dev/null +++ b/toolkit/crashreporter/crash_helper_server/src/logging/env.rs @@ -0,0 +1,58 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use cfg_if::cfg_if; +use mozbuild::config; +use std::{ + fs::{self, File}, + io, + path::PathBuf, +}; + +/// Initialize logging +pub(crate) fn init() { + let user_app_data_dir = guess_user_app_data_dir(); + let log_target = make_log_target(user_app_data_dir); + + env_logger::builder() + .filter_level(log::LevelFilter::Warn) + .parse_env( + env_logger::Env::new() + .filter("CRASH_HELPER_LOG") + .write_style("CRASH_HELPER_LOG_STYLE"), + ) + .target(env_logger::fmt::Target::Pipe(log_target)) + .init(); +} + +// The crash helper might be launched before Firefox has a chance to provide +// the UAppData special directory, so we generate its value autonomously here. +fn guess_user_app_data_dir() -> Option { + let home_dir = dirs::home_dir()?; + + cfg_if! { + if #[cfg(target_os = "linux")] { + Some(home_dir.join(".mozilla").join(config::MOZ_APP_NAME)) + } else if #[cfg(target_os = "macos")] { + Some(home_dir.join("Library").join("Application Support").join(config::MOZ_APP_BASENAME)) + } else if #[cfg(target_os = "windows")] { + Some(home_dir.join("AppData").join("Roaming").join(config::MOZ_APP_VENDOR).join(config::MOZ_APP_BASENAME)) + } else { + None + } + } +} + +fn make_log_target(log_directory: Option) -> Box { + if let Some(log_directory) = log_directory.map(|path| path.join("Crash Reports")) { + if fs::create_dir_all(&log_directory).is_ok() { + let log_path = log_directory.join(concat!(env!("CARGO_PKG_NAME"), ".log")); + if let Ok(log) = File::create(log_path) { + return Box::new(log); + } + } + } + + Box::new(io::stderr()) +} diff --git a/toolkit/crashreporter/crash_helper_server/src/phc.rs b/toolkit/crashreporter/crash_helper_server/src/phc.rs new file mode 100644 index 00000000000..c5da6a20472 --- /dev/null +++ b/toolkit/crashreporter/crash_helper_server/src/phc.rs @@ -0,0 +1,100 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// The types here must match the ones in memory/build/PHC.h + +use anyhow::{bail, Result}; +use std::{ + ffi::{c_char, c_void}, + mem::{size_of, MaybeUninit}, + slice, +}; + +#[repr(C)] +#[derive(Clone, Copy, PartialEq)] +#[allow(dead_code)] +pub(crate) enum Kind { + Unknown = 0, + NeverAllocatedPage = 1, + InUsePage = 2, + FreedPage = 3, + GuardPage = 4, +} + +const MAX_FRAMES: usize = 16; + +#[repr(C)] +pub(crate) struct StackTrace { + pub(crate) length: usize, + pub(crate) pcs: [*const c_void; MAX_FRAMES], + pub(crate) has_stack: c_char, +} + +#[repr(C)] +pub(crate) struct AddrInfo { + pub(crate) kind: Kind, + pub(crate) base_addr: *const c_void, + pub(crate) usable_size: usize, + pub(crate) alloc_stack: StackTrace, + pub(crate) free_stack: StackTrace, + pub(crate) phc_was_locked: c_char, +} + +impl AddrInfo { + pub(crate) fn from_bytes(buff: &[u8]) -> Result { + if buff.len() != size_of::() { + bail!( + "PHC AddrInfo structure size {} doesn't match expected size {}", + buff.len(), + size_of::() + ); + } + + let mut addr_info = MaybeUninit::::uninit(); + // SAFETY: MaybeUninit is always valid, even for padding bytes + let uninit_addr_info = unsafe { + slice::from_raw_parts_mut( + addr_info.as_mut_ptr() as *mut MaybeUninit, + size_of::(), + ) + }; + + for (index, &value) in buff.iter().enumerate() { + uninit_addr_info[index].write(value); + } + + let addr_info = unsafe { addr_info.assume_init() }; + if !addr_info.check_consistency() { + bail!("PHC AddrInfo structure is inconsistent"); + } + + Ok(addr_info) + } + + pub(crate) fn kind_as_str(&self) -> &'static str { + match self.kind { + Kind::Unknown => "Unknown(?!)", + Kind::NeverAllocatedPage => "NeverAllocatedPage", + Kind::InUsePage => "InUsePage(?!)", + Kind::FreedPage => "FreedPage", + Kind::GuardPage => "GuardPage", + } + } + + fn check_consistency(&self) -> bool { + let kind_value = self.kind as u32; + + if (kind_value > Kind::GuardPage as u32) + || (self.alloc_stack.length > MAX_FRAMES) + || (self.free_stack.length > MAX_FRAMES) + || (self.alloc_stack.has_stack > 1) + || (self.free_stack.has_stack > 1) + || (self.phc_was_locked > 1) + { + return false; + } + + true + } +} diff --git a/toolkit/crashreporter/google-breakpad/src/common/linux/file_id.cc b/toolkit/crashreporter/google-breakpad/src/common/linux/file_id.cc index 701a4f070f2..aaf5feb4aa0 100644 --- a/toolkit/crashreporter/google-breakpad/src/common/linux/file_id.cc +++ b/toolkit/crashreporter/google-breakpad/src/common/linux/file_id.cc @@ -46,6 +46,7 @@ #include "common/linux/linux_libc_support.h" #include "common/linux/memory_mapped_file.h" #include "common/using_std_string.h" +#include "google_breakpad/common/minidump_format.h" #include "third_party/lss/linux_syscall_support.h" namespace google_breakpad { diff --git a/toolkit/crashreporter/moz.build b/toolkit/crashreporter/moz.build index 7cd3ed8f4f5..66a13bece4d 100644 --- a/toolkit/crashreporter/moz.build +++ b/toolkit/crashreporter/moz.build @@ -18,6 +18,7 @@ JAR_MANIFESTS += ["jar.mn"] UNIFIED_SOURCES = [ "CrashAnnotations.cpp", + "ExtraFileParser.cpp", "nsExceptionHandlerUtils.cpp", ] @@ -30,6 +31,11 @@ include("/ipc/chromium/chromium-config.mozbuild") FINAL_LIBRARY = "xul" if CONFIG["MOZ_CRASHREPORTER"]: + if CONFIG["OS_ARCH"] != "WINNT": + USE_LIBS += [ + "breakpad_client", + ] + if CONFIG["OS_ARCH"] == "WINNT": DIRS += [ "breakpad-windows-libxul", @@ -58,10 +64,6 @@ if CONFIG["MOZ_CRASHREPORTER"]: "google-breakpad/src/processor", ] - UNIFIED_SOURCES += [ - "linux_utils.cc", - ] - EXPORTS += [ "linux_utils.h", ] @@ -71,13 +73,15 @@ if CONFIG["MOZ_CRASHREPORTER"]: if CONFIG["OS_TARGET"] != "Android": DIRS += ["client/app"] - - if CONFIG["OS_TARGET"] == "Android": + else: DIRS += ["minidump-analyzer/android"] DIRS += [ + "breakpad_wrapper", + "crash_helper", + "crash_helper_client", + "crash_helper_server", "mozannotation_client", - "mozannotation_server", ] TEST_DIRS += ["test"] @@ -118,7 +122,9 @@ if CONFIG["MOZ_CRASHREPORTER"]: "google-breakpad/src", ] - USE_LIBS += ["jsoncpp"] + USE_LIBS += [ + "jsoncpp", + ] PYTHON_UNITTEST_MANIFESTS += [ "tools/python.toml", diff --git a/toolkit/crashreporter/mozannotation_client/Cargo.toml b/toolkit/crashreporter/mozannotation_client/Cargo.toml index 0586c9b9692..dff1e392082 100644 --- a/toolkit/crashreporter/mozannotation_client/Cargo.toml +++ b/toolkit/crashreporter/mozannotation_client/Cargo.toml @@ -8,4 +8,3 @@ license = "MPL-2.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -nsstring = { path = "../../../xpcom/rust/nsstring/" } diff --git a/toolkit/crashreporter/mozannotation_client/cbindgen.toml b/toolkit/crashreporter/mozannotation_client/cbindgen.toml index 59b81645c0e..6802953fc5f 100644 --- a/toolkit/crashreporter/mozannotation_client/cbindgen.toml +++ b/toolkit/crashreporter/mozannotation_client/cbindgen.toml @@ -11,5 +11,5 @@ language = "C++" include_guard = "mozannotation_client_ffi_generated_h" includes = ["nsStringFwd.h"] -[export.rename] -"ThinVec" = "nsTArray" +[export] +exclude = ["nsCString"] diff --git a/toolkit/crashreporter/mozannotation_client/src/lib.rs b/toolkit/crashreporter/mozannotation_client/src/lib.rs index 2df133c4b94..526ef86c7d3 100644 --- a/toolkit/crashreporter/mozannotation_client/src/lib.rs +++ b/toolkit/crashreporter/mozannotation_client/src/lib.rs @@ -2,7 +2,6 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ -use nsstring::nsCString; use std::{ alloc::{self, Layout}, cmp::min, @@ -74,6 +73,10 @@ impl AnnotationTable { pub fn len(&self) -> usize { self.data.len() } + + pub fn is_empty(&self) -> bool { + self.data.is_empty() + } } pub type AnnotationMutex = Mutex; @@ -99,10 +102,10 @@ extern "C" { } #[cfg(target_os = "windows")] -pub const ANNOTATION_SECTION: &'static [u8; 8] = b"mozannot"; +pub const ANNOTATION_SECTION: &[u8; 8] = b"mozannot"; #[cfg(target_os = "macos")] -pub const ANNOTATION_SECTION: &'static [u8; 16] = b"mozannotation\0\0\0"; +pub const ANNOTATION_SECTION: &[u8; 16] = b"mozannotation\0\0\0"; // TODO: Use the following constants in the assembly below when constant // expressions are stabilized: https://github.com/rust-lang/rust/issues/93332 @@ -256,7 +259,7 @@ fn store_annotation(id: u32, contents: AnnotationContents, address: *const T) let len = len as usize; let align = min(usize::next_power_of_two(len), 32); unsafe { - let layout = Layout::from_size_align_unchecked(len as usize, align); + let layout = Layout::from_size_align_unchecked(len, align); let src = address as *mut u8; let dst = alloc::alloc(layout); copy_nonoverlapping(src, dst, len); @@ -302,6 +305,10 @@ fn store_annotation(id: u32, contents: AnnotationContents, address: *const T) old } +// Dummy definition so that we don't have to import the nsstring crate. +#[allow(non_camel_case_types)] +pub struct nsCString {} + /// Register a pointer to an nsCString string. /// /// Returns the value of the previously registered annotation or null. @@ -359,8 +366,12 @@ pub extern "C" fn mozannotation_register_cstring(id: u32, address: *const c_char /// the crate, and register a pointer to it. /// /// This function will be exposed to C++ +/// +/// # Safety +/// +/// `address` must point to a valid nul-terminated C string. #[no_mangle] -pub extern "C" fn mozannotation_record_cstring(id: u32, address: *const c_char) { +pub unsafe extern "C" fn mozannotation_record_cstring(id: u32, address: *const c_char) { let len = unsafe { CStr::from_ptr(address).to_bytes().len() }; store_annotation(id, AnnotationContents::OwnedByteBuffer(len as u32), address); } @@ -402,8 +413,15 @@ pub extern "C" fn mozannotation_unregister(id: u32) -> *const c_void { /// if it hasn't. /// /// This function will be exposed to C++ +/// +/// # Safety +/// +/// `contents` must point to an object of type [`AnnotationContents`] #[no_mangle] -pub extern "C" fn mozannotation_get_contents(id: u32, contents: *mut AnnotationContents) -> usize { +pub unsafe extern "C" fn mozannotation_get_contents( + id: u32, + contents: *mut AnnotationContents, +) -> usize { let annotations = &MOZANNOTATIONS.lock().unwrap().data; if let Some(annotation) = annotations.iter().find(|e| e.id == id) { if annotation.contents == AnnotationContents::Empty { @@ -414,7 +432,7 @@ pub extern "C" fn mozannotation_get_contents(id: u32, contents: *mut AnnotationC return annotation.address; } - return 0; + 0 } #[no_mangle] diff --git a/toolkit/crashreporter/mozannotation_server/Cargo.toml b/toolkit/crashreporter/mozannotation_server/Cargo.toml index bd3fb283bf1..11bc257189e 100644 --- a/toolkit/crashreporter/mozannotation_server/Cargo.toml +++ b/toolkit/crashreporter/mozannotation_server/Cargo.toml @@ -10,8 +10,7 @@ license = "MPL-2.0" [dependencies] mozannotation_client = { path = "../mozannotation_client/" } process_reader = { path = "../process_reader/" } -thin-vec = { version = "0.2.7", features = ["gecko-ffi"] } -thiserror = "1.0.38" +thiserror = "2" [target."cfg(any(target_os = \"linux\", target_os = \"android\"))".dependencies] -memoffset = "0.8" +memoffset = "0.9" diff --git a/toolkit/crashreporter/mozannotation_server/src/lib.rs b/toolkit/crashreporter/mozannotation_server/src/lib.rs index bce2d5108f4..3fef0dcf5e1 100644 --- a/toolkit/crashreporter/mozannotation_server/src/lib.rs +++ b/toolkit/crashreporter/mozannotation_server/src/lib.rs @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ -mod errors; +pub mod errors; use crate::errors::*; #[cfg(any(target_os = "linux", target_os = "android"))] @@ -16,60 +16,27 @@ use mozannotation_client::{Annotation, AnnotationContents, AnnotationMutex}; #[cfg(any(target_os = "linux", target_os = "android"))] use mozannotation_client::{MozAnnotationNote, ANNOTATION_NOTE_NAME, ANNOTATION_TYPE}; use std::cmp::min; -use std::iter::FromIterator; +use std::ffi::CString; use std::mem::{size_of, ManuallyDrop}; -use std::ptr::null_mut; -use thin_vec::ThinVec; -#[repr(C)] -#[derive(Debug)] pub enum AnnotationData { Empty, - ByteBuffer(ThinVec), + ByteBuffer(Vec), + String(CString), } -#[repr(C)] -#[derive(Debug)] pub struct CAnnotation { - id: u32, - data: AnnotationData, + #[allow(dead_code)] // This is implicitly stored to and used externally + pub id: u32, + pub data: AnnotationData, } pub type ProcessHandle = process_reader::ProcessHandle; -/// Return the annotations of a given process. -/// -/// This function will be exposed to C++ -#[no_mangle] -pub extern "C" fn mozannotation_retrieve( - process: usize, - max_annotations: usize, -) -> *mut ThinVec { - let result = retrieve_annotations(process as _, max_annotations); - match result { - // Leak the object as it will be owned by the C++ code from now on - Ok(annotations) => Box::into_raw(annotations) as *mut _, - Err(_) => null_mut(), - } -} - -/// Free the annotations returned by `mozannotation_retrieve()`. -/// -/// # Safety -/// -/// `ptr` must contain the value returned by a call to -/// `mozannotation_retrieve()` and be called only once. -#[no_mangle] -pub unsafe extern "C" fn mozannotation_free(ptr: *mut ThinVec) { - // The annotation vector will be automatically destroyed when the contents - // of this box are automatically dropped at the end of the function. - let _box = Box::from_raw(ptr); -} - pub fn retrieve_annotations( process: ProcessHandle, max_annotations: usize, -) -> Result>, AnnotationsRetrievalError> { +) -> Result, AnnotationsRetrievalError> { let reader = ProcessReader::new(process)?; let address = find_annotations(&reader)?; @@ -90,7 +57,7 @@ pub fn retrieve_annotations( let vec_pointer = annotation_table.get_ptr(); let length = annotation_table.len(); - let mut annotations = ThinVec::::with_capacity(min(max_annotations, length)); + let mut annotations = Vec::::with_capacity(min(max_annotations, length)); for i in 0..length { let annotation_address = unsafe { vec_pointer.add(i) }; @@ -99,7 +66,7 @@ pub fn retrieve_annotations( } } - Ok(Box::new(annotations)) + Ok(annotations) } fn find_annotations(reader: &ProcessReader) -> Result { @@ -123,14 +90,14 @@ fn find_annotations(reader: &ProcessReader) -> Result {} AnnotationContents::NSCStringPointer => { let string = copy_nscstring(reader, raw_annotation.address)?; - annotation.data = AnnotationData::ByteBuffer(string); + annotation.data = AnnotationData::String(string); } AnnotationContents::CStringPointer => { let string = copy_null_terminated_string_pointer(reader, raw_annotation.address)?; - annotation.data = AnnotationData::ByteBuffer(string); + annotation.data = AnnotationData::String(string); } AnnotationContents::CString => { - let string = copy_null_terminated_string(reader, raw_annotation.address)?; - annotation.data = AnnotationData::ByteBuffer(string); + annotation.data = + AnnotationData::String(reader.copy_null_terminated_string(raw_annotation.address)?); } AnnotationContents::ByteBuffer(size) | AnnotationContents::OwnedByteBuffer(size) => { - let string = copy_bytebuffer(reader, raw_annotation.address, size)?; - annotation.data = AnnotationData::ByteBuffer(string); + let buffer = copy_bytebuffer(reader, raw_annotation.address, size)?; + annotation.data = AnnotationData::ByteBuffer(buffer); } }; @@ -180,23 +147,15 @@ fn read_annotation( fn copy_null_terminated_string_pointer( reader: &ProcessReader, address: usize, -) -> Result, process_reader::error::ReadError> { +) -> Result { let buffer_address = reader.copy_object::(address)?; - copy_null_terminated_string(reader, buffer_address) -} - -fn copy_null_terminated_string( - reader: &ProcessReader, - address: usize, -) -> Result, process_reader::error::ReadError> { - let string = reader.copy_null_terminated_string(address)?; - Ok(ThinVec::::from(string.as_bytes())) + reader.copy_null_terminated_string(buffer_address) } fn copy_nscstring( reader: &ProcessReader, address: usize, -) -> Result, process_reader::error::ReadError> { +) -> Result { // HACK: This assumes the layout of the nsCString object let length_address = address + size_of::(); let length = reader.copy_object::(length_address)?; @@ -205,15 +164,19 @@ fn copy_nscstring( let data_address = reader.copy_object::(address)?; let mut vec = reader.copy_array::(data_address, length as _)?; - // Ensure that the string contains no nul characters. + // We can't assume the string is a valid C string, as it might have + // been corrupted during the crash. Ensure that it contains no nul + // characters. let nul_byte_pos = vec.iter().position(|&c| c == 0); if let Some(nul_byte_pos) = nul_byte_pos { vec.truncate(nul_byte_pos); } - Ok(ThinVec::from(vec)) + // SAFETY: This is safe because we verified that there are no nul + // characters inside the string. + Ok(unsafe { CString::from_vec_unchecked(vec) }) } else { - Ok(ThinVec::::new()) + Ok(CString::default()) } } @@ -221,7 +184,6 @@ fn copy_bytebuffer( reader: &ProcessReader, address: usize, size: u32, -) -> Result, process_reader::error::ReadError> { - let value = reader.copy_array::(address, size as _)?; - Ok(ThinVec::::from_iter(value.into_iter())) +) -> Result, process_reader::error::ReadError> { + reader.copy_array::(address, size as _) } diff --git a/toolkit/crashreporter/mozwer-rust/Cargo.toml b/toolkit/crashreporter/mozwer-rust/Cargo.toml index 89cf3d43561..fe86aea692c 100644 --- a/toolkit/crashreporter/mozwer-rust/Cargo.toml +++ b/toolkit/crashreporter/mozwer-rust/Cargo.toml @@ -6,11 +6,11 @@ edition = "2018" license = "MPL-2.0" [dependencies] +crash_helper_client = { path = "../crash_helper_client" } libc = "0.2.0" mozilla-central-workspace-hack = { version = "0.1", features = [ "mozwer_s", ], optional = true } -process_reader = { path = "../process_reader/" } rust-ini = "0.10" serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0" } @@ -22,6 +22,7 @@ features = [ "Wdk_System_Threading", "Win32_Foundation", "Win32_Security", + "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_Diagnostics_Debug", "Win32_System_ErrorReporting", @@ -29,7 +30,6 @@ features = [ "Win32_System_ProcessStatus", "Win32_System_SystemInformation", "Win32_System_SystemServices", - "Win32_System_Threading", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging", ] diff --git a/toolkit/crashreporter/mozwer-rust/lib.rs b/toolkit/crashreporter/mozwer-rust/lib.rs index 61fc8944622..4579350a35f 100644 --- a/toolkit/crashreporter/mozwer-rust/lib.rs +++ b/toolkit/crashreporter/mozwer-rust/lib.rs @@ -2,20 +2,20 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use crash_helper_client::report_external_exception; use ini::Ini; use libc::time; -use process_reader::ProcessReader; use serde::Serialize; use serde_json::ser::to_writer; use std::convert::TryInto; use std::ffi::{c_void, OsString}; use std::fs::{read_to_string, DirBuilder, File, OpenOptions}; use std::io::{BufRead, BufReader, Write}; -use std::mem::{size_of, transmute, zeroed}; +use std::mem::{size_of, zeroed}; use std::os::windows::ffi::{OsStrExt, OsStringExt}; -use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle, RawHandle}; +use std::os::windows::io::AsRawHandle; use std::path::{Path, PathBuf}; -use std::ptr::{addr_of, null, null_mut}; +use std::ptr::{null, null_mut}; use std::slice::from_raw_parts; use uuid::Uuid; use windows_sys::core::{HRESULT, PWSTR}; @@ -24,7 +24,7 @@ use windows_sys::Win32::{ Foundation::{ CloseHandle, GetLastError, SetLastError, BOOL, ERROR_INSUFFICIENT_BUFFER, ERROR_SUCCESS, EXCEPTION_BREAKPOINT, E_UNEXPECTED, FALSE, FILETIME, HANDLE, HWND, LPARAM, MAX_PATH, - STATUS_SUCCESS, S_OK, TRUE, WAIT_OBJECT_0, + STATUS_SUCCESS, S_OK, TRUE, }, Security::{ GetSidSubAuthority, GetSidSubAuthorityCount, GetTokenInformation, IsTokenRestricted, @@ -34,12 +34,9 @@ use windows_sys::Win32::{ System::Diagnostics::Debug::{ GetThreadContext, MiniDumpWithFullMemoryInfo, MiniDumpWithIndirectlyReferencedMemory, MiniDumpWithProcessThreadData, MiniDumpWithUnloadedModules, MiniDumpWriteDump, - WriteProcessMemory, EXCEPTION_POINTERS, MINIDUMP_EXCEPTION_INFORMATION, MINIDUMP_TYPE, + EXCEPTION_POINTERS, MINIDUMP_EXCEPTION_INFORMATION, MINIDUMP_TYPE, }, System::ErrorReporting::WER_RUNTIME_EXCEPTION_INFORMATION, - System::Memory::{ - VirtualAllocEx, VirtualFreeEx, MEM_COMMIT, MEM_RELEASE, MEM_RESERVE, PAGE_READWRITE, - }, System::ProcessStatus::K32GetModuleFileNameExW, System::SystemInformation::{ VerSetConditionMask, VerifyVersionInfoW, OSVERSIONINFOEXW, VER_MAJORVERSION, @@ -47,9 +44,8 @@ use windows_sys::Win32::{ }, System::SystemServices::{SECURITY_MANDATORY_MEDIUM_RID, VER_GREATER_EQUAL}, System::Threading::{ - CreateProcessW, CreateRemoteThread, GetProcessId, GetProcessTimes, GetThreadId, - OpenProcess, OpenProcessToken, OpenThread, TerminateProcess, WaitForSingleObject, - CREATE_NO_WINDOW, CREATE_UNICODE_ENVIRONMENT, LPTHREAD_START_ROUTINE, + CreateProcessW, GetProcessId, GetProcessTimes, GetThreadId, OpenProcess, OpenProcessToken, + OpenThread, TerminateProcess, CREATE_NO_WINDOW, CREATE_UNICODE_ENVIRONMENT, NORMAL_PRIORITY_CLASS, PROCESS_ALL_ACCESS, PROCESS_BASIC_INFORMATION, PROCESS_INFORMATION, STARTUPINFOW, THREAD_GET_CONTEXT, }, @@ -67,15 +63,6 @@ type PDWORD = *mut DWORD; #[allow(non_camel_case_types)] type PWER_RUNTIME_EXCEPTION_INFORMATION = *mut WER_RUNTIME_EXCEPTION_INFORMATION; -/* The following struct must be kept in sync with the identically named one in - * nsExceptionHandler.h. WER will use it to communicate with the main process - * when a child process is encountered. */ -#[repr(C)] -struct WindowsErrorReportingData { - child_pid: DWORD, - minidump_name: [u8; 40], -} - // This value comes from GeckoProcessTypes.h static MAIN_PROCESS_TYPE: u32 = 0; @@ -174,14 +161,17 @@ fn out_of_process_exception_event_callback( } let process = exception_information.hProcess; - let application_info = ApplicationInformation::from_process(process)?; let process_type: u32 = (context as usize).try_into().map_err(|_| ())?; - let startup_time = get_startup_time(process)?; - let crash_report = CrashReport::new(&application_info, startup_time, is_ui_hang); - crash_report.write_minidump(exception_information)?; if process_type == MAIN_PROCESS_TYPE { match is_sandboxed_process(process) { - Ok(false) => handle_main_process_crash(crash_report, &application_info), + Ok(false) => { + let application_info = ApplicationInformation::from_process(process)?; + let startup_time = get_startup_time(process)?; + let crash_report = CrashReport::new(&application_info, startup_time, is_ui_hang); + crash_report.write_minidump(exception_information)?; + + handle_main_process_crash(crash_report, &application_info) + } _ => { // The parent process should never be sandboxed, bail out so the // process which is impersonating it gets killed right away. Also @@ -190,7 +180,7 @@ fn out_of_process_exception_event_callback( } } } else { - handle_child_process_crash(crash_report, process) + handle_child_process_crash(exception_information) } } @@ -245,87 +235,24 @@ fn handle_main_process_crash( Ok(()) } -fn handle_child_process_crash(crash_report: CrashReport, child_process: HANDLE) -> Result<()> { - let parent_process = get_parent_process(child_process)?; - let process_reader = ProcessReader::new(parent_process).map_err(|_e| ())?; - let libxul_address = process_reader.find_module("xul.dll").map_err(|_e| ())?; - let wer_notify_proc = process_reader - .find_section(libxul_address, b"mozwerpt") - .map_err(|_e| ())?; - let wer_notify_proc = unsafe { transmute::<_, LPTHREAD_START_ROUTINE>(wer_notify_proc) }; - - let wer_data = WindowsErrorReportingData { - child_pid: get_process_id(child_process)?, - minidump_name: crash_report.get_minidump_name(), - }; - let address = copy_object_into_process(parent_process, wer_data)?; - notify_main_process(parent_process, wer_notify_proc, address) -} - -fn copy_object_into_process(process: HANDLE, data: T) -> Result<*mut T> { - let address = unsafe { - VirtualAllocEx( - process, - null(), - size_of::(), - MEM_RESERVE | MEM_COMMIT, - PAGE_READWRITE, - ) - }; - - if address.is_null() { - return Err(()); - } - - let res = unsafe { - WriteProcessMemory( - process, - address, - addr_of!(data) as *const _, - size_of::(), - null_mut(), - ) - }; - - if res == 0 { - unsafe { VirtualFreeEx(process, address as *mut _, 0, MEM_RELEASE) }; - Err(()) - } else { - Ok(address as *mut T) - } -} - -fn notify_main_process( - process: HANDLE, - wer_notify_proc: LPTHREAD_START_ROUTINE, - address: *mut WindowsErrorReportingData, +fn handle_child_process_crash( + exception_information: PWER_RUNTIME_EXCEPTION_INFORMATION, ) -> Result<()> { - let thread = unsafe { - CreateRemoteThread( - process, - null_mut(), - 0, - wer_notify_proc, - address as LPVOID, - 0, - null_mut(), - ) - }; + let process = unsafe { (*exception_information).hProcess }; + let process_id = get_process_id(process)?; + let thread = unsafe { (*exception_information).hThread }; + let thread_id = get_thread_id(thread)?; + let parent_process = get_parent_process(process)?; + let parent_pid = get_process_id(parent_process)?; - if thread == 0 { - unsafe { VirtualFreeEx(process, address as *mut _, 0, MEM_RELEASE) }; - return Err(()); - } - - // From this point on the memory pointed to by address is owned by the - // thread we've created in the main process, so we don't free it. - - let thread = unsafe { OwnedHandle::from_raw_handle(thread as RawHandle) }; - - // Don't wait forever as we want the process to get killed eventually - let res = unsafe { WaitForSingleObject(thread.as_raw_handle() as HANDLE, 5000) }; - if res != WAIT_OBJECT_0 { - return Err(()); + unsafe { + report_external_exception( + parent_pid, + process_id, + thread_id, + &raw mut (*exception_information).exceptionRecord, + &raw mut (*exception_information).context, + ); } Ok(()) @@ -366,6 +293,13 @@ fn get_process_id(process: HANDLE) -> Result { } } +fn get_thread_id(thread: HANDLE) -> Result { + match unsafe { GetThreadId(thread) } { + 0 => Err(()), + tid => Ok(tid), + } +} + fn get_process_handle(pid: DWORD) -> Result { let handle = unsafe { OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid) }; if handle != 0 { @@ -693,11 +627,6 @@ impl CrashReport { self.get_pending_path().join(self.uuid.to_string() + ".dmp") } - fn get_minidump_name(&self) -> [u8; 40] { - let bytes = (self.uuid.to_string() + ".dmp").into_bytes(); - bytes[0..40].try_into().unwrap() - } - fn get_extra_file_path(&self) -> PathBuf { self.get_pending_path() .join(self.uuid.to_string() + ".extra") diff --git a/toolkit/crashreporter/mozwer-rust/moz.build b/toolkit/crashreporter/mozwer-rust/moz.build index 18c8039e689..098a191914c 100644 --- a/toolkit/crashreporter/mozwer-rust/moz.build +++ b/toolkit/crashreporter/mozwer-rust/moz.build @@ -1,11 +1,11 @@ RustLibrary("mozwer_s") OS_LIBS += [ + "advapi32", "dbghelp", "kernel32", "ntdll", "ole32", - "psapi", "shell32", "user32", "userenv", diff --git a/toolkit/crashreporter/nsDummyExceptionHandler.cpp b/toolkit/crashreporter/nsDummyExceptionHandler.cpp index 73afbc5b6d0..06f14b1ee8f 100644 --- a/toolkit/crashreporter/nsDummyExceptionHandler.cpp +++ b/toolkit/crashreporter/nsDummyExceptionHandler.cpp @@ -118,11 +118,6 @@ nsresult AppendAppNotesToCrashReport(const nsACString& data) { bool GetAnnotation(const nsACString& key, nsACString& data) { return false; } -void GetAnnotation(ProcessId childPid, Annotation annotation, - nsACString& outStr) { - return; -} - nsresult RegisterAppMemory(void* ptr, size_t length) { return NS_ERROR_NOT_IMPLEMENTED; } @@ -203,13 +198,27 @@ bool WriteExtraFile(const nsAString& id, const AnnotationTable& annotations) { return false; } -void OOPInit() {} +#if defined(MOZ_WIDGET_ANDROID) +void SetNotificationPipeForChild(FileHandle breakpadFd, + FileHandle crashHelperFd) {} +#endif // defined(MOZ_WIDGET_ANDROID) CrashPipeType GetChildNotificationPipe() { return nullptr; } +#if defined(MOZ_WIDGET_ANDROID) +void SetCrashHelperPipes(FileHandle breakpadFd, FileHandle crashHelperFd) {} +#endif // defined(MOZ_WIDGET_ANDROID) + +#if defined(XP_LINUX) && !defined(MOZ_WIDGET_ANDROID) +MOZ_EXPORT ProcessId GetCrashHelperPid() { return -1; }; +#endif // XP_LINUX && !defined(MOZ_WIDGET_ANDROID) + bool GetLastRunCrashID(nsAString& id) { return false; } -bool SetRemoteExceptionHandler(CrashPipeType aCrashPipe) { return false; } +bool SetRemoteExceptionHandler(CrashPipeType aCrashPipe, + ProcessId aCrashHelperPid) { + return false; +} bool TakeMinidumpForChild(ProcessId childPid, nsIFile** dump, AnnotationTable& aAnnotations) { diff --git a/toolkit/crashreporter/nsExceptionHandler.cpp b/toolkit/crashreporter/nsExceptionHandler.cpp index a317f58ab6f..047e71e7237 100644 --- a/toolkit/crashreporter/nsExceptionHandler.cpp +++ b/toolkit/crashreporter/nsExceptionHandler.cpp @@ -6,27 +6,26 @@ #include "nsExceptionHandler.h" #include "nsExceptionHandlerUtils.h" +#include "ExtraFileParser.h" #include "json/json.h" #include "nsAppDirectoryServiceDefs.h" #include "nsComponentManagerUtils.h" #include "nsDirectoryServiceDefs.h" #include "nsDirectoryService.h" +#include "nsIFileStreams.h" +#include "nsNetUtil.h" #include "nsString.h" -#include "nsTHashMap.h" #include "mozilla/ArrayUtils.h" #include "mozilla/DebugOnly.h" #include "mozilla/EnumeratedRange.h" #include "mozilla/Services.h" #include "nsIObserverService.h" #include "mozilla/Unused.h" -#include "mozilla/UniquePtr.h" -#include "mozilla/Printf.h" #include "mozilla/RuntimeExceptionModule.h" #include "mozilla/ScopeExit.h" #include "mozilla/Sprintf.h" #include "mozilla/StaticPrefs_browser.h" -#include "mozilla/StaticMutex.h" #include "mozilla/SyncRunnable.h" #include "mozilla/ToString.h" #include "mozilla/TimeStamp.h" @@ -36,12 +35,11 @@ #include "nsThreadUtils.h" #include "nsThread.h" #include "jsfriendapi.h" -#include "private/pprio.h" #include "base/process_util.h" #include "common/basictypes.h" #include "mozilla/toolkit/crashreporter/mozannotation_client_ffi_generated.h" -#include "mozilla/toolkit/crashreporter/mozannotation_server_ffi_generated.h" +#include "mozilla/crash_helper_client_ffi_generated.h" #ifdef MOZ_BACKGROUNDTASKS # include "mozilla/BackgroundTasks.h" @@ -83,12 +81,16 @@ # include "mac_utils.h" #elif defined(XP_LINUX) # include "nsIINIParser.h" +# if defined(MOZ_WIDGET_ANDROID) +# include "common/linux/eintr_wrapper.h" +# else +# include // For prctl() and PR_SET_PTRACER +# endif // defined(MOZ_WIDGET_ANDROID) # include "common/linux/linux_libc_support.h" # include "third_party/lss/linux_syscall_support.h" # include "breakpad-client/linux/crash_generation/client_info.h" # include "breakpad-client/linux/crash_generation/crash_generation_server.h" # include "breakpad-client/linux/handler/exception_handler.h" -# include "common/linux/eintr_wrapper.h" # include # include # include "sys/sysinfo.h" @@ -118,7 +120,6 @@ #include #include "mozilla/Mutex.h" #include "nsDebug.h" -#include "nsCRT.h" #include "nsIFile.h" #include "mozilla/IOInterposer.h" @@ -146,6 +147,8 @@ using google_breakpad::PageAllocator; #endif using namespace mozilla; +#ifdef MOZ_PHC + namespace mozilla::phc { // Global instance that is retrieved by the process generating the crash report @@ -153,6 +156,8 @@ MOZ_GLOBINIT mozilla::phc::AddrInfo gAddrInfo; } // namespace mozilla::phc +#endif // defined(MOZ_PHC) + namespace CrashReporter { #ifdef XP_WIN @@ -163,6 +168,7 @@ typedef std::wstring xpstring; # define XP_STRLEN(x) wcslen(x) # define my_strlen strlen # define my_memchr memchr +# define CRASH_HELPER_FILENAME u"crashhelper.exe"_ns # define CRASH_REPORTER_FILENAME u"crashreporter.exe"_ns # define XP_PATH_SEPARATOR L"\\" # define XP_PATH_SEPARATOR_CHAR L'\\' @@ -176,6 +182,7 @@ typedef char XP_CHAR; typedef std::string xpstring; # define XP_TEXT(x) x # define CONVERT_XP_CHAR_TO_UTF16(x) NS_ConvertUTF8toUTF16(x) +# define CRASH_HELPER_FILENAME u"crashhelper"_ns # define CRASH_REPORTER_FILENAME u"crashreporter"_ns # define XP_PATH_SEPARATOR "/" # define XP_PATH_SEPARATOR_CHAR '/' @@ -206,51 +213,6 @@ typedef std::string xpstring; # define MAYBE_UNUSED #endif // defined(__GNUC__) -#if defined(XP_LINUX) && defined(MOZ_OXIDIZED_BREAKPAD) -class ChildProcessAuxvStore { - public: - static ChildProcessAuxvStore& global() { - static ChildProcessAuxvStore instance; - return instance; - } - void Add(pid_t aChildPid, const DirectAuxvDumpInfo& aAuxvInfo) { - std::lock_guard lock(mMutex); - mMap.emplace(aChildPid, aAuxvInfo); - } - void Remove(pid_t aChildPid) { - std::lock_guard lock(mMutex); - mMap.erase(aChildPid); - } - bool Get(pid_t aChildPid, DirectAuxvDumpInfo* aAuxvInfo) { - std::lock_guard lock(mMutex); - auto entry = mMap.find(aChildPid); - if (entry == mMap.end()) { - return false; - } - *aAuxvInfo = entry->second; - return true; - } - - private: - std::mutex mMutex; - std::unordered_map mMap; -}; - -void GetCurrentProcessAuxvInfo(DirectAuxvDumpInfo* aAuxvInfo) { - aAuxvInfo->program_header_count = getauxval(AT_PHNUM); - aAuxvInfo->program_header_address = getauxval(AT_PHDR); - aAuxvInfo->linux_gate_address = getauxval(AT_SYSINFO_EHDR); - aAuxvInfo->entry_address = getauxval(AT_ENTRY); -} -void RegisterChildAuxvInfo(pid_t aChildPid, - const DirectAuxvDumpInfo& aAuxvInfo) { - ChildProcessAuxvStore::global().Add(aChildPid, aAuxvInfo); -} -void UnregisterChildAuxvInfo(pid_t aChildPid) { - ChildProcessAuxvStore::global().Remove(aChildPid); -} -#endif // defined(XP_LINUX) && defined(MOZ_OXIDIZED_BREAKPAD) - #ifndef XP_LINUX static const XP_CHAR dumpFileExtension[] = XP_TEXT(".dmp"); #endif @@ -261,12 +223,14 @@ MOZ_RUNINIT static std::optional defaultMemoryReportPath = {}; static const char kCrashMainID[] = "crash.main.3\n"; +static CrashHelperClient* gCrashHelperClient = nullptr; static google_breakpad::ExceptionHandler* gExceptionHandler = nullptr; static mozilla::Atomic gEncounteredChildException(false); MOZ_CONSTINIT static nsCString gServerURL; MOZ_RUNINIT static xpstring pendingDirectory; MOZ_RUNINIT static xpstring crashReporterPath; +MOZ_RUNINIT static xpstring crashHelperPath; MOZ_RUNINIT static xpstring memoryReportPath; // Where crash events should go. @@ -294,6 +258,8 @@ static char* androidUserSerial = nullptr; static const char* androidStartServiceCommand = nullptr; #endif +static ProcessId gCrashHelperPid = 0; + // this holds additional data sent via the API static Mutex* notesFieldLock; static nsCString* notesField = nullptr; @@ -316,33 +282,20 @@ static bool isSafeToDump = false; // Whether to include heap regions of the crash context. static bool sIncludeContextHeap = false; -// OOP crash reporting -static CrashGenerationServer* crashServer; // chrome process has this - static std::terminate_handler oldTerminateHandler = nullptr; #if defined(XP_WIN) || defined(XP_MACOSX) -static char* childCrashNotifyPipe; +MOZ_RUNINIT static nsCString childCrashNotifyPipe; #elif defined(XP_LINUX) static int serverSocketFd = -1; static int clientSocketFd = -1; - +# if defined(MOZ_WIDGET_ANDROID) +static int crashHelperClientFd = -1; +# endif // defined(MOZ_WIDGET_ANDROID) #endif -// |dumpMapLock| must protect all access to |pidToMinidump|. -static Mutex* dumpMapLock; -struct ChildProcessData : public nsUint32HashKey { - explicit ChildProcessData(KeyTypePointer aKey) - : nsUint32HashKey(aKey), annotations(nullptr) {} - - nsCOMPtr minidump; - UniquePtr annotations; -}; - -typedef nsTHashtable ChildMinidumpMap; -static ChildMinidumpMap* pidToMinidump; -static bool OOPInitialized(); +static void OOPInit(); void RecordMainThreadId() { gMainThreadId = @@ -475,13 +428,18 @@ static void CreateFileFromPath(const xpstring& path, nsIFile** file) { DependentPathString(path.c_str(), path.size()), file); } -[[nodiscard]] static std::optional CreatePathFromFile(nsIFile* file) { - AutoPathString path; +nsresult GetNativePathFromFile(nsIFile* aFile, PathString& aPathString) { #ifdef XP_WIN - nsresult rv = file->GetPath(path); + return aFile->GetPath(aPathString); #else - nsresult rv = file->GetNativePath(path); + return aFile->GetNativePath(aPathString); #endif +} + +[[nodiscard]] +static std::optional CreatePathFromFile(nsIFile* file) { + AutoPathString path; + nsresult rv = GetNativePathFromFile(file, path); if (NS_FAILED(rv)) { return {}; } @@ -781,7 +739,7 @@ static void PHCStackTraceToString(char* aBuffer, size_t aBufferLen, strcat(aBuffer, ","); } XP_STOA(uintptr_t(aStack.mPcs[i]), addrString); - strncat(aBuffer, addrString, aBufferLen); + strncat(aBuffer, addrString, aBufferLen - 1); } } @@ -838,55 +796,6 @@ static void WritePHCAddrInfo(AnnotationWriter& writer, } } -static void PopulatePHCStackTraceAnnotation( - AnnotationTable& aAnnotations, const Annotation aName, - const Maybe& aStack) { - if (aStack.isNothing()) { - return; - } - - char addrsString[phcStringifiedAnnotationSize]; - PHCStackTraceToString(addrsString, sizeof(addrsString), *aStack); - aAnnotations[aName] = addrsString; -} - -static void PopulatePHCAnnotations(AnnotationTable& aAnnotations, - const phc::AddrInfo* aAddrInfo) { - // Is this a PHC allocation needing special treatment? - if (aAddrInfo && aAddrInfo->mKind != phc::AddrInfo::Kind::Unknown) { - const char* kindString; - switch (aAddrInfo->mKind) { - case phc::AddrInfo::Kind::Unknown: - kindString = "Unknown(?!)"; - break; - case phc::AddrInfo::Kind::NeverAllocatedPage: - kindString = "NeverAllocatedPage"; - break; - case phc::AddrInfo::Kind::InUsePage: - kindString = "InUsePage(?!)"; - break; - case phc::AddrInfo::Kind::FreedPage: - kindString = "FreedPage"; - break; - case phc::AddrInfo::Kind::GuardPage: - kindString = "GuardPage"; - break; - default: - kindString = "Unmatched(?!)"; - break; - } - - aAnnotations[Annotation::PHCKind] = kindString; - aAnnotations[Annotation::PHCBaseAddress] = - nsPrintfCString("%zu", uintptr_t(aAddrInfo->mBaseAddr)); - aAnnotations[Annotation::PHCUsableSize] = - nsPrintfCString("%zu", aAddrInfo->mUsableSize); - PopulatePHCStackTraceAnnotation(aAnnotations, Annotation::PHCAllocStack, - aAddrInfo->mAllocStack); - PopulatePHCStackTraceAnnotation(aAnnotations, Annotation::PHCFreeStack, - aAddrInfo->mFreeStack); - } -} #endif /** @@ -1812,7 +1721,13 @@ static void PrepareForMinidump() { # if defined(DEBUG) && defined(HAS_DLL_BLOCKLIST) DllBlocklist_Shutdown(); # endif -#endif // XP_WIN +#elif defined(XP_LINUX) && !defined(MOZ_WIDGET_ANDROID) + if (gCrashHelperPid) { + // Ignore the return value because we're in the exception handler, so + // there's not much we can do safely, not even log the error. + Unused << prctl(PR_SET_PTRACER, gCrashHelperPid); + } +#endif } #ifdef XP_WIN @@ -1888,22 +1803,6 @@ static bool ChildFilter(void* context) { #endif // !defined(XP_WIN) -static bool ChildMinidumpCallback( -#if defined(XP_WIN) - const wchar_t* dump_path, const wchar_t* minidump_id, -#elif defined(XP_LINUX) - const MinidumpDescriptor& descriptor, -#else // defined(XP_MACOSX) - const char* dump_dir, const char* minidump_id, -#endif - void* context, -#if defined(XP_WIN) - EXCEPTION_POINTERS* exinfo, MDRawAssertionInfo* assertion, -#endif // defined(XP_WIN) - const mozilla::phc::AddrInfo* addr_info, bool succeeded) { - return succeeded; -} - static bool ShouldReport() { // this environment variable prevents us from launching // the crash reporter client @@ -1933,10 +1832,12 @@ static nsresult LocateExecutable(nsIFile* aXREDirectory, const nsAString& aName, NS_ENSURE_SUCCESS(rv, rv); # ifdef XP_MACOSX - exePath->SetNativeLeafName("MacOS"_ns); - exePath->Append(u"crashreporter.app"_ns); - exePath->Append(u"Contents"_ns); - exePath->Append(u"MacOS"_ns); + if (aName.Equals(CRASH_REPORTER_FILENAME)) { + exePath->SetNativeLeafName("MacOS"_ns); + exePath->Append(u"crashreporter.app"_ns); + exePath->Append(u"Contents"_ns); + exePath->Append(u"MacOS"_ns); + } # endif exePath->Append(aName); @@ -2028,8 +1929,16 @@ nsresult SetExceptionHandler(nsIFile* aXREDirectory, bool force /*=false*/) { if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + // Locate the crash helper executable + PathString crashHelperPath_temp; + rv = LocateExecutable(aXREDirectory, CRASH_HELPER_FILENAME, + crashHelperPath_temp); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } crashReporterPath = crashReporterPath_temp.get(); + crashHelperPath = crashHelperPath_temp.get(); #else // On Android, we launch a service defined via MOZ_ANDROID_CRASH_HANDLER const char* androidCrashHandler = PR_GetEnv("MOZ_ANDROID_CRASH_HANDLER"); @@ -2049,6 +1958,10 @@ nsresult SetExceptionHandler(nsIFile* aXREDirectory, bool force /*=false*/) { androidStartServiceCommand = (char*)"startservice"; } } + + const char* crashHelperPathEnv = PR_GetEnv("MOZ_ANDROID_PACKAGE_NAME"); + MOZ_ASSERT(crashHelperPathEnv, "The application package name is required"); + crashHelperPath = crashHelperPathEnv; #endif // !defined(MOZ_WIDGET_ANDROID) // get temp path to use for minidump path @@ -2160,6 +2073,8 @@ nsresult SetExceptionHandler(nsIFile* aXREDirectory, bool force /*=false*/) { oldTerminateHandler = std::set_terminate(&TerminateHandler); + OOPInit(); + return NS_OK; } @@ -2178,17 +2093,33 @@ bool GetMinidumpPath(nsAString& aPath) { } nsresult SetMinidumpPath(const nsAString& aPath) { - if (!gExceptionHandler) return NS_ERROR_NOT_INITIALIZED; + if (!gExceptionHandler) { + return NS_ERROR_NOT_INITIALIZED; + } + AutoPathString path; #ifdef XP_WIN - gExceptionHandler->set_dump_path( - std::wstring(char16ptr_t(aPath.BeginReading()))); + path = aPath; +#else + path = NS_ConvertUTF16toUTF8(aPath); +#endif + + // Set the path for the in-process exception handler +#ifdef XP_WIN + gExceptionHandler->set_dump_path(std::wstring(path.get())); #elif defined(XP_LINUX) gExceptionHandler->set_minidump_descriptor( - MinidumpDescriptor(NS_ConvertUTF16toUTF8(aPath).BeginReading())); + MinidumpDescriptor(path.BeginReading())); #else - gExceptionHandler->set_dump_path(NS_ConvertUTF16toUTF8(aPath).BeginReading()); + gExceptionHandler->set_dump_path(path.BeginReading()); #endif + + // Set the path used by the crash helper for out-of-process crash generation + if (gCrashHelperClient) { + set_crash_report_path(gCrashHelperClient, + (const BreakpadChar*)path.BeginReading()); + } + return NS_OK; } @@ -2367,11 +2298,7 @@ nsresult SetupExtraData(nsIFile* aAppDataDirectory, memset(lastCrashTimeFilename, 0, sizeof(lastCrashTimeFilename)); PathString filename; -#if defined(XP_WIN) - rv = lastCrashFile->GetPath(filename); -#else - rv = lastCrashFile->GetNativePath(filename); -#endif + rv = GetNativePathFromFile(lastCrashFile, filename); NS_ENSURE_SUCCESS(rv, rv); if (filename.Length() < XP_PATH_MAX) { @@ -2942,15 +2869,33 @@ void SetProfileDirectory(nsIFile* aDir) { SetCrashEventsDir(dir); } -void SetUserAppDataDirectory(nsIFile* aDir) { - nsCOMPtr dir; - aDir->Clone(getter_AddRefs(dir)); +static void PopulatePendingDir(nsIFile* aUserAppDataDir) { + if (!pendingDirectory.empty()) { + return; + } - dir->Append(u"Crash Reports"_ns); - EnsureDirectoryExists(dir); - dir->Append(u"events"_ns); - EnsureDirectoryExists(dir); - SetCrashEventsDir(dir); + nsCOMPtr pendingDir; + aUserAppDataDir->Clone(getter_AddRefs(pendingDir)); + pendingDir->Append(u"Crash Reports"_ns); + pendingDir->Append(u"pending"_ns); + + PathString path; + if (NS_SUCCEEDED(GetNativePathFromFile(pendingDir, path))) { + pendingDirectory = xpstring(path.get()); + } +} + +void SetUserAppDataDirectory(nsIFile* aDir) { + nsCOMPtr eventsDir; + aDir->Clone(getter_AddRefs(eventsDir)); + + eventsDir->Append(u"Crash Reports"_ns); + EnsureDirectoryExists(eventsDir); + eventsDir->Append(u"events"_ns); + EnsureDirectoryExists(eventsDir); + SetCrashEventsDir(eventsDir); + + PopulatePendingDir(aDir); } void UpdateCrashEventsDir() { @@ -2991,12 +2936,9 @@ void SetMemoryReportFile(nsIFile* aFile) { } PathString path; -#ifdef XP_WIN - aFile->GetPath(path); -#else - aFile->GetNativePath(path); -#endif - memoryReportPath = xpstring(path.get()); + if (NS_SUCCEEDED(GetNativePathFromFile(aFile, path))) { + memoryReportPath = xpstring(path.get()); + } } nsresult GetDefaultMemoryReportFile(nsIFile** aFile) { @@ -3023,36 +2965,10 @@ nsresult GetDefaultMemoryReportFile(nsIFile** aFile) { return NS_OK; } -static void FindPendingDir() { - if (!pendingDirectory.empty()) { - return; - } - nsCOMPtr pendingDir; - nsresult rv = - NS_GetSpecialDirectory(XRE_USER_APP_DATA_DIR, getter_AddRefs(pendingDir)); - if (NS_FAILED(rv)) { - NS_WARNING( - "Couldn't get the user appdata directory, crash dumps will go in an " - "unusual location"); - } else { - pendingDir->Append(u"Crash Reports"_ns); - pendingDir->Append(u"pending"_ns); - - PathString path; -#ifdef XP_WIN - pendingDir->GetPath(path); -#else - pendingDir->GetNativePath(path); -#endif - pendingDirectory = xpstring(path.get()); - } -} - // The "pending" dir is Crash Reports/pending, from which minidumps // can be submitted. Because this method may be called off the main thread, // we store the pending directory as a path. static bool GetPendingDir(nsIFile** dir) { - // MOZ_ASSERT(OOPInitialized()); if (pendingDirectory.empty()) { return false; } @@ -3166,6 +3082,34 @@ bool GetExtraFileForMinidump(nsIFile* minidump, nsIFile** extraFile) { return true; } +static nsresult ReadExtraFile(nsCOMPtr& aFile, + AnnotationTable& aAnnotations) { + const int64_t kExtraFileMaxSize = 1024 * 1024 * 1024; + int64_t fileSize; + + nsresult rv = aFile->GetFileSize(&fileSize); + NS_ENSURE_SUCCESS(rv, rv); + // Reject humongous extra files, Socorro will discard them anyway + NS_ENSURE_TRUE((fileSize > 0) && (fileSize < kExtraFileMaxSize), + NS_ERROR_OUT_OF_MEMORY); + nsTArray buffer((size_t)rv); + + nsCOMPtr stream; + rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), aFile); + NS_ENSURE_SUCCESS(rv, rv); + nsCString json; + rv = NS_ReadInputStreamToString(stream, json, fileSize); + NS_ENSURE_SUCCESS(rv, rv); + auto annotations = ExtraFileParser::Parse(json); + + if (!annotations) { + return NS_ERROR_FAILURE; + } + + aAnnotations = *annotations; + return NS_OK; +} + static bool WriteExtraFile(PlatformWriter& pw, const AnnotationTable& aAnnotations) { if (!pw.Valid()) { @@ -3187,11 +3131,7 @@ bool WriteExtraFile(const nsAString& id, const AnnotationTable& annotations) { extra->Append(id + u".extra"_ns); PathString path; -#ifdef XP_WIN - NS_ENSURE_SUCCESS(extra->GetPath(path), false); -#elif defined(XP_UNIX) - NS_ENSURE_SUCCESS(extra->GetNativePath(path), false); -#endif + NS_ENSURE_SUCCESS(GetNativePathFromFile(extra, path), false); PlatformWriter pw(path.get()); return WriteExtraFile(pw, annotations); @@ -3286,82 +3226,6 @@ static void AddSharedAnnotations(AnnotationTable& aAnnotations) { AddCommonAnnotations(aAnnotations); } -static void AddChildProcessAnnotations( - AnnotationTable& aAnnotations, nsTArray* aChildAnnotations) { - if (!aChildAnnotations) { - // TODO: We should probably make a list of errors that occurred when - // generating a crash report as more than one can occurr. - aAnnotations[Annotation::DumperError] = "MissingAnnotations"; - return; - } - - for (const auto& annotation : *aChildAnnotations) { - Annotation id = static_cast(annotation.id); - const AnnotationData& data = annotation.data; - - if ((id == Annotation::PHCBaseAddress) && - (data.tag == AnnotationData::Tag::ByteBuffer)) { - // PHC is special for now, let's deal with it here -#ifdef MOZ_PHC - const auto& buffer = data.byte_buffer._0; - alignas(mozilla::phc::AddrInfo) char mem[sizeof(mozilla::phc::AddrInfo)]; - memcpy(mem, buffer.Elements(), sizeof(mozilla::phc::AddrInfo)); - const auto* addr_info = - reinterpret_cast(mem); - PopulatePHCAnnotations(aAnnotations, addr_info); -#endif - continue; - } - - if (data.tag == AnnotationData::Tag::Empty) { - continue; - } - - nsAutoCString value; - const uint8_t* buffer = data.byte_buffer._0.Elements(); - const size_t length = data.byte_buffer._0.Length(); - - switch (TypeOfAnnotation(id)) { - case AnnotationType::String: - value.Assign(reinterpret_cast(buffer), length); - break; - case AnnotationType::Boolean: - if (length == sizeof(bool)) { - value.Assign(*reinterpret_cast(buffer) ? "1" : "0"); - } - break; - case AnnotationType::U32: - if (length == sizeof(uint32_t)) { - value.AppendInt(*reinterpret_cast(buffer)); - } - break; - case AnnotationType::U64: - if (length == sizeof(uint64_t)) { - value.AppendInt(*reinterpret_cast(buffer)); - } - break; - case AnnotationType::USize: - if (length == sizeof(size_t)) { -#ifdef XP_MACOSX - // macOS defines size_t as unsigned long, which causes ambiguity - // when it comes to function overload, use a 64-bit integer instead - value.AppendInt(*reinterpret_cast(buffer)); -#else - value.AppendInt(*reinterpret_cast(buffer)); -#endif - } - break; - case AnnotationType::Object: - // Object annotations are only produced later by minidump-analyzer. - break; - } - - if (!value.IsEmpty() && ShouldIncludeAnnotation(id, value.get())) { - aAnnotations[id] = value; - } - } -} - // It really only makes sense to call this function when // ShouldReport() is true. // Uses dumpFile's filename to generate memoryReport's filename (same name @@ -3369,7 +3233,9 @@ static void AddChildProcessAnnotations( static bool MoveToPending(nsIFile* dumpFile, nsIFile* extraFile, nsIFile* memoryReport) { nsCOMPtr pendingDir; - if (!GetPendingDir(getter_AddRefs(pendingDir))) return false; + if (!GetPendingDir(getter_AddRefs(pendingDir))) { + return false; + } if (NS_FAILED(dumpFile->MoveTo(pendingDir, u""_ns))) { return false; @@ -3397,201 +3263,91 @@ static bool MoveToPending(nsIFile* dumpFile, nsIFile* extraFile, return true; } -static void MaybeAnnotateDumperError(const ClientInfo& aClientInfo, - AnnotationTable& aAnnotations) { -#if defined(MOZ_OXIDIZED_BREAKPAD) - if (aClientInfo.had_error()) { - aAnnotations[Annotation::DumperError] = - nsDependentCString(aClientInfo.error_msg()); - } -#endif -} - -static void OnChildProcessDumpRequested( - void* aContext, const ClientInfo& aClientInfo, - const xpstring& aFilePath) MOZ_NO_THREAD_SAFETY_ANALYSIS { - nsCOMPtr minidump; - - // Hold the mutex until the current dump request is complete, to - // prevent UnsetExceptionHandler() from pulling the rug out from - // under us. - MutexAutoLock lock(*dumpSafetyLock); - if (!isSafeToDump) return; - - CreateFileFromPath(aFilePath, getter_AddRefs(minidump)); - MOZ_ASSERT(minidump); - - ProcessId pid = aClientInfo.pid(); - if (ShouldReport()) { - nsCOMPtr memoryReport; - if (!memoryReportPath.empty()) { - CreateFileFromPath(memoryReportPath, getter_AddRefs(memoryReport)); - MOZ_ASSERT(memoryReport); - } - MoveToPending(minidump, nullptr, memoryReport); - } - -#if XP_WIN - nsTArray* child_annotations = mozannotation_retrieve( - reinterpret_cast(aClientInfo.process_handle()), - static_cast(Annotation::Count)); -#elif defined(XP_MACOSX) - nsTArray* child_annotations = mozannotation_retrieve( - aClientInfo.task(), static_cast(Annotation::Count)); -#else - nsTArray* child_annotations = - mozannotation_retrieve(pid, static_cast(Annotation::Count)); -#endif - - // TODO: Write a minimal set of annotations if we fail to read them, and - // add an error to the minidump to highlight this fact. - - { - MutexAutoLock lock(*dumpMapLock); - ChildProcessData* pd = pidToMinidump->PutEntry(pid); - MOZ_ASSERT(!pd->minidump); - pd->minidump = minidump; - pd->annotations = MakeUnique(); - AnnotationTable& annotations = *(pd->annotations); - AddSharedAnnotations(annotations); - AddChildProcessAnnotations(annotations, child_annotations); - - MaybeAnnotateDumperError(aClientInfo, annotations); - } - - if (child_annotations) { - mozannotation_free(child_annotations); - } -} - -static bool OOPInitialized() { return pidToMinidump != nullptr; } - -void OOPInit() { - class ProxyToMainThread : public Runnable { - public: - ProxyToMainThread() : Runnable("nsExceptionHandler::ProxyToMainThread") {} - NS_IMETHOD Run() override { - OOPInit(); - return NS_OK; - } - }; - if (!NS_IsMainThread()) { - // This logic needs to run on the main thread - nsCOMPtr mainThread = do_GetMainThread(); - mozilla::SyncRunnable::DispatchToThread(mainThread, - new ProxyToMainThread()); - return; - } - - if (OOPInitialized()) return; - - MOZ_ASSERT(NS_IsMainThread()); - - MOZ_ASSERT(gExceptionHandler != nullptr, - "attempt to initialize OOP crash reporter before in-process " - "crashreporter!"); +static void OOPInit() { + CrashHelperClient* crashHelperClient; #if defined(XP_WIN) - childCrashNotifyPipe = - mozilla::Smprintf("\\\\.\\pipe\\gecko-crash-server-pipe.%i", - static_cast(::GetCurrentProcessId())) - .release(); - - const std::wstring dumpPath = gExceptionHandler->dump_path(); - crashServer = new CrashGenerationServer( - std::wstring(NS_ConvertASCIItoUTF16(childCrashNotifyPipe).get()), - nullptr, // default security attributes - nullptr, nullptr, // we don't care about process connect here - OnChildProcessDumpRequested, nullptr, nullptr, nullptr, - nullptr, // we don't care about process exit here - nullptr, nullptr, // we don't care about upload request here - true, // automatically generate dumps - &dumpPath); - - if (sIncludeContextHeap) { - crashServer->set_include_context_heap(sIncludeContextHeap); - } + childCrashNotifyPipe = nsCString("\\\\.\\pipe\\gecko-crash-server-pipe."); + childCrashNotifyPipe.AppendInt(static_cast(::GetCurrentProcessId())); + // TODO: Create the crash server and set include_context_heap based on the + // value of sIncludeContextHeap. Also pass the release channel so we can set + // the appropriate type of minidump in the crash helper. + crashHelperClient = crash_helper_launch( + (const BreakpadChar*)crashHelperPath.c_str(), + (const BreakpadChar*)NS_ConvertUTF8toUTF16(childCrashNotifyPipe) + .BeginReading(), + (const BreakpadChar*)gExceptionHandler->dump_path().c_str()); #elif defined(XP_LINUX) - if (!CrashGenerationServer::CreateReportChannel(&serverSocketFd, - &clientSocketFd)) - MOZ_CRASH("can't create crash reporter socketpair()"); - const std::string dumpPath = gExceptionHandler->minidump_descriptor().directory(); - crashServer = new CrashGenerationServer( - serverSocketFd, -# if defined(MOZ_OXIDIZED_BREAKPAD) - [](pid_t aPid, DirectAuxvDumpInfo* aAuxvInfo) { - return ChildProcessAuxvStore::global().Get(aPid, aAuxvInfo); - }, -# endif // defined(MOZ_OXIDIZED_BREAKPAD) - [](const ClientInfo& aClientInfo, const xpstring& aFilePath) { - OnChildProcessDumpRequested(nullptr, aClientInfo, aFilePath); - }, - &dumpPath); +# if !defined(MOZ_WIDGET_ANDROID) + if (!CrashGenerationServer::CreateReportChannel(&serverSocketFd, + &clientSocketFd)) { + MOZ_CRASH("can't create crash reporter socketpair()"); + } + crashHelperClient = crash_helper_launch(crashHelperPath.c_str(), + serverSocketFd, dumpPath.c_str()); + close(serverSocketFd); +# else + crashHelperClient = crash_helper_connect(crashHelperClientFd); + set_crash_report_path(crashHelperClient, dumpPath.c_str()); +# endif // !defined(MOZ_WIDGET_ANDROID) #elif defined(XP_MACOSX) - childCrashNotifyPipe = mozilla::Smprintf("gecko-crash-server-pipe.%i", - static_cast(getpid())) - .release(); - const std::string dumpPath = gExceptionHandler->dump_path(); + childCrashNotifyPipe = nsCString("gecko-crash-server-pipe."); + childCrashNotifyPipe.AppendInt(static_cast(getpid())); - crashServer = new CrashGenerationServer(childCrashNotifyPipe, nullptr, - nullptr, OnChildProcessDumpRequested, - nullptr, nullptr, nullptr, - true, // automatically generate dumps - dumpPath); + crashHelperClient = crash_helper_launch( + crashHelperPath.c_str(), (BreakpadRawData)childCrashNotifyPipe.get(), + gExceptionHandler->dump_path().c_str()); #endif - if (!crashServer->Start()) MOZ_CRASH("can't start crash reporter server()"); - - pidToMinidump = new ChildMinidumpMap(); - - dumpMapLock = new Mutex("CrashReporter::dumpMapLock"); - - FindPendingDir(); - UpdateCrashEventsDir(); + gCrashHelperClient = crashHelperClient; } static void OOPDeinit() { - if (!OOPInitialized()) { - NS_WARNING("OOPDeinit() without successful OOPInit()"); - return; - } - - delete crashServer; - crashServer = nullptr; - - delete dumpMapLock; - dumpMapLock = nullptr; - - delete pidToMinidump; - pidToMinidump = nullptr; - #if defined(XP_WIN) || defined(XP_MACOSX) - free(childCrashNotifyPipe); - childCrashNotifyPipe = nullptr; -#endif + childCrashNotifyPipe = ""_ns; +#endif // defined(XP_WIN) || defined(XP_MACOSX) } // Parent-side API for children +#if defined(MOZ_WIDGET_ANDROID) +void SetCrashHelperPipes(FileHandle breakpadFd, FileHandle crashHelperFd) { + clientSocketFd = breakpadFd; + crashHelperClientFd = crashHelperFd; +} +#endif // defined(MOZ_WIDGET_ANDROID) + CrashPipeType GetChildNotificationPipe() { if (!GetEnabled()) { return nullptr; } - MOZ_ASSERT(OOPInitialized()); - #if defined(XP_WIN) || defined(XP_MACOSX) - return childCrashNotifyPipe; + return childCrashNotifyPipe.get(); #elif defined(XP_LINUX) return DuplicateFileHandle(clientSocketFd); #endif } -bool SetRemoteExceptionHandler(CrashPipeType aCrashPipe) { +#if defined(XP_LINUX) && !defined(MOZ_WIDGET_ANDROID) + +ProcessId GetCrashHelperPid() { + if (gCrashHelperClient) { + return crash_helper_pid(gCrashHelperClient); + } + + return base::kInvalidProcessId; +} + +#endif // defined(XP_LINUX) && !defined(MOZ_WIDGET_ANDROID) + +bool SetRemoteExceptionHandler(CrashPipeType aCrashPipe, + ProcessId aCrashHelperPid) { MOZ_ASSERT(!gExceptionHandler, "crash client already init'd"); + gCrashHelperPid = aCrashHelperPid; RegisterRuntimeExceptionModule(); InitializeAppNotes(); RegisterAnnotations(); @@ -3606,13 +3362,15 @@ bool SetRemoteExceptionHandler(CrashPipeType aCrashPipe) { static_cast(Annotation::PHCBaseAddress), &mozilla::phc::gAddrInfo, sizeof(mozilla::phc::gAddrInfo)); #endif - #if defined(XP_WIN) gExceptionHandler = new google_breakpad::ExceptionHandler( - L"", ChildFilter, ChildMinidumpCallback, + L"", ChildFilter, + nullptr, // no callback nullptr, // no callback context google_breakpad::ExceptionHandler::HANDLER_ALL, GetMinidumpType(), - NS_ConvertASCIItoUTF16(aCrashPipe).get(), nullptr); + (const wchar_t*)NS_ConvertUTF8toUTF16(aCrashPipe).BeginReading(), + nullptr // no custom info + ); gExceptionHandler->set_handle_debug_exceptions(true); # if defined(HAVE_64BIT_BUILD) @@ -3622,17 +3380,19 @@ bool SetRemoteExceptionHandler(CrashPipeType aCrashPipe) { // MinidumpDescriptor requires a non-empty path. google_breakpad::MinidumpDescriptor path("."); - gExceptionHandler = new google_breakpad::ExceptionHandler( - path, ChildFilter, ChildMinidumpCallback, - nullptr, // no callback context - true, // install signal handlers - aCrashPipe.release()); + gExceptionHandler = + new google_breakpad::ExceptionHandler(path, ChildFilter, + nullptr, // no callback + nullptr, // no callback context + true, // install signal handlers + aCrashPipe.release()); #elif defined(XP_MACOSX) - gExceptionHandler = new google_breakpad::ExceptionHandler( - "", ChildFilter, ChildMinidumpCallback, - nullptr, // no callback context - true, // install signal handlers - aCrashPipe); + gExceptionHandler = + new google_breakpad::ExceptionHandler("", ChildFilter, + nullptr, // no callback + nullptr, // no callback context + true, // install signal handlers + aCrashPipe); #endif RecordMainThreadId(); @@ -3643,37 +3403,59 @@ bool SetRemoteExceptionHandler(CrashPipeType aCrashPipe) { return gExceptionHandler->IsOutOfProcess(); } -void GetAnnotation(ProcessId childPid, Annotation annotation, - nsACString& outStr) { - if (!GetEnabled()) { - return; - } - - MutexAutoLock lock(*dumpMapLock); - - ChildProcessData* pd = pidToMinidump->GetEntry(childPid); - if (!pd) { - return; - } - - outStr = (*pd->annotations)[annotation]; -} - bool TakeMinidumpForChild(ProcessId childPid, nsIFile** dump, AnnotationTable& aAnnotations) { - if (!GetEnabled()) return false; + if (!GetEnabled()) { + return false; + } - MutexAutoLock lock(*dumpMapLock); + CrashReport* crash_report = nullptr; - ChildProcessData* pd = pidToMinidump->GetEntry(childPid); - if (!pd) return false; + if (gCrashHelperClient) { + crash_report = transfer_crash_report(gCrashHelperClient, childPid); + } - NS_IF_ADDREF(*dump = pd->minidump); - aAnnotations = *(pd->annotations); + if (!crash_report) { + return false; + } - pidToMinidump->RemoveEntry(pd); + CreateFileFromPath(xpstring((XP_CHAR*)crash_report->path), dump); + nsCString error = + crash_report->error ? nsCString(crash_report->error) : ""_ns; + release_crash_report(crash_report); - return !!*dump; + nsCOMPtr extra = nullptr; + NS_ENSURE_TRUE(GetExtraFileForMinidump(*dump, getter_AddRefs(extra)), false); + + if (ShouldReport()) { + nsCOMPtr memoryReport; + if (!memoryReportPath.empty()) { + CreateFileFromPath(memoryReportPath, getter_AddRefs(memoryReport)); + MOZ_ASSERT(memoryReport); + } + + MoveToPending(*dump, extra, memoryReport); + } + + nsresult rv = ReadExtraFile(extra, aAnnotations); + + // Unconditionally remove the temporary .extra file, it will be regenarated + // later when we finalize the crash report. + extra->Remove(false); + + if (rv != NS_OK) { + // TODO: We failed to read the annotations, this will leave an orphaned + // crash that we won't be able to submit. Clean everything up instead? + return false; + } + + AddSharedAnnotations(aAnnotations); + + if (error.Length() > 0) { + aAnnotations[Annotation::DumperError] = error; + } + + return true; } bool FinalizeOrphanedMinidump(ProcessId aChildPid, GeckoProcessType aType, @@ -3700,58 +3482,6 @@ bool FinalizeOrphanedMinidump(ProcessId aChildPid, GeckoProcessType aType, return WriteExtraFile(id, annotations); } -#ifdef XP_WIN - -// Function invoked by the WER runtime exception handler running in an -// external process. This function isn't used anywhere inside Gecko directly -// but rather invoked via CreateRemoteThread() in the main process. - -// Store this global in a section called mozwerpt where we can find it by just -// looking at the program headers. -# pragma section("mozwerpt", read, executable, shared) - -__declspec(allocate("mozwerpt")) MOZ_EXPORT DWORD WINAPI -WerNotifyProc(LPVOID aParameter) { - const WindowsErrorReportingData* werData = - static_cast(aParameter); - - auto freeParameterOnExit = MakeScopeExit([&aParameter] { - VirtualFree(aParameter, sizeof(WindowsErrorReportingData), MEM_RELEASE); - }); - - // Hold the mutex until the current dump request is complete, to - // prevent UnsetExceptionHandler() from pulling the rug out from - // under us. - MutexAutoLock safetyLock(*dumpSafetyLock); - if (!isSafeToDump || !ShouldReport()) { - return S_OK; - } - - ProcessId pid = werData->mChildPid; - nsCOMPtr minidump; - if (!GetPendingDir(getter_AddRefs(minidump))) { - return S_OK; - } - xpstring minidump_native_name(werData->mMinidumpFile, - werData->mMinidumpFile + 40); - nsString minidump_name(minidump_native_name.c_str()); - minidump->Append(minidump_name); - - { - MutexAutoLock lock(*dumpMapLock); - ChildProcessData* pd = pidToMinidump->PutEntry(pid); - MOZ_ASSERT(!pd->minidump); - pd->minidump = minidump; - pd->annotations = MakeUnique(); - (*pd->annotations)[Annotation::WindowsErrorReporting] = "1"_ns; - AddSharedAnnotations(*(pd->annotations)); - } - - return S_OK; -} - -#endif // XP_WIN - //----------------------------------------------------------------------------- // CreateMinidumpsAndPair() and helpers // @@ -3878,17 +3608,11 @@ bool CreateMinidumpsAndPair(ProcessHandle aTargetHandle, // callback when generating a dump of the calling process. XP_CHAR minidumpPath[XP_PATH_MAX] = {}; -#if defined(XP_LINUX) && defined(MOZ_OXIDIZED_BREAKPAD) - DirectAuxvDumpInfo auxvInfo = {}; - bool auxvInfoValid = - ChildProcessAuxvStore::global().Get(aTargetHandle, &auxvInfo); -#endif // defined(XP_LINUX) && defined(MOZ_OXIDIZED_BREAKPAD) - // dump the target if (!google_breakpad::ExceptionHandler::WriteMinidumpForChild( aTargetHandle, targetThread, #if defined(XP_LINUX) && defined(MOZ_OXIDIZED_BREAKPAD) - auxvInfoValid ? &auxvInfo : nullptr, + /* auxvInfo */ nullptr, #endif // defined(XP_LINUX) && defined(MOZ_OXIDIZED_BREAKPAD) dump_path, PairedDumpCallback, static_cast(minidumpPath) #ifdef XP_WIN @@ -3934,18 +3658,7 @@ bool CreateMinidumpsAndPair(ProcessHandle aTargetHandle, #endif AddSharedAnnotations(aTargetAnnotations); -#if XP_WIN - nsTArray* child_annotations = - mozannotation_retrieve(reinterpret_cast(aTargetHandle), - static_cast(Annotation::Count)); -#else - nsTArray* child_annotations = mozannotation_retrieve( - aTargetHandle, static_cast(Annotation::Count)); -#endif - AddChildProcessAnnotations(aTargetAnnotations, child_annotations); - if (child_annotations) { - mozannotation_free(child_annotations); - } + // TODO: Retrieve annotations from child process targetMinidump.forget(aMainDumpOut); @@ -3969,4 +3682,28 @@ bool UnsetRemoteExceptionHandler(bool wasSet) { return true; } +#if defined(XP_LINUX) && defined(MOZ_OXIDIZED_BREAKPAD) + +void GetCurrentProcessAuxvInfo(DirectAuxvDumpInfo* aAuxvInfo) { + aAuxvInfo->program_header_count = getauxval(AT_PHNUM); + aAuxvInfo->program_header_address = getauxval(AT_PHDR); + aAuxvInfo->linux_gate_address = getauxval(AT_SYSINFO_EHDR); + aAuxvInfo->entry_address = getauxval(AT_ENTRY); +} + +void RegisterChildAuxvInfo(pid_t aChildPid, + const DirectAuxvDumpInfo& aAuxvInfo) { + if (gCrashHelperClient) { + register_child_auxv_info(gCrashHelperClient, aChildPid, &aAuxvInfo); + } +} + +void UnregisterChildAuxvInfo(pid_t aChildPid) { + if (gCrashHelperClient) { + unregister_child_auxv_info(gCrashHelperClient, aChildPid); + } +} + +#endif // defined(XP_LINUX) && defined(MOZ_OXIDIZED_BREAKPAD) + } // namespace CrashReporter diff --git a/toolkit/crashreporter/nsExceptionHandler.h b/toolkit/crashreporter/nsExceptionHandler.h index 61711bcf174..78274200fbe 100644 --- a/toolkit/crashreporter/nsExceptionHandler.h +++ b/toolkit/crashreporter/nsExceptionHandler.h @@ -175,9 +175,6 @@ nsresult UnregisterAppMemory(void* ptr); // Include heap regions of the crash context. void SetIncludeContextHeap(bool aValue); -void GetAnnotation(ProcessId childPid, Annotation annotation, - nsACString& outStr); - // Functions for working with minidumps and .extras typedef mozilla::EnumeratedArray @@ -210,26 +207,8 @@ nsresult AppendObjCExceptionInfoToAppNotes(void* inException); nsresult GetSubmitReports(bool* aSubmitReport); nsresult SetSubmitReports(bool aSubmitReport); -#ifdef XP_WIN -// This data is stored in the parent process, there is one copy for each child -// process. The mChildPid and mMinidumpFile fields are filled by the WER runtime -// exception module when the associated child process crashes. -struct WindowsErrorReportingData { - // PID of the child process that crashed. - DWORD mChildPid; - // Filename of the generated minidump; this is not a 0-terminated string - char mMinidumpFile[40]; -}; -#endif // XP_WIN - // Out-of-process crash reporter API. -// Initializes out-of-process crash reporting. This method must be called -// before the platform-specific notification pipe APIs are called. If called -// from off the main thread, this method will synchronously proxy to the main -// thread. -void OOPInit(); - // Return true if a dump was found for |childPid|, and return the // path in |dump|. The caller owns the last reference to |dump| if it // is non-nullptr. The annotations for the crash will be stored in @@ -282,17 +261,28 @@ bool CreateMinidumpsAndPair(ProcessHandle aTargetPid, AnnotationTable& aTargetAnnotations, nsIFile** aTargetDumpOut); -#if defined(XP_WIN) || defined(XP_MACOSX) +#if defined(XP_WIN) || defined(XP_MACOSX) || defined(XP_IOS) using CrashPipeType = const char*; #else using CrashPipeType = mozilla::UniqueFileHandle; #endif // Parent-side API for children +#if defined(MOZ_WIDGET_ANDROID) +void SetCrashHelperPipes(FileHandle breakpadFd, FileHandle crashHelperFd); +#endif CrashPipeType GetChildNotificationPipe(); +#if defined(XP_LINUX) && !defined(MOZ_WIDGET_ANDROID) + +// Return the pid of the crash helper process. +MOZ_EXPORT ProcessId GetCrashHelperPid(); + +#endif // XP_LINUX && !defined(MOZ_WIDGET_ANDROID) + // Child-side API -bool SetRemoteExceptionHandler(CrashPipeType aCrashPipe); +MOZ_EXPORT bool SetRemoteExceptionHandler(CrashPipeType aCrashPipe, + ProcessId aCrashHelperPid = 0); bool UnsetRemoteExceptionHandler(bool wasSet = true); } // namespace CrashReporter diff --git a/toolkit/crashreporter/process_reader/Cargo.toml b/toolkit/crashreporter/process_reader/Cargo.toml index f866ae31692..b5e2c470690 100644 --- a/toolkit/crashreporter/process_reader/Cargo.toml +++ b/toolkit/crashreporter/process_reader/Cargo.toml @@ -10,7 +10,7 @@ license = "MPL-2.0" [dependencies] goblin = { version = "0.9", features = ["elf32", "elf64", "pe32", "pe64"] } memoffset = "0.9" -thiserror = "1.0" +thiserror = "2" [target."cfg(any(target_os = \"linux\", target_os = \"android\"))".dependencies] libc = "0.2" diff --git a/toolkit/crashreporter/process_reader/src/lib.rs b/toolkit/crashreporter/process_reader/src/lib.rs index 09936e46f1c..0c30c277406 100644 --- a/toolkit/crashreporter/process_reader/src/lib.rs +++ b/toolkit/crashreporter/process_reader/src/lib.rs @@ -15,7 +15,7 @@ pub type ProcessHandle = windows_sys::Win32::Foundation::HANDLE; #[cfg(any(target_os = "linux", target_os = "android"))] pub type ProcessHandle = libc::pid_t; -#[cfg(any(target_os = "macos"))] +#[cfg(target_os = "macos")] pub type ProcessHandle = mach2::mach_types::task_t; pub struct ProcessReader { @@ -64,7 +64,7 @@ impl ProcessReader { let array = self.copy_array::(address + length, WORD_SIZE)?; let null_terminator = array.iter().position(|&e| e == 0); length += null_terminator.unwrap_or(WORD_SIZE); - string.extend(array.into_iter()); + string.extend(array); if null_terminator.is_some() { string.truncate(length + 1); diff --git a/toolkit/crashreporter/process_reader/src/platform/linux.rs b/toolkit/crashreporter/process_reader/src/platform/linux.rs index 92e2f5db1b1..2f0fde5c2f2 100644 --- a/toolkit/crashreporter/process_reader/src/platform/linux.rs +++ b/toolkit/crashreporter/process_reader/src/platform/linux.rs @@ -61,7 +61,7 @@ impl ProcessReader { BufReader::new(maps_file) .lines() - .flatten() + .map_while(Result::ok) .map(|line| parse_proc_maps_line(&line)) .filter_map(Result::ok) .find_map(|(name, address)| { @@ -192,7 +192,10 @@ impl ProcessReader { unsafe { array.set_len(num); - Ok(std::mem::transmute(array)) + Ok(std::mem::transmute::< + std::vec::Vec>, + std::vec::Vec, + >(array)) } } } diff --git a/toolkit/crashreporter/process_reader/src/platform/macos.rs b/toolkit/crashreporter/process_reader/src/platform/macos.rs index a0dfa2b8fd5..b721dd27f8d 100644 --- a/toolkit/crashreporter/process_reader/src/platform/macos.rs +++ b/toolkit/crashreporter/process_reader/src/platform/macos.rs @@ -212,7 +212,7 @@ impl ProcessReader { if res == KERN_SUCCESS { unsafe { array.set_len(num); - Ok(std::mem::transmute(array)) + Ok(std::mem::transmute::>, Vec>(array)) } } else { Err(ReadError::MachError) diff --git a/toolkit/crashreporter/process_reader/src/platform/windows.rs b/toolkit/crashreporter/process_reader/src/platform/windows.rs index 2d8c63444e6..03e98f37995 100644 --- a/toolkit/crashreporter/process_reader/src/platform/windows.rs +++ b/toolkit/crashreporter/process_reader/src/platform/windows.rs @@ -119,9 +119,8 @@ impl ProcessReader { fn get_module_name(&self, module: HMODULE) -> Option { let mut path: [u16; MAX_PATH as usize] = [0; MAX_PATH as usize]; - let res = unsafe { - K32GetModuleBaseNameW(self.process, module, (&mut path).as_mut_ptr(), MAX_PATH) - }; + let res = + unsafe { K32GetModuleBaseNameW(self.process, module, path.as_mut_ptr(), MAX_PATH) }; if res == 0 { None diff --git a/toolkit/library/rust/shared/Cargo.toml b/toolkit/library/rust/shared/Cargo.toml index f0d190ecf6d..fb81bf1d2f3 100644 --- a/toolkit/library/rust/shared/Cargo.toml +++ b/toolkit/library/rust/shared/Cargo.toml @@ -71,6 +71,7 @@ rure = "0.2.2" rust_minidump_writer_linux = { path = "../../../crashreporter/rust_minidump_writer_linux", optional = true } mozannotation_client = { path = "../../../crashreporter/mozannotation_client", optional = true } mozannotation_server = { path = "../../../crashreporter/mozannotation_server", optional = true } +crash_helper_client = { path = "../../../crashreporter/crash_helper_client", optional = true } gecko-profiler = { path = "../../../../tools/profiler/rust-api"} midir_impl = { path = "../../../../dom/midi/midir_impl", optional = true } dom = { path = "../../../../dom/base/rust" } @@ -141,7 +142,7 @@ webrtc = ["mdns_service"] glean_disable_upload = ["fog_control/disable_upload"] glean_with_gecko = ["fog_control/with_gecko", "jog/with_gecko"] oxidized_breakpad = ["rust_minidump_writer_linux"] -crashreporter = ["mozannotation_client", "mozannotation_server"] +crashreporter = ["crash_helper_client", "mozannotation_client"] with_dbus = ["audio_thread_priority/with_dbus"] thread_sanitizer = ["xpcom/thread_sanitizer"] uniffi_fixtures = ["gkrust-uniffi-fixtures"] diff --git a/toolkit/library/rust/shared/lib.rs b/toolkit/library/rust/shared/lib.rs index e1d3d46874b..0034fc82e01 100644 --- a/toolkit/library/rust/shared/lib.rs +++ b/toolkit/library/rust/shared/lib.rs @@ -112,8 +112,9 @@ extern crate rust_minidump_writer_linux; #[cfg(feature = "crashreporter")] extern crate mozannotation_client; + #[cfg(feature = "crashreporter")] -extern crate mozannotation_server; +extern crate crash_helper_client; #[cfg(feature = "webmidi_midir_impl")] extern crate midir_impl; diff --git a/toolkit/moz.configure b/toolkit/moz.configure index 6c47287a5b3..f9b96c0a62a 100644 --- a/toolkit/moz.configure +++ b/toolkit/moz.configure @@ -116,9 +116,9 @@ def all_configure_options(): # __sandbox__._options contains items for both option.name and # option.env. But it's also an OrderedDict, meaning both are # consecutive. - # Also ignore OLD_CONFIGURE and MOZCONFIG because they're not + # Also ignore MOZCONFIG because they're not # interesting. - if option == previous or option.env in ("OLD_CONFIGURE", "MOZCONFIG"): + if option == previous or option.env in ("MOZCONFIG",): continue previous = option value = __sandbox__._value_for(option) diff --git a/toolkit/mozapps/extensions/moz.build b/toolkit/mozapps/extensions/moz.build index b33dc0fdf86..357d249fcd0 100644 --- a/toolkit/mozapps/extensions/moz.build +++ b/toolkit/mozapps/extensions/moz.build @@ -42,6 +42,22 @@ if CONFIG["MOZ_BUILD_APP"] == "browser": FINAL_TARGET_FILES.browser.chrome.browser.content.browser += [ "!%s" % built_in_addons, ] +elif CONFIG["MOZ_BUILD_APP"] == "mobile/android": + # TODO: double-chek if GeckoView is really not using the default en-US dictionary + # as mentioned in test_dictionary_webextension.js skip-if comment here + # https://searchfox.org/mozilla-central/rev/f3c8c63a09/toolkit/mozapps/extensions/test/xpcshell/xpcshell.toml#101 + # + # mobile/shared/actors/ContentDelegateChild.sys.mjs seems to be importing + # toolkit/modules/InlineSpellChecker.sys.mjs which includes a call to + # `spellchecker.GetDictionaryList()` internally. + FINAL_TARGET_FILES.chrome.chrome.content += [ + "!%s" % built_in_addons, + ] +elif CONFIG["MOZ_BUILD_APP"] == "comm/mail": + # NOTE: used by Thunderbird to auto-install the default en-US dictionary. + FINAL_TARGET_FILES.chrome.browser.content += [ + "!%s" % built_in_addons, + ] EXTRA_PP_COMPONENTS += [ "extensions.manifest", diff --git a/toolkit/mozapps/update/tests/data/xpcshellUtilsAUS.js b/toolkit/mozapps/update/tests/data/xpcshellUtilsAUS.js index 8efb112e8d1..b12e4e67f54 100644 --- a/toolkit/mozapps/update/tests/data/xpcshellUtilsAUS.js +++ b/toolkit/mozapps/update/tests/data/xpcshellUtilsAUS.js @@ -7,9 +7,6 @@ * "Couldn't get the user appdata directory. Crash events may not be produced." * in nsExceptionHandler.cpp (possibly bug 619104) * - * Test log warnings that happen after the test has finished - * "OOPDeinit() without successful OOPInit()" in nsExceptionHandler.cpp - * (bug 619104) * "XPCOM objects created/destroyed from static ctor/dtor" in nsTraceRefcnt.cpp * (possibly bug 457479) * diff --git a/toolkit/xre/Bootstrap.h b/toolkit/xre/Bootstrap.h index 9f0fcd4d0cb..4adcfa3a5bd 100644 --- a/toolkit/xre/Bootstrap.h +++ b/toolkit/xre/Bootstrap.h @@ -46,6 +46,12 @@ struct BootstrapConfig { * When the pointer above is non-null, may indicate the directory where * application files are, relative to the XRE. */ const char* appDataPath; +#if defined(MOZ_WIDGET_ANDROID) + /* Crash notification socket used by Breakpad. */ + int crashChildNotificationSocket; + /* Crash socket used to communicate with the crash helper. */ + int crashHelperSocket; +#endif }; /** diff --git a/toolkit/xre/GeckoArgs.h b/toolkit/xre/GeckoArgs.h index abadf8bfcec..457a301376c 100644 --- a/toolkit/xre/GeckoArgs.h +++ b/toolkit/xre/GeckoArgs.h @@ -228,13 +228,16 @@ static CommandLineArg sNotForBrowser{"-notForBrowser", "notforbrowser"}; static CommandLineArg sPluginPath{"-pluginPath", "pluginpath"}; static CommandLineArg sPluginNativeEvent{"-pluginNativeEvent", "pluginnativeevent"}; - -#if defined(XP_WIN) || defined(MOZ_WIDGET_COCOA) -static CommandLineArg sCrashReporter{"-crashReporter", - "crashreporter"}; -#elif defined(XP_UNIX) +#if defined(XP_LINUX) static CommandLineArg sCrashReporter{"-crashReporter", "crashreporter"}; +# if !defined(MOZ_WIDGET_ANDROID) +static CommandLineArg sCrashHelperPid{"-crashHelperPid", + "crashhelperpid"}; +# endif // !defined(MOZ_WIDGET_ANDROID) +#else +static CommandLineArg sCrashReporter{"-crashReporter", + "crashreporter"}; #endif #if defined(XP_WIN) diff --git a/toolkit/xre/nsAppRunner.cpp b/toolkit/xre/nsAppRunner.cpp index 9d5e267cc4f..3a69d17eba6 100644 --- a/toolkit/xre/nsAppRunner.cpp +++ b/toolkit/xre/nsAppRunner.cpp @@ -6104,6 +6104,11 @@ int XREMain::XRE_main(int argc, char* argv[], const BootstrapConfig& aConfig) { return NS_OK; }); +#if defined(MOZ_WIDGET_ANDROID) + CrashReporter::SetCrashHelperPipes(aConfig.crashChildNotificationSocket, + aConfig.crashHelperSocket); +#endif // defined(MOZ_WIDGET_ANDROID) + mozilla::AutoIOInterposer ioInterposerGuard; ioInterposerGuard.Init(); diff --git a/toolkit/xre/nsEmbedFunctions.cpp b/toolkit/xre/nsEmbedFunctions.cpp index eeab0cf1045..9ce6af94637 100644 --- a/toolkit/xre/nsEmbedFunctions.cpp +++ b/toolkit/xre/nsEmbedFunctions.cpp @@ -352,8 +352,16 @@ nsresult XRE_InitChildProcess(int aArgc, char* aArgv[], if (!CrashReporter::IsDummy()) { auto crashReporterArg = geckoargs::sCrashReporter.Get(aArgc, aArgv); if (crashReporterArg) { + CrashReporter::ProcessId crashHelperPid = base::kInvalidProcessId; +#if defined(XP_LINUX) && !defined(MOZ_WIDGET_ANDROID) + auto crashHelperPidArg = geckoargs::sCrashHelperPid.Get(aArgc, aArgv); + MOZ_ASSERT(crashHelperPidArg); + crashHelperPid = + static_cast(*crashHelperPidArg); +#endif // defined(XP_LINUX) && !defined(MOZ_WIDGET_ANDROID) + exceptionHandlerIsSet = CrashReporter::SetRemoteExceptionHandler( - std::move(*crashReporterArg)); + std::move(*crashReporterArg), crashHelperPid); MOZ_ASSERT(exceptionHandlerIsSet, "Should have been able to set remote exception handler"); diff --git a/toolkit/xre/test/test_install_hash.js b/toolkit/xre/test/test_install_hash.js index 9dc331fac51..ae8dbe58262 100644 --- a/toolkit/xre/test/test_install_hash.js +++ b/toolkit/xre/test/test_install_hash.js @@ -15,6 +15,7 @@ const XRE = Cc["@mozilla.org/xre/directory-provider;1"].getService( ); const HASH = XRE.getInstallHash(false); const EXE = Services.dirsvc.get("XREExeF", Ci.nsIFile); +const GREBIND = Services.dirsvc.get("GreBinD", Ci.nsIFile); const SCRIPT = do_get_file("show_hash.js", false); async function getHash(bin) { @@ -22,9 +23,10 @@ async function getHash(bin) { // If this test is running through firefox.exe -xpcshell, we need // to make sure to execute the script through it as well. let args = []; - if (!bin.leafName.startsWith("xpcshell")) { + if (!bin.leafName.toLowerCase().startsWith("xpcshell")) { args.push("-xpcshell"); } + args.push("-g", GREBIND.path); args.push(SCRIPT.path); let proc = await Subprocess.call({ diff --git a/tools/rusttests/config/mozconfigs/common b/tools/rusttests/config/mozconfigs/common index cac5b57ce93..55d6cbff94f 100644 --- a/tools/rusttests/config/mozconfigs/common +++ b/tools/rusttests/config/mozconfigs/common @@ -4,11 +4,11 @@ # This file is included by all "tools/rusttests" mozconfigs -MOZ_AUTOMATION_BUILD_SYMBOLS=0 +export MOZ_AUTOMATION_BUILD_SYMBOLS=0 MOZ_AUTOMATION_PACKAGE=0 MOZ_AUTOMATION_PACKAGE_GENERATED_SOURCES=0 MOZ_AUTOMATION_UPLOAD=0 -MOZ_AUTOMATION_CHECK=0 +export MOZ_AUTOMATION_CHECK=0 ac_add_options --enable-project=tools/rusttests . "$topsrcdir/build/mozconfig.common" . "$topsrcdir/build/mozconfig.common.override" diff --git a/tools/rusttests/config/mozconfigs/linux64/rusttests b/tools/rusttests/config/mozconfigs/linux64/rusttests index 64d502087b8..847fac51af5 100644 --- a/tools/rusttests/config/mozconfigs/linux64/rusttests +++ b/tools/rusttests/config/mozconfigs/linux64/rusttests @@ -1,2 +1,5 @@ . "$topsrcdir/build/unix/mozconfig.linux" . "$topsrcdir/tools/rusttests/config/mozconfigs/common" + +# Not active for that configuration +unset MOZ_NO_PIE_COMPAT diff --git a/widget/gtk/nsDragService.cpp b/widget/gtk/nsDragService.cpp index f95a9754c33..d0ded4d7349 100644 --- a/widget/gtk/nsDragService.cpp +++ b/widget/gtk/nsDragService.cpp @@ -2462,12 +2462,14 @@ void nsDragSession::SetDragIcon(GdkDragContext* aContext) { // When mDragPopup has a parent it's already attached to D&D context. // That may happens when D&D operation is aborted but not finished // on Gtk side yet so let's remove it now. + g_object_ref(gtkWidget); if (GtkWidget* parent = gtk_widget_get_parent(gtkWidget)) { gtk_container_remove(GTK_CONTAINER(parent), gtkWidget); } LOGDRAGSERVICE(" set drag popup [%p]", widget.get()); OpenDragPopup(); gtk_drag_set_icon_widget(aContext, gtkWidget, offsetX, offsetY); + g_object_unref(gtkWidget); return; } else { LOGDRAGSERVICE(" NS_NATIVE_SHELLWIDGET is missing!"); diff --git a/widget/gtk/nsWindow.cpp b/widget/gtk/nsWindow.cpp index d5248f105fe..27c81407827 100644 --- a/widget/gtk/nsWindow.cpp +++ b/widget/gtk/nsWindow.cpp @@ -8390,7 +8390,10 @@ static gboolean generic_event_cb(GtkWidget* widget, GdkEvent* aEvent) { } void nsWindow::GtkWidgetDestroyHandler(GtkWidget* aWidget) { - MOZ_RELEASE_ASSERT(mIsDestroyed, "Releasing live widget!"); + if (!mIsDestroyed) { + NS_WARNING("GtkWidgetDestroyHandler called for live nsWindow!"); + Destroy(); + } if (aWidget == mShell) { mShell = nullptr; return; diff --git a/xpcom/build/XREShellData.h b/xpcom/build/XREShellData.h index 8fa47ab459d..57b7396a8b6 100644 --- a/xpcom/build/XREShellData.h +++ b/xpcom/build/XREShellData.h @@ -33,6 +33,8 @@ struct XREShellData { #if defined(ANDROID) FILE* outFile; FILE* errFile; + int crashChildNotificationSocket; + int crashHelperSocket; #endif #if defined(LIBFUZZER) LibFuzzerDriver fuzzerDriver;