зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1899177 - Part 2: Vendor icu_calendar and calendrical_calculations. r=spidermonkey-reviewers,dminor
The large files check in "vendor_rust.py" had to be manually disabled to allow importing "third_party/rust/calendrical_calculations/src/astronomy.rs". (About 20% of the file size is test code.) Differential Revision: https://phabricator.services.mozilla.com/D211763
This commit is contained in:
Родитель
e268f80c64
Коммит
a4952400f8
|
@ -622,6 +622,16 @@ version = "1.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c"
|
||||
|
||||
[[package]]
|
||||
name = "calendrical_calculations"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8dfe3bc6a50b4667fafdb6d9cf26731c5418c457e317d8166c972014facf9a5d"
|
||||
dependencies = [
|
||||
"core_maths",
|
||||
"displaydoc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "camino"
|
||||
version = "1.1.2"
|
||||
|
@ -2771,12 +2781,36 @@ dependencies = [
|
|||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_calendar"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7eb932a690c92f87955e923106181ee0d5682e688ff37fb5c7b296e1fe806edb"
|
||||
dependencies = [
|
||||
"calendrical_calculations",
|
||||
"displaydoc",
|
||||
"icu_calendar_data",
|
||||
"icu_locid",
|
||||
"icu_locid_transform",
|
||||
"icu_provider",
|
||||
"tinystr",
|
||||
"writeable",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_calendar_data"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22aec7d032735d9acb256eeef72adcac43c3b7572f19b51576a63d664b524ca2"
|
||||
|
||||
[[package]]
|
||||
name = "icu_capi"
|
||||
version = "1.5.0"
|
||||
dependencies = [
|
||||
"diplomat",
|
||||
"diplomat-runtime",
|
||||
"icu_calendar",
|
||||
"icu_locid",
|
||||
"icu_properties",
|
||||
"icu_provider",
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
{"files":{"Cargo.toml":"690c151bccdceefbca495bfb96d3d482d4ecff698b5500201cad595ccfd9d7ce","LICENSE":"192ea857d1bff2b87c174de36cbae5c173234726c6b8eceab9790a535d7dbc95","README.md":"cdffd3b4ab1a4efad95de0dcdf0b097fce24fedf04d076e8ab3909ac2e2accb5","src/astronomy.rs":"5e95c8a42bb32570932205d008842332f83a0e03d02166de6ec7956385e12b4c","src/chinese_based.rs":"e6a1c88f7cd627f74a25bd38eee653b83aafe7a01652e8251c97510b05f1818f","src/coptic.rs":"4ef26fbc10d7e81057f7a9876951e1a62758a7881c58dab9428dccee2e5fd25f","src/error.rs":"aec0635f228f81806a8af7683f46e6b89a316423cb493f48fa44e0f942d54d2f","src/ethiopian.rs":"0ea906c421d78462390fc43fd3653bd0f07615051495ba1c0c80b4a90cf8367a","src/hebrew.rs":"f434b7a4d359aa478669491d0cf712082d9ec0e65f9136bb1d1daca6ac453e2f","src/helpers.rs":"8861b51f6c15cb88cfea6322feb8b13ec61c4a37ff44aaac8ead5541ac3a3549","src/islamic.rs":"488ffbad6b8a15c86b51efcc523e3f1095a924180de52b4333fe0d2aabb64ad3","src/iso.rs":"a1ec6befd39edeb8ab625db43411bdb4f38d92081afb848dcfe8ab55b18f57cc","src/julian.rs":"e6d3cef1867c813edc9e5bc28870cd646127eaf13ced32889bce8991af2f6235","src/lib.rs":"5437fa651b598c348f3a789abbfa7e576a7945e16a059731cfa4e3200c3ae625","src/persian.rs":"cbd7258f2efa995f5b9185cdb1e3678af44c0f4059a023c7ef5347aade35c94c","src/rata_die.rs":"d8eac3c48f46956b0f9ddda2da9aab82a7af1187e743fa6058d9b5112e70ebe5"},"package":"8dfe3bc6a50b4667fafdb6d9cf26731c5418c457e317d8166c972014facf9a5d"}
|
|
@ -0,0 +1,63 @@
|
|||
# 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.66"
|
||||
name = "calendrical_calculations"
|
||||
version = "0.1.0"
|
||||
authors = ["The ICU4X Project Developers"]
|
||||
include = [
|
||||
"data/**/*",
|
||||
"src/**/*",
|
||||
"examples/**/*",
|
||||
"benches/**/*",
|
||||
"tests/**/*",
|
||||
"Cargo.toml",
|
||||
"LICENSE",
|
||||
"README.md",
|
||||
]
|
||||
description = "Calendrical calculations in Rust"
|
||||
readme = "README.md"
|
||||
keywords = [
|
||||
"zerocopy",
|
||||
"serialization",
|
||||
"zero-copy",
|
||||
"serde",
|
||||
]
|
||||
categories = [
|
||||
"rust-patterns",
|
||||
"memory-management",
|
||||
"caching",
|
||||
"no-std",
|
||||
"data-structures",
|
||||
]
|
||||
license = "Apache-2.0"
|
||||
repository = "https://github.com/unicode-org/icu4x"
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["bench"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
[package.metadata.workspaces]
|
||||
independent = true
|
||||
|
||||
[dependencies.core_maths]
|
||||
version = "0.1"
|
||||
|
||||
[dependencies.displaydoc]
|
||||
version = "0.2.3"
|
||||
default-features = false
|
||||
|
||||
[features]
|
||||
std = []
|
|
@ -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 2023 The Unicode Consortium
|
||||
|
||||
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.
|
|
@ -0,0 +1,21 @@
|
|||
# calendrical_calculations [![crates.io](https://img.shields.io/crates/v/calendrical_calculations)](https://crates.io/crates/calendrical_calculations)
|
||||
|
||||
<!-- cargo-rdme start -->
|
||||
|
||||
Calendrical calculations
|
||||
|
||||
This crate implements algorithms from
|
||||
Calendrical Calculations by Reingold & Dershowitz, Cambridge University Press, 4th edition (2018)
|
||||
as needed by [ICU4X](https://github.com/unicode-org/icu4x).
|
||||
|
||||
Most of these algorithms can be found as lisp code in the book or
|
||||
[on GithHub](https://github.com/EdReingold/calendar-code2/blob/main/calendar.l).
|
||||
|
||||
The primary purpose of this crate is use by ICU4X, however if non-ICU4X users need this we are happy
|
||||
to add more structure to this crate as needed.
|
||||
|
||||
<!-- cargo-rdme end -->
|
||||
|
||||
## More Information
|
||||
|
||||
For more information on development, authorship, contributing etc. please visit [`ICU4X home page`](https://github.com/unicode-org/icu4x).
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,652 @@
|
|||
use crate::astronomy::{self, Astronomical, Location, MEAN_SYNODIC_MONTH, MEAN_TROPICAL_YEAR};
|
||||
use crate::helpers::i64_to_i32;
|
||||
use crate::rata_die::{Moment, RataDie};
|
||||
use core::num::NonZeroU8;
|
||||
#[allow(unused_imports)]
|
||||
use core_maths::*;
|
||||
|
||||
// Don't iterate more than 14 times (which accounts for checking for 13 months)
|
||||
const MAX_ITERS_FOR_MONTHS_OF_YEAR: u8 = 14;
|
||||
|
||||
/// The trait ChineseBased is used by Chinese-based calendars to perform computations shared by such calendar.
|
||||
/// To do so, calendars should:
|
||||
///
|
||||
/// - Implement `fn location` by providing a location at which observations of the moon are recorded, which
|
||||
/// may change over time (the zone is important, long, lat, and elevation are not relevant for these calculations)
|
||||
/// - Define `const EPOCH` as a `RataDie` marking the start date of the era of the Calendar for internal use,
|
||||
/// which may not accurately reflect how years or eras are marked traditionally or seen by end-users
|
||||
pub trait ChineseBased {
|
||||
/// Given a fixed date, return the location used for observations of the new moon in order to
|
||||
/// calculate the beginning of months. For multiple Chinese-based lunar calendars, this has
|
||||
/// changed over the years, and can cause differences in calendar date.
|
||||
fn location(fixed: RataDie) -> Location;
|
||||
|
||||
/// The RataDie of the beginning of the epoch used for internal computation; this may not
|
||||
/// reflect traditional methods of year-tracking or eras, since Chinese-based calendars
|
||||
/// may not track years ordinally in the same way many western calendars do.
|
||||
const EPOCH: RataDie;
|
||||
}
|
||||
|
||||
// The equivalent first day in the Chinese calendar (based on inception of the calendar)
|
||||
const CHINESE_EPOCH: RataDie = RataDie::new(-963099); // Feb. 15, 2637 BCE (-2636)
|
||||
|
||||
/// The Chinese calendar relies on knowing the current day at the moment of a new moon;
|
||||
/// however, this can vary depending on location. As such, new moon calculations are based
|
||||
/// on the time in Beijing. Before 1929, local time was used, represented as UTC+(1397/180 h).
|
||||
/// In 1929, China adopted a standard time zone based on 120 degrees of longitude, meaning
|
||||
/// from 1929 onward, all new moon calculations are based on UTC+8h.
|
||||
///
|
||||
/// Offsets are not given in hours, but in partial days (1 hour = 1 / 24 day)
|
||||
const UTC_OFFSET_PRE_1929: f64 = (1397.0 / 180.0) / 24.0;
|
||||
const UTC_OFFSET_POST_1929: f64 = 8.0 / 24.0;
|
||||
|
||||
const CHINESE_LOCATION_PRE_1929: Location =
|
||||
Location::new_unchecked(39.0, 116.0, 43.5, UTC_OFFSET_PRE_1929);
|
||||
const CHINESE_LOCATION_POST_1929: Location =
|
||||
Location::new_unchecked(39.0, 116.0, 43.5, UTC_OFFSET_POST_1929);
|
||||
|
||||
// The first day in the Korean Dangi calendar (based on the founding of Gojoseon)
|
||||
const KOREAN_EPOCH: RataDie = RataDie::new(-852065); // Lunar new year 2333 BCE (-2332 ISO)
|
||||
|
||||
/// The Korean Dangi calendar relies on knowing the current day at the moment of a new moon;
|
||||
/// however, this can vary depending on location. As such, new moon calculations are based on
|
||||
/// the time in Seoul. Before 1908, local time was used, represented as UTC+(3809/450 h).
|
||||
/// This changed multiple times as different standard timezones were adopted in Korea.
|
||||
/// Currently, UTC+9h is used.
|
||||
///
|
||||
/// Offsets are not given in hours, but in partial days (1 hour = 1 / 24 day).
|
||||
const UTC_OFFSET_ORIGINAL: f64 = (3809.0 / 450.0) / 24.0;
|
||||
const UTC_OFFSET_1908: f64 = 8.5 / 24.0;
|
||||
const UTC_OFFSET_1912: f64 = 9.0 / 24.0;
|
||||
const UTC_OFFSET_1954: f64 = 8.5 / 24.0;
|
||||
const UTC_OFFSET_1961: f64 = 9.0 / 24.0;
|
||||
|
||||
const FIXED_1908: RataDie = RataDie::new(696608); // Apr 1, 1908
|
||||
const FIXED_1912: RataDie = RataDie::new(697978); // Jan 1, 1912
|
||||
const FIXED_1954: RataDie = RataDie::new(713398); // Mar 21, 1954
|
||||
const FIXED_1961: RataDie = RataDie::new(716097); // Aug 10, 1961
|
||||
|
||||
const KOREAN_LATITUDE: f64 = 37.0 + (34.0 / 60.0);
|
||||
const KOREAN_LONGITUDE: f64 = 126.0 + (58.0 / 60.0);
|
||||
const KOREAN_ELEVATION: f64 = 0.0;
|
||||
|
||||
const KOREAN_LOCATION_ORIGINAL: Location = Location::new_unchecked(
|
||||
KOREAN_LATITUDE,
|
||||
KOREAN_LONGITUDE,
|
||||
KOREAN_ELEVATION,
|
||||
UTC_OFFSET_ORIGINAL,
|
||||
);
|
||||
const KOREAN_LOCATION_1908: Location = Location::new_unchecked(
|
||||
KOREAN_LATITUDE,
|
||||
KOREAN_LONGITUDE,
|
||||
KOREAN_ELEVATION,
|
||||
UTC_OFFSET_1908,
|
||||
);
|
||||
const KOREAN_LOCATION_1912: Location = Location::new_unchecked(
|
||||
KOREAN_LATITUDE,
|
||||
KOREAN_LONGITUDE,
|
||||
KOREAN_ELEVATION,
|
||||
UTC_OFFSET_1912,
|
||||
);
|
||||
const KOREAN_LOCATION_1954: Location = Location::new_unchecked(
|
||||
KOREAN_LATITUDE,
|
||||
KOREAN_LONGITUDE,
|
||||
KOREAN_ELEVATION,
|
||||
UTC_OFFSET_1954,
|
||||
);
|
||||
const KOREAN_LOCATION_1961: Location = Location::new_unchecked(
|
||||
KOREAN_LATITUDE,
|
||||
KOREAN_LONGITUDE,
|
||||
KOREAN_ELEVATION,
|
||||
UTC_OFFSET_1961,
|
||||
);
|
||||
|
||||
/// A type implementing [`ChineseBased`] for the Chinese calendar
|
||||
#[derive(Debug)]
|
||||
#[allow(clippy::exhaustive_structs)] // newtype
|
||||
pub struct Chinese;
|
||||
|
||||
/// A type implementing [`ChineseBased`] for the Dangi (Korean) calendar
|
||||
#[derive(Debug)]
|
||||
#[allow(clippy::exhaustive_structs)] // newtype
|
||||
pub struct Dangi;
|
||||
|
||||
impl ChineseBased for Chinese {
|
||||
fn location(fixed: RataDie) -> Location {
|
||||
let year = crate::iso::iso_year_from_fixed(fixed);
|
||||
if year < 1929 {
|
||||
CHINESE_LOCATION_PRE_1929
|
||||
} else {
|
||||
CHINESE_LOCATION_POST_1929
|
||||
}
|
||||
}
|
||||
|
||||
const EPOCH: RataDie = CHINESE_EPOCH;
|
||||
}
|
||||
|
||||
impl ChineseBased for Dangi {
|
||||
fn location(fixed: RataDie) -> Location {
|
||||
if fixed < FIXED_1908 {
|
||||
KOREAN_LOCATION_ORIGINAL
|
||||
} else if fixed < FIXED_1912 {
|
||||
KOREAN_LOCATION_1908
|
||||
} else if fixed < FIXED_1954 {
|
||||
KOREAN_LOCATION_1912
|
||||
} else if fixed < FIXED_1961 {
|
||||
KOREAN_LOCATION_1954
|
||||
} else {
|
||||
KOREAN_LOCATION_1961
|
||||
}
|
||||
}
|
||||
|
||||
const EPOCH: RataDie = KOREAN_EPOCH;
|
||||
}
|
||||
|
||||
/// Marks the bounds of a lunar year
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[allow(clippy::exhaustive_structs)] // we're comfortable making frequent breaking changes to this crate
|
||||
pub struct YearBounds {
|
||||
/// The date marking the start of the current lunar year
|
||||
pub new_year: RataDie,
|
||||
/// The date marking the start of the next lunar year
|
||||
pub next_new_year: RataDie,
|
||||
}
|
||||
|
||||
impl YearBounds {
|
||||
/// Compute the YearBounds for the lunar year (年) containing `date`,
|
||||
/// as well as the corresponding solar year (歲). Note that since the two
|
||||
/// years overlap significantly but not entirely, the solstice bounds for the solar
|
||||
/// year *may* not include `date`.
|
||||
#[inline]
|
||||
pub fn compute<C: ChineseBased>(date: RataDie) -> Self {
|
||||
let prev_solstice = winter_solstice_on_or_before::<C>(date);
|
||||
let (new_year, next_solstice) = new_year_on_or_before_fixed_date::<C>(date, prev_solstice);
|
||||
// Using 400 here since new years can be up to 390 days apart, and we add some padding
|
||||
let next_new_year = new_year_on_or_before_fixed_date::<C>(new_year + 400, next_solstice).0;
|
||||
|
||||
Self {
|
||||
new_year,
|
||||
next_new_year,
|
||||
}
|
||||
}
|
||||
|
||||
/// The number of days in this year
|
||||
pub fn count_days(self) -> u16 {
|
||||
let result = self.next_new_year - self.new_year;
|
||||
debug_assert!(
|
||||
((u16::MIN as i64)..=(u16::MAX as i64)).contains(&result),
|
||||
"Days in year should be in range of u16."
|
||||
);
|
||||
result as u16
|
||||
}
|
||||
|
||||
/// Whether or not this is a leap year
|
||||
pub fn is_leap(self) -> bool {
|
||||
let difference = self.next_new_year - self.new_year;
|
||||
difference > 365
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the current major solar term of a fixed date, output as an integer from 1..=12.
|
||||
///
|
||||
/// Based on functions from _Calendrical Calculations_ by Reingold & Dershowitz.
|
||||
/// Lisp reference code: https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L5273-L5281
|
||||
pub(crate) fn major_solar_term_from_fixed<C: ChineseBased>(date: RataDie) -> u32 {
|
||||
let moment: Moment = date.as_moment();
|
||||
let location = C::location(date);
|
||||
let universal: Moment = Location::universal_from_standard(moment, location);
|
||||
let solar_longitude =
|
||||
i64_to_i32(Astronomical::solar_longitude(Astronomical::julian_centuries(universal)) as i64);
|
||||
debug_assert!(
|
||||
solar_longitude.is_ok(),
|
||||
"Solar longitude should be in range of i32"
|
||||
);
|
||||
let s = solar_longitude.unwrap_or_else(|e| e.saturate());
|
||||
let result_signed = (2 + s.div_euclid(30) - 1).rem_euclid(12) + 1;
|
||||
debug_assert!(result_signed >= 0);
|
||||
result_signed as u32
|
||||
}
|
||||
|
||||
/// Returns true if the month of a given fixed date does not have a major solar term,
|
||||
/// false otherwise.
|
||||
///
|
||||
/// Based on functions from _Calendrical Calculations_ by Reingold & Dershowitz.
|
||||
/// Lisp reference code: https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L5345-L5351
|
||||
pub(crate) fn no_major_solar_term<C: ChineseBased>(date: RataDie) -> bool {
|
||||
major_solar_term_from_fixed::<C>(date)
|
||||
== major_solar_term_from_fixed::<C>(new_moon_on_or_after::<C>((date + 1).as_moment()))
|
||||
}
|
||||
|
||||
/// The fixed date in standard time at the observation location of the next new moon on or after a given Moment.
|
||||
///
|
||||
/// Based on functions from _Calendrical Calculations_ by Reingold & Dershowitz.
|
||||
/// Lisp reference code: https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L5329-L5338
|
||||
pub(crate) fn new_moon_on_or_after<C: ChineseBased>(moment: Moment) -> RataDie {
|
||||
let new_moon_moment = Astronomical::new_moon_at_or_after(midnight::<C>(moment));
|
||||
let location = C::location(new_moon_moment.as_rata_die());
|
||||
Location::standard_from_universal(new_moon_moment, location).as_rata_die()
|
||||
}
|
||||
|
||||
/// The fixed date in standard time at the observation location of the previous new moon before a given Moment.
|
||||
///
|
||||
/// Based on functions from _Calendrical Calculations_ by Reingold & Dershowitz.
|
||||
/// Lisp reference code: https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L5318-L5327
|
||||
pub(crate) fn new_moon_before<C: ChineseBased>(moment: Moment) -> RataDie {
|
||||
let new_moon_moment = Astronomical::new_moon_before(midnight::<C>(moment));
|
||||
let location = C::location(new_moon_moment.as_rata_die());
|
||||
Location::standard_from_universal(new_moon_moment, location).as_rata_die()
|
||||
}
|
||||
|
||||
/// Universal time of midnight at start of a Moment's day at the observation location
|
||||
///
|
||||
/// Based on functions from _Calendrical Calculations_ by Reingold & Dershowitz.
|
||||
/// Lisp reference code: https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L5353-L5357
|
||||
pub(crate) fn midnight<C: ChineseBased>(moment: Moment) -> Moment {
|
||||
Location::universal_from_standard(moment, C::location(moment.as_rata_die()))
|
||||
}
|
||||
|
||||
/// Determines the fixed date of the lunar new year given the start of its corresponding solar year (歲), which is
|
||||
/// also the winter solstice
|
||||
///
|
||||
/// Based on functions from _Calendrical Calculations_ by Reingold & Dershowitz.
|
||||
/// Lisp reference code: https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L5370-L5394
|
||||
pub(crate) fn new_year_in_sui<C: ChineseBased>(prior_solstice: RataDie) -> (RataDie, RataDie) {
|
||||
// s1 is prior_solstice
|
||||
// Using 370 here since solstices are ~365 days apart
|
||||
let following_solstice = winter_solstice_on_or_before::<C>(prior_solstice + 370); // s2
|
||||
let month_after_eleventh = new_moon_on_or_after::<C>((prior_solstice + 1).as_moment()); // m12
|
||||
let month_after_twelfth = new_moon_on_or_after::<C>((month_after_eleventh + 1).as_moment()); // m13
|
||||
let next_eleventh_month = new_moon_before::<C>((following_solstice + 1).as_moment()); // next-m11
|
||||
let lhs_argument =
|
||||
((next_eleventh_month - month_after_eleventh) as f64 / MEAN_SYNODIC_MONTH).round() as i64;
|
||||
if lhs_argument == 12
|
||||
&& (no_major_solar_term::<C>(month_after_eleventh)
|
||||
|| no_major_solar_term::<C>(month_after_twelfth))
|
||||
{
|
||||
(
|
||||
new_moon_on_or_after::<C>((month_after_twelfth + 1).as_moment()),
|
||||
following_solstice,
|
||||
)
|
||||
} else {
|
||||
(month_after_twelfth, following_solstice)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the moment of the nearest winter solstice on or before a given fixed date
|
||||
///
|
||||
/// Based on functions from _Calendrical Calculations_ by Reingold & Dershowitz.
|
||||
/// Lisp reference code: https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L5359-L5368
|
||||
pub(crate) fn winter_solstice_on_or_before<C: ChineseBased>(date: RataDie) -> RataDie {
|
||||
let approx = Astronomical::estimate_prior_solar_longitude(
|
||||
astronomy::WINTER,
|
||||
midnight::<C>((date + 1).as_moment()),
|
||||
);
|
||||
let mut iters = 0;
|
||||
let mut day = Moment::new((approx.inner() - 1.0).floor());
|
||||
while iters < MAX_ITERS_FOR_MONTHS_OF_YEAR
|
||||
&& astronomy::WINTER
|
||||
>= Astronomical::solar_longitude(Astronomical::julian_centuries(midnight::<C>(
|
||||
day + 1.0,
|
||||
)))
|
||||
{
|
||||
iters += 1;
|
||||
day += 1.0;
|
||||
}
|
||||
debug_assert!(
|
||||
iters < MAX_ITERS_FOR_MONTHS_OF_YEAR,
|
||||
"Number of iterations was higher than expected"
|
||||
);
|
||||
day.as_rata_die()
|
||||
}
|
||||
|
||||
/// Get the fixed date of the nearest Lunar New Year on or before a given fixed date.
|
||||
/// This function also returns the solstice following a given date for optimization (see #3743).
|
||||
///
|
||||
/// To call this function you must precompute the value of the prior solstice, which
|
||||
/// is the result of winter_solstice_on_or_before
|
||||
///
|
||||
/// Based on functions from _Calendrical Calculations_ by Reingold & Dershowitz.
|
||||
/// Lisp reference code: https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L5396-L5405
|
||||
pub(crate) fn new_year_on_or_before_fixed_date<C: ChineseBased>(
|
||||
date: RataDie,
|
||||
prior_solstice: RataDie,
|
||||
) -> (RataDie, RataDie) {
|
||||
let new_year = new_year_in_sui::<C>(prior_solstice);
|
||||
if date >= new_year.0 {
|
||||
new_year
|
||||
} else {
|
||||
// This happens when we're at the end of the current lunar year
|
||||
// and the solstice has already happened. Thus the relevant solstice
|
||||
// for the current lunar year is the previous one, which we calculate by offsetting
|
||||
// back by a year.
|
||||
let date_in_last_sui = date - 180; // This date is in the current lunar year, but the last solar year
|
||||
let prior_solstice = winter_solstice_on_or_before::<C>(date_in_last_sui);
|
||||
new_year_in_sui::<C>(prior_solstice)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a RataDie in the middle of a year; this is not necessarily meant for direct use in
|
||||
/// calculations; rather, it is useful for getting a RataDie guaranteed to be in a given year
|
||||
/// as input for other calculations like calculating the leap month in a year.
|
||||
///
|
||||
/// Based on functions from _Calendrical Calculations_ by Reingold & Dershowitz
|
||||
/// Lisp reference code: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L5469-L5475>
|
||||
pub fn fixed_mid_year_from_year<C: ChineseBased>(elapsed_years: i32) -> RataDie {
|
||||
let cycle = (elapsed_years - 1).div_euclid(60) + 1;
|
||||
let year = (elapsed_years - 1).rem_euclid(60) + 1;
|
||||
C::EPOCH + ((((cycle - 1) * 60 + year - 1) as f64 + 0.5) * MEAN_TROPICAL_YEAR) as i64
|
||||
}
|
||||
|
||||
/// Whether this year is a leap year
|
||||
pub fn is_leap_year<C: ChineseBased>(year: i32) -> bool {
|
||||
let mid_year = fixed_mid_year_from_year::<C>(year);
|
||||
YearBounds::compute::<C>(mid_year).is_leap()
|
||||
}
|
||||
|
||||
/// The last month and day in this year
|
||||
pub fn last_month_day_in_year<C: ChineseBased>(year: i32) -> (u8, u8) {
|
||||
let mid_year = fixed_mid_year_from_year::<C>(year);
|
||||
let year_bounds = YearBounds::compute::<C>(mid_year);
|
||||
let last_day = year_bounds.next_new_year - 1;
|
||||
let month = if year_bounds.is_leap() { 13 } else { 12 };
|
||||
let day = last_day - new_moon_before::<C>(last_day.as_moment()) + 1;
|
||||
(month, day as u8)
|
||||
}
|
||||
|
||||
/// Calculated the numbers of days in the given year
|
||||
pub fn days_in_provided_year<C: ChineseBased>(year: i32) -> u16 {
|
||||
let mid_year = fixed_mid_year_from_year::<C>(year);
|
||||
let bounds = YearBounds::compute::<C>(mid_year);
|
||||
|
||||
bounds.count_days()
|
||||
}
|
||||
|
||||
/// chinese_based_date_from_fixed returns extra things for use in caching
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub struct ChineseFromFixedResult {
|
||||
/// The chinese year
|
||||
pub year: i32,
|
||||
/// The chinese month
|
||||
pub month: u8,
|
||||
/// The chinese day
|
||||
pub day: u8,
|
||||
/// The bounds of the current lunar year
|
||||
pub year_bounds: YearBounds,
|
||||
/// The index of the leap month, if any
|
||||
pub leap_month: Option<NonZeroU8>,
|
||||
}
|
||||
|
||||
/// Get a chinese based date from a fixed date, with the related ISO year
|
||||
///
|
||||
/// Months are calculated by iterating through the dates of new moons until finding the last month which
|
||||
/// does not exceed the given fixed date. The day of month is calculated by subtracting the fixed date
|
||||
/// from the fixed date of the beginning of the month.
|
||||
///
|
||||
/// The calculation for `elapsed_years` and `month` in this function are based on code from _Calendrical Calculations_ by Reingold & Dershowitz.
|
||||
/// Lisp reference code: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L5414-L5459>
|
||||
pub fn chinese_based_date_from_fixed<C: ChineseBased>(date: RataDie) -> ChineseFromFixedResult {
|
||||
let year_bounds = YearBounds::compute::<C>(date);
|
||||
let first_day_of_year = year_bounds.new_year;
|
||||
|
||||
let year_float =
|
||||
(1.5 - 1.0 / 12.0 + ((first_day_of_year - C::EPOCH) as f64) / MEAN_TROPICAL_YEAR).floor();
|
||||
let year_int = i64_to_i32(year_float as i64);
|
||||
debug_assert!(year_int.is_ok(), "Year should be in range of i32");
|
||||
let year = year_int.unwrap_or_else(|e| e.saturate());
|
||||
|
||||
let new_moon = new_moon_before::<C>((date + 1).as_moment());
|
||||
let month_i64 = ((new_moon - first_day_of_year) as f64 / MEAN_SYNODIC_MONTH).round() as i64 + 1;
|
||||
debug_assert!(
|
||||
((u8::MIN as i64)..=(u8::MAX as i64)).contains(&month_i64),
|
||||
"Month should be in range of u8! Value {month_i64} failed for RD {date:?}"
|
||||
);
|
||||
let month = month_i64 as u8;
|
||||
let day_i64 = date - new_moon + 1;
|
||||
debug_assert!(
|
||||
((u8::MIN as i64)..=(u8::MAX as i64)).contains(&month_i64),
|
||||
"Day should be in range of u8! Value {month_i64} failed for RD {date:?}"
|
||||
);
|
||||
let day = day_i64 as u8;
|
||||
let leap_month = if year_bounds.is_leap() {
|
||||
// This doesn't need to be checked for `None`, since `get_leap_month_from_new_year`
|
||||
// will always return a number greater than or equal to 1, and less than 14.
|
||||
NonZeroU8::new(get_leap_month_from_new_year::<C>(first_day_of_year))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
ChineseFromFixedResult {
|
||||
year,
|
||||
month,
|
||||
day,
|
||||
year_bounds,
|
||||
leap_month,
|
||||
}
|
||||
}
|
||||
|
||||
/// Given that `new_year` is the first day of a leap year, find which month in the year is a leap month.
|
||||
/// Since the first month in which there are no major solar terms is a leap month, this function
|
||||
/// cycles through months until it finds the leap month, then returns the number of that month. This
|
||||
/// function assumes the date passed in is in a leap year and tests to ensure this is the case in debug
|
||||
/// mode by asserting that no more than thirteen months are analyzed.
|
||||
///
|
||||
/// Conceptually similar to code from _Calendrical Calculations_ by Reingold & Dershowitz
|
||||
/// Lisp reference code: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L5443-L5450>
|
||||
pub fn get_leap_month_from_new_year<C: ChineseBased>(new_year: RataDie) -> u8 {
|
||||
let mut cur = new_year;
|
||||
let mut result = 1;
|
||||
while result < MAX_ITERS_FOR_MONTHS_OF_YEAR && !no_major_solar_term::<C>(cur) {
|
||||
cur = new_moon_on_or_after::<C>((cur + 1).as_moment());
|
||||
result += 1;
|
||||
}
|
||||
debug_assert!(result < MAX_ITERS_FOR_MONTHS_OF_YEAR, "The given year was not a leap year and an unexpected number of iterations occurred searching for a leap month.");
|
||||
result
|
||||
}
|
||||
|
||||
/// Returns the number of days in the given (year, month). In the Chinese calendar, months start at each
|
||||
/// new moon, so this function finds the number of days between the new moon at the beginning of the given
|
||||
/// month and the new moon at the beginning of the next month.
|
||||
pub fn month_days<C: ChineseBased>(year: i32, month: u8) -> u8 {
|
||||
let mid_year = fixed_mid_year_from_year::<C>(year);
|
||||
let prev_solstice = winter_solstice_on_or_before::<C>(mid_year);
|
||||
let new_year = new_year_on_or_before_fixed_date::<C>(mid_year, prev_solstice).0;
|
||||
days_in_month::<C>(month, new_year, None).0
|
||||
}
|
||||
|
||||
/// Returns the number of days in the given `month` after the given `new_year`.
|
||||
/// Also returns the RataDie of the new moon beginning the next month.
|
||||
pub fn days_in_month<C: ChineseBased>(
|
||||
month: u8,
|
||||
new_year: RataDie,
|
||||
prev_new_moon: Option<RataDie>,
|
||||
) -> (u8, RataDie) {
|
||||
let approx = new_year + ((month - 1) as i64 * 29);
|
||||
let prev_new_moon = if let Some(prev_moon) = prev_new_moon {
|
||||
prev_moon
|
||||
} else {
|
||||
new_moon_before::<C>((approx + 15).as_moment())
|
||||
};
|
||||
let next_new_moon = new_moon_on_or_after::<C>((approx + 15).as_moment());
|
||||
let result = (next_new_moon - prev_new_moon) as u8;
|
||||
debug_assert!(result == 29 || result == 30);
|
||||
(result, next_new_moon)
|
||||
}
|
||||
|
||||
/// Given the new year and a month/day pair, calculate the number of days until the first day of the given month
|
||||
pub fn days_until_month<C: ChineseBased>(new_year: RataDie, month: u8) -> u16 {
|
||||
let month_approx = 28_u16.saturating_mul(u16::from(month) - 1);
|
||||
|
||||
let new_moon = new_moon_on_or_after::<C>(new_year.as_moment() + (month_approx as f64));
|
||||
let result = new_moon - new_year;
|
||||
debug_assert!(((u16::MIN as i64)..=(u16::MAX as i64)).contains(&result), "Result {result} from new moon: {new_moon:?} and new year: {new_year:?} should be in range of u16!");
|
||||
result as u16
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
use super::*;
|
||||
use crate::rata_die::Moment;
|
||||
|
||||
#[test]
|
||||
fn test_chinese_new_moon_directionality() {
|
||||
for i in (-1000..1000).step_by(31) {
|
||||
let moment = Moment::new(i as f64);
|
||||
let before = new_moon_before::<Chinese>(moment);
|
||||
let after = new_moon_on_or_after::<Chinese>(moment);
|
||||
assert!(before < after, "Chinese new moon directionality failed for Moment: {moment:?}, with:\n\tBefore: {before:?}\n\tAfter: {after:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chinese_new_year_on_or_before() {
|
||||
let fixed = crate::iso::fixed_from_iso(2023, 6, 22);
|
||||
let prev_solstice = winter_solstice_on_or_before::<Chinese>(fixed);
|
||||
let result_fixed = new_year_on_or_before_fixed_date::<Chinese>(fixed, prev_solstice).0;
|
||||
let (y, m, d) = crate::iso::iso_from_fixed(result_fixed).unwrap();
|
||||
assert_eq!(y, 2023);
|
||||
assert_eq!(m, 1);
|
||||
assert_eq!(d, 22);
|
||||
}
|
||||
|
||||
fn seollal_on_or_before(fixed: RataDie) -> RataDie {
|
||||
let prev_solstice = winter_solstice_on_or_before::<Dangi>(fixed);
|
||||
new_year_on_or_before_fixed_date::<Dangi>(fixed, prev_solstice).0
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seollal() {
|
||||
#[derive(Debug)]
|
||||
struct TestCase {
|
||||
iso_year: i32,
|
||||
iso_month: u8,
|
||||
iso_day: u8,
|
||||
expected_year: i32,
|
||||
expected_month: u8,
|
||||
expected_day: u8,
|
||||
}
|
||||
|
||||
let cases = [
|
||||
TestCase {
|
||||
iso_year: 2024,
|
||||
iso_month: 6,
|
||||
iso_day: 6,
|
||||
expected_year: 2024,
|
||||
expected_month: 2,
|
||||
expected_day: 10,
|
||||
},
|
||||
TestCase {
|
||||
iso_year: 2024,
|
||||
iso_month: 2,
|
||||
iso_day: 9,
|
||||
expected_year: 2023,
|
||||
expected_month: 1,
|
||||
expected_day: 22,
|
||||
},
|
||||
TestCase {
|
||||
iso_year: 2023,
|
||||
iso_month: 1,
|
||||
iso_day: 22,
|
||||
expected_year: 2023,
|
||||
expected_month: 1,
|
||||
expected_day: 22,
|
||||
},
|
||||
TestCase {
|
||||
iso_year: 2023,
|
||||
iso_month: 1,
|
||||
iso_day: 21,
|
||||
expected_year: 2022,
|
||||
expected_month: 2,
|
||||
expected_day: 1,
|
||||
},
|
||||
TestCase {
|
||||
iso_year: 2022,
|
||||
iso_month: 6,
|
||||
iso_day: 6,
|
||||
expected_year: 2022,
|
||||
expected_month: 2,
|
||||
expected_day: 1,
|
||||
},
|
||||
TestCase {
|
||||
iso_year: 2021,
|
||||
iso_month: 6,
|
||||
iso_day: 6,
|
||||
expected_year: 2021,
|
||||
expected_month: 2,
|
||||
expected_day: 12,
|
||||
},
|
||||
TestCase {
|
||||
iso_year: 2020,
|
||||
iso_month: 6,
|
||||
iso_day: 6,
|
||||
expected_year: 2020,
|
||||
expected_month: 1,
|
||||
expected_day: 25,
|
||||
},
|
||||
TestCase {
|
||||
iso_year: 2019,
|
||||
iso_month: 6,
|
||||
iso_day: 6,
|
||||
expected_year: 2019,
|
||||
expected_month: 2,
|
||||
expected_day: 5,
|
||||
},
|
||||
TestCase {
|
||||
iso_year: 2018,
|
||||
iso_month: 6,
|
||||
iso_day: 6,
|
||||
expected_year: 2018,
|
||||
expected_month: 2,
|
||||
expected_day: 16,
|
||||
},
|
||||
TestCase {
|
||||
iso_year: 2025,
|
||||
iso_month: 6,
|
||||
iso_day: 6,
|
||||
expected_year: 2025,
|
||||
expected_month: 1,
|
||||
expected_day: 29,
|
||||
},
|
||||
TestCase {
|
||||
iso_year: 2026,
|
||||
iso_month: 8,
|
||||
iso_day: 8,
|
||||
expected_year: 2026,
|
||||
expected_month: 2,
|
||||
expected_day: 17,
|
||||
},
|
||||
TestCase {
|
||||
iso_year: 2027,
|
||||
iso_month: 4,
|
||||
iso_day: 4,
|
||||
expected_year: 2027,
|
||||
expected_month: 2,
|
||||
expected_day: 7,
|
||||
},
|
||||
TestCase {
|
||||
iso_year: 2028,
|
||||
iso_month: 9,
|
||||
iso_day: 21,
|
||||
expected_year: 2028,
|
||||
expected_month: 1,
|
||||
expected_day: 27,
|
||||
},
|
||||
];
|
||||
|
||||
for case in cases {
|
||||
let fixed = crate::iso::fixed_from_iso(case.iso_year, case.iso_month, case.iso_day);
|
||||
let seollal = seollal_on_or_before(fixed);
|
||||
let (y, m, d) = crate::iso::iso_from_fixed(seollal).unwrap();
|
||||
assert_eq!(
|
||||
y, case.expected_year,
|
||||
"Year check failed for case: {case:?}"
|
||||
);
|
||||
assert_eq!(
|
||||
m, case.expected_month,
|
||||
"Month check failed for case: {case:?}"
|
||||
);
|
||||
assert_eq!(d, case.expected_day, "Day check failed for case: {case:?}");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
// This file is part of ICU4X.
|
||||
//
|
||||
// The contents of this file implement algorithms from Calendrical Calculations
|
||||
// by Reingold & Dershowitz, Cambridge University Press, 4th edition (2018),
|
||||
// which have been released as Lisp code at <https://github.com/EdReingold/calendar-code2/>
|
||||
// under the Apache-2.0 license. Accordingly, this file is released under
|
||||
// the Apache License, Version 2.0 which can be found at the calendrical_calculations
|
||||
// package root or at http://www.apache.org/licenses/LICENSE-2.0.
|
||||
|
||||
use crate::helpers::{i64_to_i32, I32CastError};
|
||||
use crate::rata_die::RataDie;
|
||||
|
||||
pub(crate) const COPTIC_EPOCH: RataDie = crate::julian::fixed_from_julian(284, 8, 29);
|
||||
|
||||
/// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/1ee51ecfaae6f856b0d7de3e36e9042100b4f424/calendar.l#L1978>
|
||||
pub fn fixed_from_coptic(year: i32, month: u8, day: u8) -> RataDie {
|
||||
COPTIC_EPOCH - 1
|
||||
+ 365 * (year as i64 - 1)
|
||||
+ year.div_euclid(4) as i64
|
||||
+ 30 * (month as i64 - 1)
|
||||
+ day as i64
|
||||
}
|
||||
|
||||
/// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/1ee51ecfaae6f856b0d7de3e36e9042100b4f424/calendar.l#L1990>
|
||||
pub fn coptic_from_fixed(date: RataDie) -> Result<(i32, u8, u8), I32CastError> {
|
||||
let year = (4 * (date - COPTIC_EPOCH) + 1463).div_euclid(1461);
|
||||
let year = i64_to_i32(year)?;
|
||||
let month = ((date - fixed_from_coptic(year, 1, 1)).div_euclid(30) + 1) as u8; // <= 12 < u8::MAX
|
||||
let day = (date + 1 - fixed_from_coptic(year, month, 1)) as u8; // <= days_in_month < u8::MAX
|
||||
|
||||
Ok((year, month, day))
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
// This file is part of ICU4X.
|
||||
//
|
||||
// The contents of this file implement algorithms from Calendrical Calculations
|
||||
// by Reingold & Dershowitz, Cambridge University Press, 4th edition (2018),
|
||||
// which have been released as Lisp code at <https://github.com/EdReingold/calendar-code2/>
|
||||
// under the Apache-2.0 license. Accordingly, this file is released under
|
||||
// the Apache License, Version 2.0 which can be found at the calendrical_calculations
|
||||
// package root or at http://www.apache.org/licenses/LICENSE-2.0.
|
||||
|
||||
use displaydoc::Display;
|
||||
|
||||
/// A list of error outcomes for exceeding location bounds
|
||||
#[derive(Display, Debug, Copy, Clone, PartialEq)]
|
||||
#[non_exhaustive]
|
||||
pub enum LocationOutOfBoundsError {
|
||||
/// Latitude value was out of bounds
|
||||
#[displaydoc("Latitude {0} outside bounds of -90 to 90")]
|
||||
Latitude(f64),
|
||||
|
||||
/// Longitude value was out of bounds
|
||||
#[displaydoc("Longitude {0} outside bounds of -180 to 180")]
|
||||
Longitude(f64),
|
||||
|
||||
/// Offset value was out of bounds
|
||||
#[displaydoc("Offset {0} outside bounds of {1} to {2}")]
|
||||
Offset(f64, f64, f64),
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
// This file is part of ICU4X.
|
||||
//
|
||||
// The contents of this file implement algorithms from Calendrical Calculations
|
||||
// by Reingold & Dershowitz, Cambridge University Press, 4th edition (2018),
|
||||
// which have been released as Lisp code at <https://github.com/EdReingold/calendar-code2/>
|
||||
// under the Apache-2.0 license. Accordingly, this file is released under
|
||||
// the Apache License, Version 2.0 which can be found at the calendrical_calculations
|
||||
// package root or at http://www.apache.org/licenses/LICENSE-2.0.
|
||||
|
||||
use crate::helpers::I32CastError;
|
||||
use crate::rata_die::RataDie;
|
||||
|
||||
const ETHIOPIC_TO_COPTIC_OFFSET: i64 =
|
||||
super::coptic::COPTIC_EPOCH.const_diff(crate::julian::fixed_from_julian(8, 8, 29));
|
||||
|
||||
/// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/1ee51ecfaae6f856b0d7de3e36e9042100b4f424/calendar.l#L2017>
|
||||
pub fn fixed_from_ethiopian(year: i32, month: u8, day: u8) -> RataDie {
|
||||
crate::coptic::fixed_from_coptic(year, month, day) - ETHIOPIC_TO_COPTIC_OFFSET
|
||||
}
|
||||
|
||||
/// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/1ee51ecfaae6f856b0d7de3e36e9042100b4f424/calendar.l#L2028>
|
||||
pub fn ethiopian_from_fixed(date: RataDie) -> Result<(i32, u8, u8), I32CastError> {
|
||||
crate::coptic::coptic_from_fixed(date + ETHIOPIC_TO_COPTIC_OFFSET)
|
||||
}
|
|
@ -0,0 +1,707 @@
|
|||
// This file is part of ICU4X.
|
||||
//
|
||||
// The contents of this file implement algorithms from Calendrical Calculations
|
||||
// by Reingold & Dershowitz, Cambridge University Press, 4th edition (2018),
|
||||
// which have been released as Lisp code at <https://github.com/EdReingold/calendar-code2/>
|
||||
// under the Apache-2.0 license. Accordingly, this file is released under
|
||||
// the Apache License, Version 2.0 which can be found at the calendrical_calculations
|
||||
// package root or at http://www.apache.org/licenses/LICENSE-2.0.
|
||||
|
||||
use crate::helpers::{final_func, i64_to_i32, next_u8};
|
||||
use crate::rata_die::{Moment, RataDie};
|
||||
#[allow(unused_imports)]
|
||||
use core_maths::*;
|
||||
|
||||
/// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L2206>
|
||||
const FIXED_HEBREW_EPOCH: RataDie = crate::julian::fixed_from_julian_book_version(-3761, 10, 7);
|
||||
|
||||
/// Biblical Hebrew dates. The months are reckoned a bit strangely, with the new year occurring on
|
||||
/// Tishri (as in the civil calendar) but the months being numbered in a different order
|
||||
#[derive(Copy, Clone, Debug, Default, Hash, Eq, PartialEq, PartialOrd, Ord)]
|
||||
#[allow(clippy::exhaustive_structs)]
|
||||
pub struct BookHebrew {
|
||||
/// The year
|
||||
pub year: i32,
|
||||
/// The month
|
||||
pub month: u8,
|
||||
/// The day
|
||||
pub day: u8,
|
||||
}
|
||||
|
||||
// The BookHebrew Months
|
||||
/// The biblical month number used for the month of Nisan
|
||||
pub const NISAN: u8 = 1;
|
||||
/// The biblical month number used for the month of Iyyar
|
||||
pub const IYYAR: u8 = 2;
|
||||
/// The biblical month number used for the month of Sivan
|
||||
pub const SIVAN: u8 = 3;
|
||||
/// The biblical month number used for the month of Tammuz
|
||||
pub const TAMMUZ: u8 = 4;
|
||||
/// The biblical month number used for the month of Av
|
||||
pub const AV: u8 = 5;
|
||||
/// The biblical month number used for the month of Elul
|
||||
pub const ELUL: u8 = 6;
|
||||
/// The biblical month number used for the month of Tishri
|
||||
pub const TISHRI: u8 = 7;
|
||||
/// The biblical month number used for the month of Marheshvan
|
||||
pub const MARHESHVAN: u8 = 8;
|
||||
/// The biblical month number used for the month of Kislev
|
||||
pub const KISLEV: u8 = 9;
|
||||
/// The biblical month number used for the month of Tevet
|
||||
pub const TEVET: u8 = 10;
|
||||
/// The biblical month number used for the month of Shevat
|
||||
pub const SHEVAT: u8 = 11;
|
||||
/// The biblical month number used for the month of Adar (and Adar I)
|
||||
pub const ADAR: u8 = 12;
|
||||
/// The biblical month number used for the month of Adar II
|
||||
pub const ADARII: u8 = 13;
|
||||
|
||||
// BIBLICAL HEBREW CALENDAR FUNCTIONS
|
||||
|
||||
impl BookHebrew {
|
||||
/// The civil calendar has the same year and day numbering as the book one, but the months are numbered
|
||||
/// differently
|
||||
pub fn to_civil_date(self) -> (i32, u8, u8) {
|
||||
let biblical_month = self.month;
|
||||
let biblical_year = self.year;
|
||||
let mut civil_month;
|
||||
civil_month = (biblical_month + 6) % 12;
|
||||
|
||||
if civil_month == 0 {
|
||||
civil_month = 12;
|
||||
}
|
||||
|
||||
if Self::is_hebrew_leap_year(biblical_year) && biblical_month < TISHRI {
|
||||
civil_month += 1;
|
||||
}
|
||||
(biblical_year, civil_month, self.day)
|
||||
}
|
||||
|
||||
/// The civil calendar has the same year and day numbering as the book one, but the months are numbered
|
||||
/// differently
|
||||
pub fn from_civil_date(civil_year: i32, civil_month: u8, civil_day: u8) -> Self {
|
||||
let mut biblical_month;
|
||||
|
||||
if civil_month <= 6 {
|
||||
biblical_month = civil_month + 6; // months 1-6 correspond to biblical months 7-12
|
||||
} else {
|
||||
biblical_month = civil_month - 6; // months 7-12 correspond to biblical months 1-6
|
||||
if Self::is_hebrew_leap_year(civil_year) {
|
||||
biblical_month -= 1
|
||||
}
|
||||
if biblical_month == 0 {
|
||||
// Special case for Adar II in a leap year
|
||||
biblical_month = 13;
|
||||
}
|
||||
}
|
||||
|
||||
BookHebrew {
|
||||
year: civil_year,
|
||||
month: biblical_month,
|
||||
day: civil_day,
|
||||
}
|
||||
}
|
||||
// Moment of mean conjunction (New Moon) of h_month in BookHebrew
|
||||
/// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L2244>
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn molad(book_year: i32, book_month: u8) -> Moment {
|
||||
let y = if book_month < TISHRI {
|
||||
book_year + 1
|
||||
} else {
|
||||
book_year
|
||||
}; // Treat Nisan as start of year
|
||||
|
||||
let months_elapsed = (book_month as f64 - TISHRI as f64) // Months this year
|
||||
+ ((235.0 * y as f64 - 234.0) / 19.0).floor(); // Months until New Year.
|
||||
|
||||
Moment::new(
|
||||
FIXED_HEBREW_EPOCH.to_f64_date() - (876.0 / 25920.0)
|
||||
+ months_elapsed * (29.0 + (1.0 / 2.0) + (793.0 / 25920.0)),
|
||||
)
|
||||
}
|
||||
|
||||
// ADAR = 12, ADARII = 13
|
||||
/// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L2217>
|
||||
#[allow(dead_code)]
|
||||
fn last_month_of_book_hebrew_year(book_year: i32) -> u8 {
|
||||
if Self::is_hebrew_leap_year(book_year) {
|
||||
ADARII
|
||||
} else {
|
||||
ADAR
|
||||
}
|
||||
}
|
||||
|
||||
// Number of days elapsed from the (Sunday) noon prior to the epoch of the BookHebrew Calendar to the molad of Tishri of BookHebrew year (h_year) or one day later
|
||||
/// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L2261>
|
||||
fn book_hebrew_calendar_elapsed_days(book_year: i32) -> i32 {
|
||||
let months_elapsed = ((235.0 * book_year as f64 - 234.0) / 19.0).floor() as i64;
|
||||
let parts_elapsed = 12084 + 13753 * months_elapsed;
|
||||
let days = 29 * months_elapsed + (parts_elapsed as f64 / 25920.0).floor() as i64;
|
||||
|
||||
if (3 * (days + 1)).rem_euclid(7) < 3 {
|
||||
days as i32 + 1
|
||||
} else {
|
||||
days as i32
|
||||
}
|
||||
}
|
||||
|
||||
// Delays to start of BookHebrew year to keep ordinary year in range 353-356 and leap year in range 383-386
|
||||
/// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L2301>
|
||||
fn book_hebrew_year_length_correction(book_year: i32) -> u8 {
|
||||
let ny0 = Self::book_hebrew_calendar_elapsed_days(book_year - 1);
|
||||
let ny1 = Self::book_hebrew_calendar_elapsed_days(book_year);
|
||||
let ny2 = Self::book_hebrew_calendar_elapsed_days(book_year + 1);
|
||||
|
||||
if (ny2 - ny1) == 356 {
|
||||
2
|
||||
} else if (ny1 - ny0) == 382 {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
// Fixed date of BookHebrew new year
|
||||
/// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L2294>
|
||||
pub fn book_hebrew_new_year(book_year: i32) -> RataDie {
|
||||
RataDie::new(
|
||||
FIXED_HEBREW_EPOCH.to_i64_date()
|
||||
+ Self::book_hebrew_calendar_elapsed_days(book_year) as i64
|
||||
+ Self::book_hebrew_year_length_correction(book_year) as i64,
|
||||
)
|
||||
}
|
||||
|
||||
/// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L2315>
|
||||
pub fn days_in_book_hebrew_year(book_year: i32) -> u16 {
|
||||
(Self::book_hebrew_new_year(1 + book_year) - Self::book_hebrew_new_year(book_year)) as u16
|
||||
}
|
||||
|
||||
/// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/1ee51ecfaae6f856b0d7de3e36e9042100b4f424/calendar.l#L2275-L2278>
|
||||
pub fn is_hebrew_leap_year(book_year: i32) -> bool {
|
||||
(7 * book_year + 1).rem_euclid(19) < 7
|
||||
}
|
||||
|
||||
// True if the month Marheshvan is going to be long in given BookHebrew year
|
||||
/// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L2321>
|
||||
#[allow(dead_code)]
|
||||
fn is_long_marheshvan(book_year: i32) -> bool {
|
||||
let long_marheshavan_year_lengths = [355, 385];
|
||||
long_marheshavan_year_lengths.contains(&Self::days_in_book_hebrew_year(book_year))
|
||||
}
|
||||
|
||||
// True if the month Kislve is going to be short in given BookHebrew year
|
||||
/// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L2326>
|
||||
#[allow(dead_code)]
|
||||
fn is_short_kislev(book_year: i32) -> bool {
|
||||
let short_kislev_year_lengths = [353, 383];
|
||||
short_kislev_year_lengths.contains(&Self::days_in_book_hebrew_year(book_year))
|
||||
}
|
||||
|
||||
// Last day of month (h_month) in BookHebrew year (book_year)
|
||||
/// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L2230>
|
||||
pub fn last_day_of_book_hebrew_month(book_year: i32, book_month: u8) -> u8 {
|
||||
match book_month {
|
||||
IYYAR | TAMMUZ | ELUL | TEVET | ADARII => 29,
|
||||
ADAR => {
|
||||
if !Self::is_hebrew_leap_year(book_year) {
|
||||
29
|
||||
} else {
|
||||
30
|
||||
}
|
||||
}
|
||||
MARHESHVAN => {
|
||||
if !Self::is_long_marheshvan(book_year) {
|
||||
29
|
||||
} else {
|
||||
30
|
||||
}
|
||||
}
|
||||
KISLEV => {
|
||||
if Self::is_short_kislev(book_year) {
|
||||
29
|
||||
} else {
|
||||
30
|
||||
}
|
||||
}
|
||||
_ => 30,
|
||||
}
|
||||
}
|
||||
|
||||
/// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L2331>
|
||||
pub fn fixed_from_book_hebrew(date: BookHebrew) -> RataDie {
|
||||
let book_year = date.year;
|
||||
let book_month = date.month;
|
||||
let book_day = date.day;
|
||||
|
||||
let mut total_days = Self::book_hebrew_new_year(book_year) + book_day.into() - 1; // (day - 1) Days so far this month.
|
||||
|
||||
if book_month < TISHRI {
|
||||
// Then add days in prior months this year before
|
||||
for m in
|
||||
(TISHRI..=Self::last_month_of_book_hebrew_year(book_year)).chain(NISAN..book_month)
|
||||
{
|
||||
total_days += Self::last_day_of_book_hebrew_month(book_year, m).into();
|
||||
}
|
||||
} else {
|
||||
// Else add days in prior months this year
|
||||
for m in TISHRI..book_month {
|
||||
total_days += Self::last_day_of_book_hebrew_month(book_year, m).into();
|
||||
}
|
||||
}
|
||||
|
||||
total_days
|
||||
}
|
||||
|
||||
/// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L2352>
|
||||
pub fn book_hebrew_from_fixed(date: RataDie) -> BookHebrew {
|
||||
let approx = i64_to_i32(
|
||||
1 + ((date - FIXED_HEBREW_EPOCH) as f64).div_euclid(35975351.0 / 98496.0) as i64, // The value 35975351/98496, the average length of a BookHebrew year, can be approximated by 365.25
|
||||
)
|
||||
.unwrap_or_else(|e| e.saturate());
|
||||
|
||||
// Search forward for the year
|
||||
let year_condition = |year: i32| Self::book_hebrew_new_year(year) <= date;
|
||||
let year = final_func(approx - 1, year_condition);
|
||||
|
||||
// Starting month for search for month.
|
||||
let start = if date
|
||||
< Self::fixed_from_book_hebrew(BookHebrew {
|
||||
year,
|
||||
month: NISAN,
|
||||
day: 1,
|
||||
}) {
|
||||
TISHRI
|
||||
} else {
|
||||
NISAN
|
||||
};
|
||||
|
||||
let month_condition = |m: u8| {
|
||||
date <= Self::fixed_from_book_hebrew(BookHebrew {
|
||||
year,
|
||||
month: m,
|
||||
day: Self::last_day_of_book_hebrew_month(year, m),
|
||||
})
|
||||
};
|
||||
// Search forward from either Tishri or Nisan.
|
||||
let month = next_u8(start, month_condition);
|
||||
|
||||
// Calculate the day by subtraction.
|
||||
let day = (date
|
||||
- Self::fixed_from_book_hebrew(BookHebrew {
|
||||
year,
|
||||
month,
|
||||
day: 1,
|
||||
}))
|
||||
+ 1;
|
||||
|
||||
BookHebrew {
|
||||
year,
|
||||
month,
|
||||
day: day as u8,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct DateCase {
|
||||
year: i32,
|
||||
month: u8,
|
||||
day: u8,
|
||||
}
|
||||
|
||||
static TEST_FIXED_DATE: [i64; 33] = [
|
||||
-214193, -61387, 25469, 49217, 171307, 210155, 253427, 369740, 400085, 434355, 452605,
|
||||
470160, 473837, 507850, 524156, 544676, 567118, 569477, 601716, 613424, 626596, 645554,
|
||||
664224, 671401, 694799, 704424, 708842, 709409, 709580, 727274, 728714, 744313, 764652,
|
||||
];
|
||||
|
||||
static HEBREW_DATES: [DateCase; 33] = [
|
||||
DateCase {
|
||||
year: 3174,
|
||||
month: 5,
|
||||
day: 10,
|
||||
},
|
||||
DateCase {
|
||||
year: 3593,
|
||||
month: 9,
|
||||
day: 25,
|
||||
},
|
||||
DateCase {
|
||||
year: 3831,
|
||||
month: 7,
|
||||
day: 3,
|
||||
},
|
||||
DateCase {
|
||||
year: 3896,
|
||||
month: 7,
|
||||
day: 9,
|
||||
},
|
||||
DateCase {
|
||||
year: 4230,
|
||||
month: 10,
|
||||
day: 18,
|
||||
},
|
||||
DateCase {
|
||||
year: 4336,
|
||||
month: 3,
|
||||
day: 4,
|
||||
},
|
||||
DateCase {
|
||||
year: 4455,
|
||||
month: 8,
|
||||
day: 13,
|
||||
},
|
||||
DateCase {
|
||||
year: 4773,
|
||||
month: 2,
|
||||
day: 6,
|
||||
},
|
||||
DateCase {
|
||||
year: 4856,
|
||||
month: 2,
|
||||
day: 23,
|
||||
},
|
||||
DateCase {
|
||||
year: 4950,
|
||||
month: 1,
|
||||
day: 7,
|
||||
},
|
||||
DateCase {
|
||||
year: 5000,
|
||||
month: 13,
|
||||
day: 8,
|
||||
},
|
||||
DateCase {
|
||||
year: 5048,
|
||||
month: 1,
|
||||
day: 21,
|
||||
},
|
||||
DateCase {
|
||||
year: 5058,
|
||||
month: 2,
|
||||
day: 7,
|
||||
},
|
||||
DateCase {
|
||||
year: 5151,
|
||||
month: 4,
|
||||
day: 1,
|
||||
},
|
||||
DateCase {
|
||||
year: 5196,
|
||||
month: 11,
|
||||
day: 7,
|
||||
},
|
||||
DateCase {
|
||||
year: 5252,
|
||||
month: 1,
|
||||
day: 3,
|
||||
},
|
||||
DateCase {
|
||||
year: 5314,
|
||||
month: 7,
|
||||
day: 1,
|
||||
},
|
||||
DateCase {
|
||||
year: 5320,
|
||||
month: 12,
|
||||
day: 27,
|
||||
},
|
||||
DateCase {
|
||||
year: 5408,
|
||||
month: 3,
|
||||
day: 20,
|
||||
},
|
||||
DateCase {
|
||||
year: 5440,
|
||||
month: 4,
|
||||
day: 3,
|
||||
},
|
||||
DateCase {
|
||||
year: 5476,
|
||||
month: 5,
|
||||
day: 5,
|
||||
},
|
||||
DateCase {
|
||||
year: 5528,
|
||||
month: 4,
|
||||
day: 4,
|
||||
},
|
||||
DateCase {
|
||||
year: 5579,
|
||||
month: 5,
|
||||
day: 11,
|
||||
},
|
||||
DateCase {
|
||||
year: 5599,
|
||||
month: 1,
|
||||
day: 12,
|
||||
},
|
||||
DateCase {
|
||||
year: 5663,
|
||||
month: 1,
|
||||
day: 22,
|
||||
},
|
||||
DateCase {
|
||||
year: 5689,
|
||||
month: 5,
|
||||
day: 19,
|
||||
},
|
||||
DateCase {
|
||||
year: 5702,
|
||||
month: 7,
|
||||
day: 8,
|
||||
},
|
||||
DateCase {
|
||||
year: 5703,
|
||||
month: 1,
|
||||
day: 14,
|
||||
},
|
||||
DateCase {
|
||||
year: 5704,
|
||||
month: 7,
|
||||
day: 8,
|
||||
},
|
||||
DateCase {
|
||||
year: 5752,
|
||||
month: 13,
|
||||
day: 12,
|
||||
},
|
||||
DateCase {
|
||||
year: 5756,
|
||||
month: 12,
|
||||
day: 5,
|
||||
},
|
||||
DateCase {
|
||||
year: 5799,
|
||||
month: 8,
|
||||
day: 12,
|
||||
},
|
||||
DateCase {
|
||||
year: 5854,
|
||||
month: 5,
|
||||
day: 5,
|
||||
},
|
||||
];
|
||||
|
||||
static EXPECTED_MOLAD_DATES: [f64; 33] = [
|
||||
-1850718767f64 / 8640f64,
|
||||
-1591805959f64 / 25920f64,
|
||||
660097927f64 / 25920f64,
|
||||
1275506059f64 / 25920f64,
|
||||
4439806081f64 / 25920f64,
|
||||
605235101f64 / 2880f64,
|
||||
3284237627f64 / 12960f64,
|
||||
9583515841f64 / 25920f64,
|
||||
2592403883f64 / 6480f64,
|
||||
2251656649f64 / 5184f64,
|
||||
11731320839f64 / 25920f64,
|
||||
12185988041f64 / 25920f64,
|
||||
6140833583f64 / 12960f64,
|
||||
6581722991f64 / 12960f64,
|
||||
6792982499f64 / 12960f64,
|
||||
4705980311f64 / 8640f64,
|
||||
14699670013f64 / 25920f64,
|
||||
738006961f64 / 1296f64,
|
||||
1949499007f64 / 3240f64,
|
||||
5299956319f64 / 8640f64,
|
||||
3248250415f64 / 5184f64,
|
||||
16732660061f64 / 25920f64,
|
||||
17216413717f64 / 25920f64,
|
||||
1087650871f64 / 1620f64,
|
||||
2251079609f64 / 3240f64,
|
||||
608605601f64 / 864f64,
|
||||
306216383f64 / 432f64,
|
||||
18387526207f64 / 25920f64,
|
||||
3678423761f64 / 5184f64,
|
||||
1570884431f64 / 2160f64,
|
||||
18888119389f64 / 25920f64,
|
||||
19292268013f64 / 25920f64,
|
||||
660655045f64 / 864f64,
|
||||
];
|
||||
|
||||
static EXPECTED_LAST_HEBREW_MONTH: [u8; 33] = [
|
||||
12, 12, 12, 12, 12, 12, 12, 12, 13, 12, 13, 12, 12, 12, 12, 13, 12, 13, 12, 13, 12, 12, 12,
|
||||
12, 12, 13, 12, 13, 12, 13, 12, 12, 12,
|
||||
];
|
||||
|
||||
static EXPECTED_HEBREW_ELASPED_CALENDAR_DAYS: [i32; 33] = [
|
||||
1158928, 1311957, 1398894, 1422636, 1544627, 1583342, 1626812, 1742956, 1773254, 1807597,
|
||||
1825848, 1843388, 1847051, 1881010, 1897460, 1917895, 1940545, 1942729, 1974889, 1986554,
|
||||
1999723, 2018712, 2037346, 2044640, 2068027, 2077507, 2082262, 2082617, 2083000, 2100511,
|
||||
2101988, 2117699, 2137779,
|
||||
];
|
||||
|
||||
static EXPECTED_FIXED_HEBREW_NEW_YEAR: [i64; 33] = [
|
||||
-214497, -61470, 25467, 49209, 171200, 209915, 253385, 369529, 399827, 434172, 452421,
|
||||
469963, 473624, 507583, 524033, 544468, 567118, 569302, 601462, 613127, 626296, 645285,
|
||||
663919, 671213, 694600, 704080, 708835, 709190, 709573, 727084, 728561, 744272, 764352,
|
||||
];
|
||||
|
||||
static EXPECTED_DAYS_IN_HEBREW_YEAR: [u16; 33] = [
|
||||
354, 354, 355, 355, 355, 355, 355, 353, 383, 354, 383, 354, 354, 355, 353, 383, 353, 385,
|
||||
353, 383, 355, 354, 354, 354, 355, 385, 355, 383, 354, 385, 355, 354, 355,
|
||||
];
|
||||
|
||||
static EXPECTED_MARHESHVAN_VALUES: [bool; 33] = [
|
||||
false, false, true, true, true, true, true, false, false, false, false, false, false, true,
|
||||
false, false, false, true, false, false, true, false, false, false, true, true, true,
|
||||
false, false, true, true, false, true,
|
||||
];
|
||||
|
||||
static EXPECTED_KISLEV_VALUES: [bool; 33] = [
|
||||
false, false, false, false, false, false, false, true, true, false, true, false, false,
|
||||
false, true, true, true, false, true, true, false, false, false, false, false, false,
|
||||
false, true, false, false, false, false, false,
|
||||
];
|
||||
|
||||
static EXPECTED_DAY_IN_MONTH: [u8; 33] = [
|
||||
30, 30, 30, 30, 29, 30, 30, 29, 29, 30, 29, 30, 29, 29, 30, 30, 30, 30, 30, 29, 30, 29, 30,
|
||||
30, 30, 30, 30, 30, 30, 29, 29, 29, 30,
|
||||
];
|
||||
|
||||
#[allow(dead_code)]
|
||||
static CIVIL_EXPECTED_DAY_IN_MONTH: [u8; 33] = [
|
||||
30, 30, 30, 30, 29, 30, 29, 29, 29, 30, 30, 30, 29, 29, 30, 29, 30, 29, 29, 30, 30, 29, 30,
|
||||
30, 30, 30, 30, 29, 30, 30, 29, 29, 30,
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn test_hebrew_epoch() {
|
||||
// page 119 of the Calendrical Calculations book
|
||||
let fixed_hebrew_date = -1373427.0;
|
||||
assert_eq!(FIXED_HEBREW_EPOCH.to_f64_date(), fixed_hebrew_date);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hebrew_molad() {
|
||||
let precision = 1_00000f64;
|
||||
for (case, expected) in HEBREW_DATES.iter().zip(EXPECTED_MOLAD_DATES.iter()) {
|
||||
let molad =
|
||||
(BookHebrew::molad(case.year, case.month).inner() * precision).round() / precision;
|
||||
let final_expected = (expected * precision).round() / precision;
|
||||
assert_eq!(molad, final_expected, "{case:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_last_book_hebrew_month() {
|
||||
for (case, expected) in HEBREW_DATES.iter().zip(EXPECTED_LAST_HEBREW_MONTH.iter()) {
|
||||
let last_month = BookHebrew::last_month_of_book_hebrew_year(case.year);
|
||||
assert_eq!(last_month, *expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_book_hebrew_calendar_elapsed_days() {
|
||||
for (case, expected) in HEBREW_DATES
|
||||
.iter()
|
||||
.zip(EXPECTED_HEBREW_ELASPED_CALENDAR_DAYS.iter())
|
||||
{
|
||||
let elapsed_days = BookHebrew::book_hebrew_calendar_elapsed_days(case.year);
|
||||
assert_eq!(elapsed_days, *expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_book_hebrew_year_length_correction() {
|
||||
let year_length_correction: [u8; 33] = [
|
||||
2, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
];
|
||||
for (case, expected) in HEBREW_DATES.iter().zip(year_length_correction.iter()) {
|
||||
let correction = BookHebrew::book_hebrew_year_length_correction(case.year);
|
||||
assert_eq!(correction, *expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_book_hebrew_new_year() {
|
||||
for (case, expected) in HEBREW_DATES
|
||||
.iter()
|
||||
.zip(EXPECTED_FIXED_HEBREW_NEW_YEAR.iter())
|
||||
{
|
||||
let f_date = BookHebrew::book_hebrew_new_year(case.year);
|
||||
assert_eq!(f_date.to_i64_date(), *expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_days_in_book_hebrew_year() {
|
||||
for (case, expected) in HEBREW_DATES.iter().zip(EXPECTED_DAYS_IN_HEBREW_YEAR.iter()) {
|
||||
let days_in_year = BookHebrew::days_in_book_hebrew_year(case.year);
|
||||
assert_eq!(days_in_year, *expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_long_marheshvan() {
|
||||
for (case, expected) in HEBREW_DATES.iter().zip(EXPECTED_MARHESHVAN_VALUES.iter()) {
|
||||
let marsheshvan = BookHebrew::is_long_marheshvan(case.year);
|
||||
assert_eq!(marsheshvan, *expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_short_kislev() {
|
||||
for (case, expected) in HEBREW_DATES.iter().zip(EXPECTED_KISLEV_VALUES.iter()) {
|
||||
let kislev = BookHebrew::is_short_kislev(case.year);
|
||||
assert_eq!(kislev, *expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_last_day_in_book_hebrew_month() {
|
||||
for (case, expected) in HEBREW_DATES.iter().zip(EXPECTED_DAY_IN_MONTH.iter()) {
|
||||
let days_in_month = BookHebrew::last_day_of_book_hebrew_month(case.year, case.month);
|
||||
assert_eq!(days_in_month, *expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fixed_from_book_hebrew() {
|
||||
for (case, f_date) in HEBREW_DATES.iter().zip(TEST_FIXED_DATE.iter()) {
|
||||
assert_eq!(
|
||||
BookHebrew::fixed_from_book_hebrew(BookHebrew {
|
||||
year: case.year,
|
||||
month: case.month,
|
||||
day: case.day
|
||||
}),
|
||||
RataDie::new(*f_date),
|
||||
"{case:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_book_hebrew_from_fixed() {
|
||||
for (case, f_date) in HEBREW_DATES.iter().zip(TEST_FIXED_DATE.iter()) {
|
||||
assert_eq!(
|
||||
BookHebrew::book_hebrew_from_fixed(RataDie::new(*f_date)),
|
||||
BookHebrew {
|
||||
year: case.year,
|
||||
month: case.month,
|
||||
day: case.day
|
||||
},
|
||||
"{case:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_civil_to_book_conversion() {
|
||||
for (f_date, case) in TEST_FIXED_DATE.iter().zip(HEBREW_DATES.iter()) {
|
||||
let book_hebrew = BookHebrew::book_hebrew_from_fixed(RataDie::new(*f_date));
|
||||
let (y, m, d) = book_hebrew.to_civil_date();
|
||||
let book_hebrew = BookHebrew::from_civil_date(y, m, d);
|
||||
|
||||
assert_eq!(
|
||||
(case.year, case.month),
|
||||
(book_hebrew.year, book_hebrew.month)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,336 @@
|
|||
// This file is part of ICU4X.
|
||||
//
|
||||
// The contents of this file implement algorithms from Calendrical Calculations
|
||||
// by Reingold & Dershowitz, Cambridge University Press, 4th edition (2018),
|
||||
// which have been released as Lisp code at <https://github.com/EdReingold/calendar-code2/>
|
||||
// under the Apache-2.0 license. Accordingly, this file is released under
|
||||
// the Apache License, Version 2.0 which can be found at the calendrical_calculations
|
||||
// package root or at http://www.apache.org/licenses/LICENSE-2.0.
|
||||
|
||||
use crate::astronomy::Location;
|
||||
use crate::rata_die::{Moment, RataDie};
|
||||
#[allow(unused_imports)]
|
||||
use core_maths::*;
|
||||
|
||||
pub(crate) trait IntegerRoundings {
|
||||
fn div_ceil(self, rhs: Self) -> Self;
|
||||
}
|
||||
|
||||
impl IntegerRoundings for i64 {
|
||||
// copied from std
|
||||
fn div_ceil(self, rhs: Self) -> Self {
|
||||
let d = self / rhs;
|
||||
let r = self % rhs;
|
||||
if (r > 0 && rhs > 0) || (r < 0 && rhs < 0) {
|
||||
d + 1
|
||||
} else {
|
||||
d
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_div_ceil() {
|
||||
assert_eq!(IntegerRoundings::div_ceil(i64::MIN, 1), i64::MIN);
|
||||
assert_eq!(
|
||||
IntegerRoundings::div_ceil(i64::MIN, 2),
|
||||
-4611686018427387904
|
||||
);
|
||||
assert_eq!(
|
||||
IntegerRoundings::div_ceil(i64::MIN, 3),
|
||||
-3074457345618258602
|
||||
);
|
||||
|
||||
assert_eq!(IntegerRoundings::div_ceil(-10, 1), -10);
|
||||
assert_eq!(IntegerRoundings::div_ceil(-10, 2), -5);
|
||||
assert_eq!(IntegerRoundings::div_ceil(-10, 3), -3);
|
||||
|
||||
assert_eq!(IntegerRoundings::div_ceil(-9, 1), -9);
|
||||
assert_eq!(IntegerRoundings::div_ceil(-9, 2), -4);
|
||||
assert_eq!(IntegerRoundings::div_ceil(-9, 3), -3);
|
||||
|
||||
assert_eq!(IntegerRoundings::div_ceil(-8, 1), -8);
|
||||
assert_eq!(IntegerRoundings::div_ceil(-8, 2), -4);
|
||||
assert_eq!(IntegerRoundings::div_ceil(-8, 3), -2);
|
||||
|
||||
assert_eq!(IntegerRoundings::div_ceil(-2, 1), -2);
|
||||
assert_eq!(IntegerRoundings::div_ceil(-2, 2), -1);
|
||||
assert_eq!(IntegerRoundings::div_ceil(-2, 3), 0);
|
||||
|
||||
assert_eq!(IntegerRoundings::div_ceil(-1, 1), -1);
|
||||
assert_eq!(IntegerRoundings::div_ceil(-1, 2), 0);
|
||||
assert_eq!(IntegerRoundings::div_ceil(-1, 3), 0);
|
||||
|
||||
assert_eq!(IntegerRoundings::div_ceil(0, 1), 0);
|
||||
assert_eq!(IntegerRoundings::div_ceil(0, 2), 0);
|
||||
assert_eq!(IntegerRoundings::div_ceil(0, 3), 0);
|
||||
|
||||
assert_eq!(IntegerRoundings::div_ceil(1, 1), 1);
|
||||
assert_eq!(IntegerRoundings::div_ceil(1, 2), 1);
|
||||
assert_eq!(IntegerRoundings::div_ceil(1, 3), 1);
|
||||
|
||||
assert_eq!(IntegerRoundings::div_ceil(2, 1), 2);
|
||||
assert_eq!(IntegerRoundings::div_ceil(2, 2), 1);
|
||||
assert_eq!(IntegerRoundings::div_ceil(2, 3), 1);
|
||||
|
||||
assert_eq!(IntegerRoundings::div_ceil(8, 1), 8);
|
||||
assert_eq!(IntegerRoundings::div_ceil(8, 2), 4);
|
||||
assert_eq!(IntegerRoundings::div_ceil(8, 3), 3);
|
||||
|
||||
assert_eq!(IntegerRoundings::div_ceil(9, 1), 9);
|
||||
assert_eq!(IntegerRoundings::div_ceil(9, 2), 5);
|
||||
assert_eq!(IntegerRoundings::div_ceil(9, 3), 3);
|
||||
|
||||
assert_eq!(IntegerRoundings::div_ceil(10, 1), 10);
|
||||
assert_eq!(IntegerRoundings::div_ceil(10, 2), 5);
|
||||
assert_eq!(IntegerRoundings::div_ceil(10, 3), 4);
|
||||
|
||||
assert_eq!(IntegerRoundings::div_ceil(i64::MAX, 1), 9223372036854775807);
|
||||
assert_eq!(IntegerRoundings::div_ceil(i64::MAX, 2), 4611686018427387904);
|
||||
assert_eq!(IntegerRoundings::div_ceil(i64::MAX, 3), 3074457345618258603);
|
||||
|
||||
for n in -100..100 {
|
||||
for d in 1..5 {
|
||||
let x1 = IntegerRoundings::div_ceil(n, d);
|
||||
let x2 = (n as f64 / d as f64).ceil();
|
||||
assert_eq!(x1, x2 as i64);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Highest power is *last*
|
||||
pub(crate) fn poly(x: f64, coeffs: &[f64]) -> f64 {
|
||||
coeffs.iter().rev().fold(0.0, |a, c| a * x + c)
|
||||
}
|
||||
|
||||
// A generic function that finds a value within an interval
|
||||
// where a certain condition is satisfied.
|
||||
pub(crate) fn binary_search(
|
||||
mut l: f64,
|
||||
mut h: f64,
|
||||
test: impl Fn(f64) -> bool,
|
||||
epsilon: f64,
|
||||
) -> f64 {
|
||||
debug_assert!(l < h);
|
||||
|
||||
loop {
|
||||
let mid = l + (h - l) / 2.0;
|
||||
|
||||
// Determine which direction to go. `test` returns true if we need to go left.
|
||||
(l, h) = if test(mid) { (l, mid) } else { (mid, h) };
|
||||
|
||||
// When the interval width reaches `epsilon`, return the current midpoint.
|
||||
if (h - l) < epsilon {
|
||||
return mid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn next_moment<F>(mut index: Moment, location: Location, condition: F) -> RataDie
|
||||
where
|
||||
F: Fn(Moment, Location) -> bool,
|
||||
{
|
||||
loop {
|
||||
if condition(index, location) {
|
||||
return index.as_rata_die();
|
||||
}
|
||||
index += 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn next<F>(mut index: RataDie, condition: F) -> RataDie
|
||||
where
|
||||
F: Fn(RataDie) -> bool,
|
||||
{
|
||||
loop {
|
||||
if condition(index) {
|
||||
return index;
|
||||
}
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn next_u8<F>(mut index: u8, condition: F) -> u8
|
||||
where
|
||||
F: Fn(u8) -> bool,
|
||||
{
|
||||
loop {
|
||||
if condition(index) {
|
||||
return index;
|
||||
}
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// "Final" is a reserved keyword in rust, which explains the naming convention here.
|
||||
pub(crate) fn final_func<F>(mut index: i32, condition: F) -> i32
|
||||
where
|
||||
F: Fn(i32) -> bool,
|
||||
{
|
||||
while condition(index) {
|
||||
index += 1;
|
||||
}
|
||||
index - 1
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_binary_search() {
|
||||
struct TestCase {
|
||||
test_fn: fn(f64) -> bool,
|
||||
range: (f64, f64),
|
||||
expected: f64,
|
||||
}
|
||||
|
||||
let test_cases = [
|
||||
TestCase {
|
||||
test_fn: |x: f64| x >= 4.0,
|
||||
range: (0.0, 10.0),
|
||||
expected: 4.0,
|
||||
},
|
||||
TestCase {
|
||||
test_fn: |x: f64| x * x >= 2.0,
|
||||
range: (0.0, 2.0),
|
||||
expected: 2.0f64.sqrt(),
|
||||
},
|
||||
TestCase {
|
||||
test_fn: |x: f64| x >= -4.0,
|
||||
range: (-10.0, 0.0),
|
||||
expected: -4.0,
|
||||
},
|
||||
TestCase {
|
||||
test_fn: |x: f64| x >= 0.0,
|
||||
range: (0.0, 10.0),
|
||||
expected: 0.0,
|
||||
},
|
||||
TestCase {
|
||||
test_fn: |x: f64| x > 10.0,
|
||||
range: (0.0, 10.0),
|
||||
expected: 10.0,
|
||||
},
|
||||
];
|
||||
|
||||
for case in test_cases {
|
||||
let result = binary_search(case.range.0, case.range.1, case.test_fn, 1e-4);
|
||||
assert!((result - case.expected).abs() < 0.0001);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn invert_angular<F: Fn(f64) -> f64>(f: F, y: f64, r: (f64, f64)) -> f64 {
|
||||
binary_search(r.0, r.1, |x| (f(x) - y).rem_euclid(360.0) < 180.0, 1e-5)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invert_angular() {
|
||||
struct TestCase {
|
||||
f: fn(f64) -> f64,
|
||||
y: f64,
|
||||
r: (f64, f64),
|
||||
expected: f64,
|
||||
}
|
||||
|
||||
fn f1(x: f64) -> f64 {
|
||||
(2.0 * x).rem_euclid(360.0)
|
||||
}
|
||||
|
||||
fn f2(x: f64) -> f64 {
|
||||
(3.0 * x).rem_euclid(360.0)
|
||||
}
|
||||
|
||||
fn f3(x: f64) -> f64 {
|
||||
(x).rem_euclid(360.0)
|
||||
}
|
||||
// tolerance for comparing floating points.
|
||||
let tolerance = 1e-5;
|
||||
|
||||
let test_cases = [
|
||||
TestCase {
|
||||
f: f1,
|
||||
y: 4.0,
|
||||
r: (0.0, 10.0),
|
||||
expected: 4.0,
|
||||
},
|
||||
TestCase {
|
||||
f: f2,
|
||||
y: 6.0,
|
||||
r: (0.0, 20.0),
|
||||
expected: 6.0,
|
||||
},
|
||||
TestCase {
|
||||
f: f3,
|
||||
y: 400.0,
|
||||
r: (0.0, 10.0),
|
||||
expected: 10.0,
|
||||
},
|
||||
TestCase {
|
||||
f: f3,
|
||||
y: 0.0,
|
||||
r: (0.0, 10.0),
|
||||
expected: 0.0,
|
||||
},
|
||||
TestCase {
|
||||
f: f3,
|
||||
y: 10.0,
|
||||
r: (0.0, 10.0),
|
||||
expected: 10.0,
|
||||
},
|
||||
];
|
||||
|
||||
for case in test_cases {
|
||||
let x = invert_angular(case.f, case.y, case.r);
|
||||
assert!((((case.f)(x)).rem_euclid(360.0) - case.expected).abs() < tolerance);
|
||||
}
|
||||
}
|
||||
|
||||
/// Error returned when casting from an i32
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[allow(clippy::exhaustive_enums)] // enum is specific to function and has a closed set of possible values
|
||||
pub enum I32CastError {
|
||||
/// Less than i32::MIN
|
||||
BelowMin,
|
||||
/// Greater than i32::MAX
|
||||
AboveMax,
|
||||
}
|
||||
|
||||
impl I32CastError {
|
||||
pub(crate) const fn saturate(self) -> i32 {
|
||||
match self {
|
||||
I32CastError::BelowMin => i32::MIN,
|
||||
I32CastError::AboveMax => i32::MAX,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert an i64 to i32 and with information on which way it was out of bounds if so
|
||||
#[inline]
|
||||
pub const fn i64_to_i32(input: i64) -> Result<i32, I32CastError> {
|
||||
if input < i32::MIN as i64 {
|
||||
Err(I32CastError::BelowMin)
|
||||
} else if input > i32::MAX as i64 {
|
||||
Err(I32CastError::AboveMax)
|
||||
} else {
|
||||
Ok(input as i32)
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert an i64 to i32 but saturate at th ebounds
|
||||
#[inline]
|
||||
pub fn i64_to_saturated_i32(input: i64) -> i32 {
|
||||
i64_to_i32(input).unwrap_or_else(|i| i.saturate())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_i64_to_saturated_i32() {
|
||||
assert_eq!(i64_to_saturated_i32(i64::MIN), i32::MIN);
|
||||
assert_eq!(i64_to_saturated_i32(-2147483649), -2147483648);
|
||||
assert_eq!(i64_to_saturated_i32(-2147483648), -2147483648);
|
||||
assert_eq!(i64_to_saturated_i32(-2147483647), -2147483647);
|
||||
assert_eq!(i64_to_saturated_i32(-2147483646), -2147483646);
|
||||
assert_eq!(i64_to_saturated_i32(-100), -100);
|
||||
assert_eq!(i64_to_saturated_i32(0), 0);
|
||||
assert_eq!(i64_to_saturated_i32(100), 100);
|
||||
assert_eq!(i64_to_saturated_i32(2147483646), 2147483646);
|
||||
assert_eq!(i64_to_saturated_i32(2147483647), 2147483647);
|
||||
assert_eq!(i64_to_saturated_i32(2147483648), 2147483647);
|
||||
assert_eq!(i64_to_saturated_i32(2147483649), 2147483647);
|
||||
assert_eq!(i64_to_saturated_i32(i64::MAX), i32::MAX);
|
||||
}
|
|
@ -0,0 +1,268 @@
|
|||
use crate::astronomy::*;
|
||||
use crate::helpers::{i64_to_saturated_i32, next};
|
||||
use crate::rata_die::{Moment, RataDie};
|
||||
#[allow(unused_imports)]
|
||||
use core_maths::*;
|
||||
|
||||
// Different islamic calendars use different epochs (Thursday vs Friday) due to disagreement on the exact date of Mohammed's migration to Mecca.
|
||||
/// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L2066>
|
||||
const FIXED_ISLAMIC_EPOCH_FRIDAY: RataDie = crate::julian::fixed_from_julian(622, 7, 16);
|
||||
const FIXED_ISLAMIC_EPOCH_THURSDAY: RataDie = crate::julian::fixed_from_julian(622, 7, 15);
|
||||
|
||||
/// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L6898>
|
||||
const CAIRO: Location = Location {
|
||||
latitude: 30.1,
|
||||
longitude: 31.3,
|
||||
elevation: 200.0,
|
||||
zone: (1_f64 / 12_f64),
|
||||
};
|
||||
|
||||
/// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L6904>
|
||||
pub fn fixed_from_islamic_observational(year: i32, month: u8, day: u8) -> RataDie {
|
||||
let year = i64::from(year);
|
||||
let month = i64::from(month);
|
||||
let day = i64::from(day);
|
||||
let midmonth = FIXED_ISLAMIC_EPOCH_FRIDAY.to_f64_date()
|
||||
+ (((year - 1) as f64) * 12.0 + month as f64 - 0.5) * MEAN_SYNODIC_MONTH;
|
||||
let lunar_phase =
|
||||
Astronomical::calculate_lunar_phase_at_or_before(RataDie::new(midmonth as i64));
|
||||
Astronomical::phasis_on_or_before(RataDie::new(midmonth as i64), CAIRO, Some(lunar_phase)) + day
|
||||
- 1
|
||||
}
|
||||
|
||||
/// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/1ee51ecfaae6f856b0d7de3e36e9042100b4f424/calendar.l#L6983-L6995>
|
||||
pub fn observational_islamic_from_fixed(date: RataDie) -> (i32, u8, u8) {
|
||||
let lunar_phase = Astronomical::calculate_lunar_phase_at_or_before(date);
|
||||
let crescent = Astronomical::phasis_on_or_before(date, CAIRO, Some(lunar_phase));
|
||||
let elapsed_months =
|
||||
((crescent - FIXED_ISLAMIC_EPOCH_FRIDAY) as f64 / MEAN_SYNODIC_MONTH).round() as i32;
|
||||
let year = elapsed_months.div_euclid(12) + 1;
|
||||
let month = elapsed_months.rem_euclid(12) + 1;
|
||||
let day = (date - crescent + 1) as u8;
|
||||
|
||||
(year, month as u8, day)
|
||||
}
|
||||
|
||||
// Saudi visibility criterion on eve of fixed date in Mecca.
|
||||
// The start of the new month only happens if both of these criterias are met: The moon is a waxing crescent at sunset of the previous day
|
||||
// and the moon sets after the sun on that same evening.
|
||||
/// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L6957>
|
||||
fn saudi_criterion(date: RataDie) -> Option<bool> {
|
||||
let sunset = Astronomical::sunset((date - 1).as_moment(), MECCA)?;
|
||||
let tee = Location::universal_from_standard(sunset, MECCA);
|
||||
let phase = Astronomical::lunar_phase(tee, Astronomical::julian_centuries(tee));
|
||||
let moonlag = Astronomical::moonlag((date - 1).as_moment(), MECCA)?;
|
||||
|
||||
Some(phase > 0.0 && phase < 90.0 && moonlag > 0.0)
|
||||
}
|
||||
|
||||
pub(crate) fn adjusted_saudi_criterion(date: RataDie) -> bool {
|
||||
if let Some(x) = saudi_criterion(date) {
|
||||
x
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// Closest fixed date on or before date when Saudi visibility criterion is held.
|
||||
/// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L6966>
|
||||
pub fn saudi_new_month_on_or_before(date: RataDie) -> RataDie {
|
||||
let last_new_moon = (Astronomical::lunar_phase_at_or_before(0.0, date.as_moment()))
|
||||
.inner()
|
||||
.floor(); // Gets the R.D Date of the prior new moon
|
||||
let age = date.to_f64_date() - last_new_moon;
|
||||
// Explanation of why the value 3.0 is chosen: https://github.com/unicode-org/icu4x/pull/3673/files#r1267460916
|
||||
let tau = if age <= 3.0 && !adjusted_saudi_criterion(date) {
|
||||
// Checks if the criterion is not yet visible on the evening of date
|
||||
last_new_moon - 30.0 // Goes back a month
|
||||
} else {
|
||||
last_new_moon
|
||||
};
|
||||
|
||||
next(RataDie::new(tau as i64), adjusted_saudi_criterion) // Loop that increments the day and checks if the criterion is now visible
|
||||
}
|
||||
|
||||
/// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L6996>
|
||||
pub fn saudi_islamic_from_fixed(date: RataDie) -> (i32, u8, u8) {
|
||||
let crescent = saudi_new_month_on_or_before(date);
|
||||
let elapsed_months =
|
||||
((crescent - FIXED_ISLAMIC_EPOCH_FRIDAY) as f64 / MEAN_SYNODIC_MONTH).round() as i64;
|
||||
let year = i64_to_saturated_i32(elapsed_months.div_euclid(12) + 1);
|
||||
let month = (elapsed_months.rem_euclid(12) + 1) as u8;
|
||||
let day = ((date - crescent) + 1) as u8;
|
||||
|
||||
(year, month, day)
|
||||
}
|
||||
|
||||
/// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L6981>
|
||||
pub fn fixed_from_saudi_islamic(year: i32, month: u8, day: u8) -> RataDie {
|
||||
let midmonth = RataDie::new(
|
||||
FIXED_ISLAMIC_EPOCH_FRIDAY.to_i64_date()
|
||||
+ (((year as f64 - 1.0) * 12.0 + month as f64 - 0.5) * MEAN_SYNODIC_MONTH).floor()
|
||||
as i64,
|
||||
);
|
||||
let first_day_of_month = saudi_new_month_on_or_before(midmonth).to_i64_date();
|
||||
|
||||
RataDie::new(first_day_of_month + day as i64 - 1)
|
||||
}
|
||||
|
||||
/// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L2076>
|
||||
pub fn fixed_from_islamic_civil(year: i32, month: u8, day: u8) -> RataDie {
|
||||
let year = i64::from(year);
|
||||
let month = i64::from(month);
|
||||
let day = i64::from(day);
|
||||
|
||||
RataDie::new(
|
||||
(FIXED_ISLAMIC_EPOCH_FRIDAY.to_i64_date() - 1)
|
||||
+ (year - 1) * 354
|
||||
+ (3 + year * 11).div_euclid(30)
|
||||
+ 29 * (month - 1)
|
||||
+ month.div_euclid(2)
|
||||
+ day,
|
||||
)
|
||||
}
|
||||
/// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L2090>
|
||||
pub fn islamic_civil_from_fixed(date: RataDie) -> (i32, u8, u8) {
|
||||
let year =
|
||||
i64_to_saturated_i32(((date - FIXED_ISLAMIC_EPOCH_FRIDAY) * 30 + 10646).div_euclid(10631));
|
||||
let prior_days = date.to_f64_date() - fixed_from_islamic_civil(year, 1, 1).to_f64_date();
|
||||
debug_assert!(prior_days >= 0.0);
|
||||
debug_assert!(prior_days <= 354.);
|
||||
let month = (((prior_days * 11.0) + 330.0) / 325.0) as u8; // Prior days is maximum 354 (when year length is 355), making the value always less than 12
|
||||
debug_assert!(month <= 12);
|
||||
let day =
|
||||
(date.to_f64_date() - fixed_from_islamic_civil(year, month, 1).to_f64_date() + 1.0) as u8; // The value will always be number between 1-30 because of the difference between the date and lunar ordinals function.
|
||||
|
||||
(year, month, day)
|
||||
}
|
||||
|
||||
/// Lisp code reference:https: //github.com/EdReingold/calendar-code2/blob/main/calendar.l#L2076
|
||||
pub fn fixed_from_islamic_tabular(year: i32, month: u8, day: u8) -> RataDie {
|
||||
let year = i64::from(year);
|
||||
let month = i64::from(month);
|
||||
let day = i64::from(day);
|
||||
RataDie::new(
|
||||
(FIXED_ISLAMIC_EPOCH_THURSDAY.to_i64_date() - 1)
|
||||
+ (year - 1) * 354
|
||||
+ (3 + year * 11).div_euclid(30)
|
||||
+ 29 * (month - 1)
|
||||
+ month.div_euclid(2)
|
||||
+ day,
|
||||
)
|
||||
}
|
||||
/// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L2090>
|
||||
pub fn islamic_tabular_from_fixed(date: RataDie) -> (i32, u8, u8) {
|
||||
let year = i64_to_saturated_i32(
|
||||
((date - FIXED_ISLAMIC_EPOCH_THURSDAY) * 30 + 10646).div_euclid(10631),
|
||||
);
|
||||
let prior_days = date.to_f64_date() - fixed_from_islamic_tabular(year, 1, 1).to_f64_date();
|
||||
debug_assert!(prior_days >= 0.0);
|
||||
debug_assert!(prior_days <= 354.);
|
||||
let month = (((prior_days * 11.0) + 330.0) / 325.0) as u8; // Prior days is maximum 354 (when year length is 355), making the value always less than 12
|
||||
debug_assert!(month <= 12);
|
||||
let day =
|
||||
(date.to_f64_date() - fixed_from_islamic_tabular(year, month, 1).to_f64_date() + 1.0) as u8; // The value will always be number between 1-30 because of the difference between the date and lunar ordinals function.
|
||||
|
||||
(year, month, day)
|
||||
}
|
||||
|
||||
/// The number of days in a month for the observational islamic calendar
|
||||
pub fn observational_islamic_month_days(year: i32, month: u8) -> u8 {
|
||||
let midmonth = FIXED_ISLAMIC_EPOCH_FRIDAY.to_f64_date()
|
||||
+ (((year - 1) as f64) * 12.0 + month as f64 - 0.5) * MEAN_SYNODIC_MONTH;
|
||||
|
||||
let lunar_phase: f64 =
|
||||
Astronomical::calculate_lunar_phase_at_or_before(RataDie::new(midmonth as i64));
|
||||
let f_date =
|
||||
Astronomical::phasis_on_or_before(RataDie::new(midmonth as i64), CAIRO, Some(lunar_phase));
|
||||
|
||||
Astronomical::month_length(f_date, CAIRO)
|
||||
}
|
||||
|
||||
/// The number of days in a month for the Saudi (Umm Al-Qura) calendar
|
||||
pub fn saudi_islamic_month_days(year: i32, month: u8) -> u8 {
|
||||
// We cannot use month_days from the book here, that is for the observational calendar
|
||||
//
|
||||
// Instead we subtract the two new months calculated using the saudi criterion
|
||||
let midmonth = Moment::new(
|
||||
FIXED_ISLAMIC_EPOCH_FRIDAY.to_f64_date()
|
||||
+ (((year - 1) as f64) * 12.0 + month as f64 - 0.5) * MEAN_SYNODIC_MONTH,
|
||||
);
|
||||
let midmonth_next = midmonth + MEAN_SYNODIC_MONTH;
|
||||
|
||||
let month_start = saudi_new_month_on_or_before(midmonth.as_rata_die());
|
||||
let next_month_start = saudi_new_month_on_or_before(midmonth_next.as_rata_die());
|
||||
|
||||
let diff = next_month_start - month_start;
|
||||
debug_assert!(
|
||||
diff <= 30,
|
||||
"umm-al-qura months must not be more than 30 days"
|
||||
);
|
||||
u8::try_from(diff).unwrap_or(30)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
static TEST_FIXED_DATE: [i64; 33] = [
|
||||
-214193, -61387, 25469, 49217, 171307, 210155, 253427, 369740, 400085, 434355, 452605,
|
||||
470160, 473837, 507850, 524156, 544676, 567118, 569477, 601716, 613424, 626596, 645554,
|
||||
664224, 671401, 694799, 704424, 708842, 709409, 709580, 727274, 728714, 744313, 764652,
|
||||
];
|
||||
// Removed: 601716 and 727274 fixed dates
|
||||
static TEST_FIXED_DATE_UMMALQURA: [i64; 31] = [
|
||||
-214193, -61387, 25469, 49217, 171307, 210155, 253427, 369740, 400085, 434355, 452605,
|
||||
470160, 473837, 507850, 524156, 544676, 567118, 569477, 613424, 626596, 645554, 664224,
|
||||
671401, 694799, 704424, 708842, 709409, 709580, 728714, 744313, 764652,
|
||||
];
|
||||
// Values from lisp code
|
||||
static SAUDI_CRITERION_EXPECTED: [bool; 33] = [
|
||||
false, false, true, false, false, true, false, true, false, false, true, false, false,
|
||||
true, true, true, true, false, false, true, true, true, false, false, false, false, false,
|
||||
false, true, false, true, false, true,
|
||||
];
|
||||
// Values from lisp code, removed two expected months.
|
||||
static SAUDI_NEW_MONTH_OR_BEFORE_EXPECTED: [f64; 31] = [
|
||||
-214203.0, -61412.0, 25467.0, 49210.0, 171290.0, 210152.0, 253414.0, 369735.0, 400063.0,
|
||||
434348.0, 452598.0, 470139.0, 473830.0, 507850.0, 524150.0, 544674.0, 567118.0, 569450.0,
|
||||
613421.0, 626592.0, 645551.0, 664214.0, 671391.0, 694779.0, 704405.0, 708835.0, 709396.0,
|
||||
709573.0, 728709.0, 744301.0, 764647.0,
|
||||
];
|
||||
#[test]
|
||||
fn test_islamic_epoch_friday() {
|
||||
let epoch = FIXED_ISLAMIC_EPOCH_FRIDAY.to_i64_date();
|
||||
// Iso year of Islamic Epoch
|
||||
let epoch_year_from_fixed = crate::iso::iso_year_from_fixed(RataDie::new(epoch));
|
||||
// 622 is the correct ISO year for the Islamic Epoch
|
||||
assert_eq!(epoch_year_from_fixed, 622);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_islamic_epoch_thursday() {
|
||||
let epoch = FIXED_ISLAMIC_EPOCH_THURSDAY.to_i64_date();
|
||||
// Iso year of Islamic Epoch
|
||||
let epoch_year_from_fixed = crate::iso::iso_year_from_fixed(RataDie::new(epoch));
|
||||
// 622 is the correct ISO year for the Islamic Epoch
|
||||
assert_eq!(epoch_year_from_fixed, 622);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_saudi_criterion() {
|
||||
for (boolean, f_date) in SAUDI_CRITERION_EXPECTED.iter().zip(TEST_FIXED_DATE.iter()) {
|
||||
let bool_result = saudi_criterion(RataDie::new(*f_date)).unwrap();
|
||||
assert_eq!(*boolean, bool_result, "{f_date:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_saudi_new_month_or_before() {
|
||||
for (date, f_date) in SAUDI_NEW_MONTH_OR_BEFORE_EXPECTED
|
||||
.iter()
|
||||
.zip(TEST_FIXED_DATE_UMMALQURA.iter())
|
||||
{
|
||||
let date_result = saudi_new_month_on_or_before(RataDie::new(*f_date)).to_f64_date();
|
||||
assert_eq!(*date, date_result, "{f_date:?}");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
// This file is part of ICU4X.
|
||||
//
|
||||
// The contents of this file implement algorithms from Calendrical Calculations
|
||||
// by Reingold & Dershowitz, Cambridge University Press, 4th edition (2018),
|
||||
// which have been released as Lisp code at <https://github.com/EdReingold/calendar-code2/>
|
||||
// under the Apache-2.0 license. Accordingly, this file is released under
|
||||
// the Apache License, Version 2.0 which can be found at the calendrical_calculations
|
||||
// package root or at http://www.apache.org/licenses/LICENSE-2.0.
|
||||
|
||||
use crate::helpers::{i64_to_i32, I32CastError};
|
||||
use crate::rata_die::RataDie;
|
||||
|
||||
// The Gregorian epoch is equivalent to first day in fixed day measurement
|
||||
const EPOCH: RataDie = RataDie::new(1);
|
||||
|
||||
/// Whether or not `year` is a leap year
|
||||
pub fn is_leap_year(year: i32) -> bool {
|
||||
year % 4 == 0 && (year % 400 == 0 || year % 100 != 0)
|
||||
}
|
||||
|
||||
// Fixed is day count representation of calendars starting from Jan 1st of year 1.
|
||||
// The fixed calculations algorithms are from the Calendrical Calculations book.
|
||||
//
|
||||
/// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/1ee51ecfaae6f856b0d7de3e36e9042100b4f424/calendar.l#L1167-L1189>
|
||||
pub fn fixed_from_iso(year: i32, month: u8, day: u8) -> RataDie {
|
||||
let prev_year = (year as i64) - 1;
|
||||
// Calculate days per year
|
||||
let mut fixed: i64 = (EPOCH.to_i64_date() - 1) + 365 * prev_year;
|
||||
// Calculate leap year offset
|
||||
let offset = prev_year.div_euclid(4) - prev_year.div_euclid(100) + prev_year.div_euclid(400);
|
||||
// Adjust for leap year logic
|
||||
fixed += offset;
|
||||
// Days of current year
|
||||
fixed += (367 * (month as i64) - 362).div_euclid(12);
|
||||
// Leap year adjustment for the current year
|
||||
fixed += if month <= 2 {
|
||||
0
|
||||
} else if is_leap_year(year) {
|
||||
-1
|
||||
} else {
|
||||
-2
|
||||
};
|
||||
// Days passed in current month
|
||||
fixed += day as i64;
|
||||
RataDie::new(fixed)
|
||||
}
|
||||
|
||||
/// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/1ee51ecfaae6f856b0d7de3e36e9042100b4f424/calendar.l#L1191-L1217>
|
||||
pub(crate) fn iso_year_from_fixed(date: RataDie) -> i64 {
|
||||
// Shouldn't overflow because it's not possbile to construct extreme values of RataDie
|
||||
let date = date - EPOCH;
|
||||
|
||||
// 400 year cycles have 146097 days
|
||||
let (n_400, date) = (date.div_euclid(146097), date.rem_euclid(146097));
|
||||
|
||||
// 100 year cycles have 36524 days
|
||||
let (n_100, date) = (date.div_euclid(36524), date.rem_euclid(36524));
|
||||
|
||||
// 4 year cycles have 1461 days
|
||||
let (n_4, date) = (date.div_euclid(1461), date.rem_euclid(1461));
|
||||
|
||||
let n_1 = date.div_euclid(365);
|
||||
|
||||
let year = 400 * n_400 + 100 * n_100 + 4 * n_4 + n_1;
|
||||
|
||||
if n_100 == 4 || n_1 == 4 {
|
||||
year
|
||||
} else {
|
||||
year + 1
|
||||
}
|
||||
}
|
||||
|
||||
fn iso_new_year(year: i32) -> RataDie {
|
||||
fixed_from_iso(year, 1, 1)
|
||||
}
|
||||
|
||||
/// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/1ee51ecfaae6f856b0d7de3e36e9042100b4f424/calendar.l#L1525-L1540>
|
||||
pub fn iso_from_fixed(date: RataDie) -> Result<(i32, u8, u8), I32CastError> {
|
||||
let year = iso_year_from_fixed(date);
|
||||
let year = i64_to_i32(year)?;
|
||||
// Calculates the prior days of the adjusted year, then applies a correction based on leap year conditions for the correct ISO date conversion.
|
||||
let prior_days = date - iso_new_year(year);
|
||||
let correction = if date < fixed_from_iso(year, 3, 1) {
|
||||
0
|
||||
} else if is_leap_year(year) {
|
||||
1
|
||||
} else {
|
||||
2
|
||||
};
|
||||
let month = (12 * (prior_days + correction) + 373).div_euclid(367) as u8; // in 1..12 < u8::MAX
|
||||
let day = (date - fixed_from_iso(year, month, 1) + 1) as u8; // <= days_in_month < u8::MAX
|
||||
Ok((year, month, day))
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
// This file is part of ICU4X.
|
||||
//
|
||||
// The contents of this file implement algorithms from Calendrical Calculations
|
||||
// by Reingold & Dershowitz, Cambridge University Press, 4th edition (2018),
|
||||
// which have been released as Lisp code at <https://github.com/EdReingold/calendar-code2/>
|
||||
// under the Apache-2.0 license. Accordingly, this file is released under
|
||||
// the Apache License, Version 2.0 which can be found at the calendrical_calculations
|
||||
// package root or at http://www.apache.org/licenses/LICENSE-2.0.
|
||||
|
||||
use crate::helpers::{i64_to_i32, I32CastError};
|
||||
use crate::rata_die::RataDie;
|
||||
|
||||
// Julian epoch is equivalent to fixed_from_iso of December 30th of 0 year
|
||||
// 1st Jan of 1st year Julian is equivalent to December 30th of 0th year of ISO year
|
||||
const JULIAN_EPOCH: RataDie = RataDie::new(-1);
|
||||
|
||||
/// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/1ee51ecfaae6f856b0d7de3e36e9042100b4f424/calendar.l#L1684-L1687>
|
||||
#[inline(always)]
|
||||
pub const fn is_leap_year(year: i32) -> bool {
|
||||
year % 4 == 0
|
||||
}
|
||||
|
||||
/// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/1ee51ecfaae6f856b0d7de3e36e9042100b4f424/calendar.l#L1689-L1709>
|
||||
pub const fn fixed_from_julian(year: i32, month: u8, day: u8) -> RataDie {
|
||||
let mut fixed =
|
||||
JULIAN_EPOCH.to_i64_date() - 1 + 365 * (year as i64 - 1) + (year as i64 - 1).div_euclid(4);
|
||||
debug_assert!(month > 0 && month < 13, "Month should be in range 1..=12.");
|
||||
fixed += match month {
|
||||
1 => 0,
|
||||
2 => 31,
|
||||
3 => 59,
|
||||
4 => 90,
|
||||
5 => 120,
|
||||
6 => 151,
|
||||
7 => 181,
|
||||
8 => 212,
|
||||
9 => 243,
|
||||
10 => 273,
|
||||
11 => 304,
|
||||
12 => 334,
|
||||
_ => -1,
|
||||
};
|
||||
// Only add one if the month is after February (month > 2), since leap days are added to the end of February
|
||||
if month > 2 && is_leap_year(year) {
|
||||
fixed += 1;
|
||||
}
|
||||
RataDie::new(fixed + (day as i64))
|
||||
}
|
||||
|
||||
/// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/1ee51ecfaae6f856b0d7de3e36e9042100b4f424/calendar.l#L1711-L1738>
|
||||
pub fn julian_from_fixed(date: RataDie) -> Result<(i32, u8, u8), I32CastError> {
|
||||
let approx = (4 * date.to_i64_date() + 1464).div_euclid(1461);
|
||||
let year = i64_to_i32(approx)?;
|
||||
let prior_days = date
|
||||
- fixed_from_julian(year, 1, 1)
|
||||
- if is_leap_year(year) && date > fixed_from_julian(year, 2, 28) {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let adjusted_year = if prior_days >= 365 {
|
||||
year.saturating_add(1)
|
||||
} else {
|
||||
year
|
||||
};
|
||||
let adjusted_prior_days = prior_days.rem_euclid(365);
|
||||
debug_assert!((0..365).contains(&adjusted_prior_days));
|
||||
let month = if adjusted_prior_days < 31 {
|
||||
1
|
||||
} else if adjusted_prior_days < 59 {
|
||||
2
|
||||
} else if adjusted_prior_days < 90 {
|
||||
3
|
||||
} else if adjusted_prior_days < 120 {
|
||||
4
|
||||
} else if adjusted_prior_days < 151 {
|
||||
5
|
||||
} else if adjusted_prior_days < 181 {
|
||||
6
|
||||
} else if adjusted_prior_days < 212 {
|
||||
7
|
||||
} else if adjusted_prior_days < 243 {
|
||||
8
|
||||
} else if adjusted_prior_days < 273 {
|
||||
9
|
||||
} else if adjusted_prior_days < 304 {
|
||||
10
|
||||
} else if adjusted_prior_days < 334 {
|
||||
11
|
||||
} else {
|
||||
12
|
||||
};
|
||||
let day = (date - fixed_from_julian(adjusted_year, month, 1) + 1) as u8; // as days_in_month is < u8::MAX
|
||||
debug_assert!(day <= 31, "Day assertion failed; date: {date:?}, adjusted_year: {adjusted_year}, prior_days: {prior_days}, month: {month}, day: {day}");
|
||||
|
||||
Ok((adjusted_year, month, day))
|
||||
}
|
||||
|
||||
/// Get a fixed date from the ymd of a Julian date; years are counted as in _Calendrical Calculations_ by Reingold & Dershowitz,
|
||||
/// meaning there is no year 0. For instance, near the epoch date, years are counted: -3, -2, -1, 1, 2, 3 instead of -2, -1, 0, 1, 2, 3.
|
||||
///
|
||||
/// Primarily useful for use with code constructing epochs specified in the bookg
|
||||
pub const fn fixed_from_julian_book_version(book_year: i32, month: u8, day: u8) -> RataDie {
|
||||
debug_assert!(book_year != 0);
|
||||
// TODO: Should we check the bounds here?
|
||||
fixed_from_julian(
|
||||
if book_year < 0 {
|
||||
book_year + 1
|
||||
} else {
|
||||
book_year
|
||||
},
|
||||
month,
|
||||
day,
|
||||
)
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
// This file is part of ICU4X.
|
||||
//
|
||||
// The contents of this file implement algorithms from Calendrical Calculations
|
||||
// by Reingold & Dershowitz, Cambridge University Press, 4th edition (2018),
|
||||
// which have been released as Lisp code at <https://github.com/EdReingold/calendar-code2/>
|
||||
// under the Apache-2.0 license. Accordingly, this file is released under
|
||||
// the Apache License, Version 2.0 which can be found at the calendrical_calculations
|
||||
// package root or at http://www.apache.org/licenses/LICENSE-2.0.
|
||||
|
||||
//! Calendrical calculations
|
||||
//!
|
||||
//! This crate implements algorithms from
|
||||
//! Calendrical Calculations by Reingold & Dershowitz, Cambridge University Press, 4th edition (2018)
|
||||
//! as needed by [ICU4X](https://github.com/unicode-org/icu4x).
|
||||
//!
|
||||
//! Most of these algorithms can be found as lisp code in the book or
|
||||
//! [on GithHub](https://github.com/EdReingold/calendar-code2/blob/main/calendar.l).
|
||||
//!
|
||||
//! The primary purpose of this crate is use by ICU4X, however if non-ICU4X users need this we are happy
|
||||
//! to add more structure to this crate as needed.
|
||||
// https://github.com/unicode-org/icu4x/blob/main/docs/process/boilerplate.md#library-annotations
|
||||
#![cfg_attr(not(any(test, feature = "std")), no_std)]
|
||||
#![cfg_attr(
|
||||
not(test),
|
||||
deny(
|
||||
clippy::indexing_slicing,
|
||||
clippy::unwrap_used,
|
||||
clippy::expect_used,
|
||||
clippy::panic,
|
||||
clippy::exhaustive_structs,
|
||||
clippy::exhaustive_enums,
|
||||
missing_debug_implementations,
|
||||
)
|
||||
)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
mod astronomy;
|
||||
/// Chinese-like lunar calendars (Chinese, Dangi)
|
||||
pub mod chinese_based;
|
||||
/// The Coptic calendar
|
||||
pub mod coptic;
|
||||
/// Error handling
|
||||
mod error;
|
||||
/// The ethiopian calendar
|
||||
pub mod ethiopian;
|
||||
/// The Hebrew calendar
|
||||
pub mod hebrew;
|
||||
/// Additional math helpers
|
||||
pub mod helpers;
|
||||
/// Various islamic lunar calendars
|
||||
pub mod islamic;
|
||||
/// The ISO calendar (also usable as Gregorian)
|
||||
pub mod iso;
|
||||
/// The Julian calendar
|
||||
pub mod julian;
|
||||
/// The persian calendar
|
||||
pub mod persian;
|
||||
/// Representation of Rata Die (R.D., also called J.D. for Julain Date)
|
||||
/// dates, which are represented as the number of days since ISO date 0001-01-01.
|
||||
pub mod rata_die;
|
|
@ -0,0 +1,120 @@
|
|||
// This file is part of ICU4X.
|
||||
//
|
||||
// The contents of this file implement algorithms from Calendrical Calculations
|
||||
// by Reingold & Dershowitz, Cambridge University Press, 4th edition (2018),
|
||||
// which have been released as Lisp code at <https://github.com/EdReingold/calendar-code2/>
|
||||
// under the Apache-2.0 license. Accordingly, this file is released under
|
||||
// the Apache License, Version 2.0 which can be found at the calendrical_calculations
|
||||
// package root or at http://www.apache.org/licenses/LICENSE-2.0.
|
||||
|
||||
use crate::helpers::{i64_to_i32, I32CastError, IntegerRoundings};
|
||||
use crate::rata_die::RataDie;
|
||||
/// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L4720>
|
||||
// Book states that the Persian epoch is the date: 3/19/622 and since the Persian Calendar has no year 0, the best choice was to use the Julian function.
|
||||
const FIXED_PERSIAN_EPOCH: RataDie = crate::julian::fixed_from_julian(622, 3, 19);
|
||||
|
||||
/// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L4803>
|
||||
pub fn fixed_from_arithmetic_persian(year: i32, month: u8, day: u8) -> RataDie {
|
||||
let p_year = i64::from(year);
|
||||
let month = i64::from(month);
|
||||
let day = i64::from(day);
|
||||
let y = if p_year > 0 {
|
||||
p_year - 474
|
||||
} else {
|
||||
p_year - 473
|
||||
};
|
||||
let year = y.rem_euclid(2820) + 474;
|
||||
|
||||
RataDie::new(
|
||||
FIXED_PERSIAN_EPOCH.to_i64_date() - 1
|
||||
+ 1029983 * y.div_euclid(2820)
|
||||
+ 365 * (year - 1)
|
||||
+ (31 * year - 5).div_euclid(128)
|
||||
+ if month <= 7 {
|
||||
31 * (month - 1)
|
||||
} else {
|
||||
30 * (month - 1) + 6
|
||||
}
|
||||
+ day,
|
||||
)
|
||||
}
|
||||
|
||||
/// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L4857>
|
||||
pub fn arithmetic_persian_from_fixed(date: RataDie) -> Result<(i32, u8, u8), I32CastError> {
|
||||
let year = arithmetic_persian_year_from_fixed(date);
|
||||
let year = i64_to_i32(year)?;
|
||||
#[allow(clippy::unwrap_used)] // valid month,day
|
||||
let day_of_year = 1_i64 + (date - fixed_from_arithmetic_persian(year, 1, 1));
|
||||
#[allow(unstable_name_collisions)] // div_ceil is unstable and polyfilled
|
||||
let month = if day_of_year <= 186 {
|
||||
day_of_year.div_ceil(31) as u8
|
||||
} else {
|
||||
(day_of_year - 6).div_ceil(30) as u8
|
||||
};
|
||||
let day = (date - fixed_from_arithmetic_persian(year, month, 1) + 1) as u8;
|
||||
Ok((year, month, day))
|
||||
}
|
||||
|
||||
/// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L4829>
|
||||
fn arithmetic_persian_year_from_fixed(date: RataDie) -> i64 {
|
||||
let d0 = date - fixed_from_arithmetic_persian(475, 1, 1);
|
||||
let n2820 = d0.div_euclid(1029983);
|
||||
let d1 = d0.rem_euclid(1029983);
|
||||
let y2820 = if d1 == 1029982 {
|
||||
2820
|
||||
} else {
|
||||
(128 * d1 + 46878).div_euclid(46751)
|
||||
};
|
||||
let year = 474 + n2820 * 2820 + y2820;
|
||||
if year > 0 {
|
||||
year
|
||||
} else {
|
||||
year - 1
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn test_persian_epoch() {
|
||||
let epoch = FIXED_PERSIAN_EPOCH.to_i64_date();
|
||||
// Iso year of Persian Epoch
|
||||
let epoch_year_from_fixed = crate::iso::iso_year_from_fixed(RataDie::new(epoch));
|
||||
// 622 is the correct ISO year for the Persian Epoch
|
||||
assert_eq!(epoch_year_from_fixed, 622);
|
||||
}
|
||||
|
||||
// Persian New Year occurring in March of Gregorian year (g_year) to fixed date
|
||||
fn nowruz(g_year: i32) -> RataDie {
|
||||
let (y, _m, _d) = crate::iso::iso_from_fixed(FIXED_PERSIAN_EPOCH).unwrap();
|
||||
let persian_year = g_year - y + 1;
|
||||
let year = if persian_year <= 0 {
|
||||
persian_year - 1
|
||||
} else {
|
||||
persian_year
|
||||
};
|
||||
fixed_from_arithmetic_persian(year, 1, 1)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nowruz() {
|
||||
let fixed_date = nowruz(622).to_i64_date();
|
||||
assert_eq!(fixed_date, FIXED_PERSIAN_EPOCH.to_i64_date());
|
||||
// These values are used as test data in appendix C of the "Calendrical Calculations" book
|
||||
let nowruz_test_year_start = 2000;
|
||||
let nowruz_test_year_end = 2103;
|
||||
|
||||
for year in nowruz_test_year_start..=nowruz_test_year_end {
|
||||
let two_thousand_eight_to_fixed = nowruz(year).to_i64_date();
|
||||
let iso_date = crate::iso::fixed_from_iso(year, 3, 21);
|
||||
let (persian_year, _m, _d) = arithmetic_persian_from_fixed(iso_date).unwrap();
|
||||
assert_eq!(
|
||||
arithmetic_persian_from_fixed(RataDie::new(two_thousand_eight_to_fixed))
|
||||
.unwrap()
|
||||
.0,
|
||||
persian_year
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,191 @@
|
|||
// This file is part of ICU4X.
|
||||
//
|
||||
// The contents of this file implement algorithms from Calendrical Calculations
|
||||
// by Reingold & Dershowitz, Cambridge University Press, 4th edition (2018),
|
||||
// which have been released as Lisp code at <https://github.com/EdReingold/calendar-code2/>
|
||||
// under the Apache-2.0 license. Accordingly, this file is released under
|
||||
// the Apache License, Version 2.0 which can be found at the calendrical_calculations
|
||||
// package root or at http://www.apache.org/licenses/LICENSE-2.0.
|
||||
|
||||
use core::ops::{Add, AddAssign, Sub, SubAssign};
|
||||
#[allow(unused_imports)]
|
||||
use core_maths::*;
|
||||
|
||||
/// The *Rata Die*, or *R.D.*, or `fixed_date`: number of days since January 1, 1 CE.
|
||||
///
|
||||
/// See: <https://en.wikipedia.org/wiki/Rata_Die>
|
||||
///
|
||||
/// It is a logic error to construct a RataDie
|
||||
/// except from a date that is in range of one of the official calendars.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct RataDie(i64);
|
||||
|
||||
impl RataDie {
|
||||
/// Create a RataDie
|
||||
pub const fn new(fixed_date: i64) -> Self {
|
||||
let result = Self(fixed_date);
|
||||
#[cfg(debug_assertions)]
|
||||
result.check();
|
||||
result
|
||||
}
|
||||
|
||||
/// Check that it is in range
|
||||
#[cfg(debug_assertions)]
|
||||
pub const fn check(&self) {
|
||||
if self.0 > i64::MAX / 256 {
|
||||
debug_assert!(
|
||||
false,
|
||||
"RataDie is not designed to store values near to the overflow boundary"
|
||||
);
|
||||
}
|
||||
if self.0 < i64::MIN / 256 {
|
||||
debug_assert!(
|
||||
false,
|
||||
"RataDie is not designed to store values near to the overflow boundary"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A valid RataDie that is intended to be below all dates representable in calendars
|
||||
///
|
||||
/// For testing only
|
||||
#[doc(hidden)]
|
||||
pub const fn big_negative() -> Self {
|
||||
Self::new(i64::MIN / 256 / 256)
|
||||
}
|
||||
|
||||
/// Convert this to an i64 value representing the RataDie
|
||||
pub const fn to_i64_date(self) -> i64 {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Convert this to an f64 value representing the RataDie
|
||||
pub const fn to_f64_date(self) -> f64 {
|
||||
self.0 as f64
|
||||
}
|
||||
|
||||
/// Calculate the number of days between two RataDie in a const-friendly way
|
||||
pub const fn const_diff(self, rhs: Self) -> i64 {
|
||||
self.0 - rhs.0
|
||||
}
|
||||
|
||||
/// Convert this to a [`Moment`]
|
||||
pub const fn as_moment(&self) -> Moment {
|
||||
Moment::new(self.0 as f64)
|
||||
}
|
||||
}
|
||||
|
||||
/// Shift a RataDie N days into the future
|
||||
impl Add<i64> for RataDie {
|
||||
type Output = Self;
|
||||
fn add(self, rhs: i64) -> Self::Output {
|
||||
let result = Self(self.0 + rhs);
|
||||
#[cfg(debug_assertions)]
|
||||
result.check();
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign<i64> for RataDie {
|
||||
fn add_assign(&mut self, rhs: i64) {
|
||||
self.0 += rhs;
|
||||
#[cfg(debug_assertions)]
|
||||
self.check();
|
||||
}
|
||||
}
|
||||
|
||||
/// Shift a RataDie N days into the past
|
||||
impl Sub<i64> for RataDie {
|
||||
type Output = Self;
|
||||
fn sub(self, rhs: i64) -> Self::Output {
|
||||
let result = Self(self.0 - rhs);
|
||||
#[cfg(debug_assertions)]
|
||||
result.check();
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl SubAssign<i64> for RataDie {
|
||||
fn sub_assign(&mut self, rhs: i64) {
|
||||
self.0 -= rhs;
|
||||
#[cfg(debug_assertions)]
|
||||
self.check();
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate the number of days between two RataDie
|
||||
impl Sub for RataDie {
|
||||
type Output = i64;
|
||||
fn sub(self, rhs: Self) -> Self::Output {
|
||||
self.0 - rhs.0
|
||||
}
|
||||
}
|
||||
|
||||
/// A moment is a RataDie with a fractional part giving the time of day.
|
||||
///
|
||||
/// NOTE: This should not cause overflow errors for most cases, but consider
|
||||
/// alternative implementations if necessary.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
|
||||
pub struct Moment(f64);
|
||||
|
||||
/// Add a number of days to a Moment
|
||||
impl Add<f64> for Moment {
|
||||
type Output = Self;
|
||||
fn add(self, rhs: f64) -> Self::Output {
|
||||
Self(self.0 + rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign<f64> for Moment {
|
||||
fn add_assign(&mut self, rhs: f64) {
|
||||
self.0 += rhs;
|
||||
}
|
||||
}
|
||||
|
||||
/// Subtract a number of days from a Moment
|
||||
impl Sub<f64> for Moment {
|
||||
type Output = Self;
|
||||
fn sub(self, rhs: f64) -> Self::Output {
|
||||
Self(self.0 - rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl SubAssign<f64> for Moment {
|
||||
fn sub_assign(&mut self, rhs: f64) {
|
||||
self.0 -= rhs;
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate the number of days between two moments
|
||||
impl Sub for Moment {
|
||||
type Output = f64;
|
||||
fn sub(self, rhs: Self) -> Self::Output {
|
||||
self.0 - rhs.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Moment {
|
||||
/// Create a new moment
|
||||
pub const fn new(value: f64) -> Moment {
|
||||
Moment(value)
|
||||
}
|
||||
|
||||
/// Get the inner field of a Moment
|
||||
pub const fn inner(&self) -> f64 {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Get the RataDie of a Moment
|
||||
pub fn as_rata_die(&self) -> RataDie {
|
||||
RataDie::new(self.0.floor() as i64)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_moment_to_rata_die_conversion() {
|
||||
for i in -1000..=1000 {
|
||||
let moment = Moment::new(i as f64);
|
||||
let rata_die = moment.as_rata_die();
|
||||
assert_eq!(rata_die.to_i64_date(), i);
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
{"files":{"Cargo.lock":"84b7de1aceb9f2202f84e1d284f1c6a2e4e4bbfe729c7fdaed2fdc4603f94041","Cargo.toml":"d4f6e0f401a4d53365f2107d19f7bf7becd73a7e66939cd3a08964460602055d","LICENSE":"853f87c96f3d249f200fec6db1114427bc8bdf4afddc93c576956d78152ce978","README.md":"dcac57005c07034745512963cf09963e54d73141a61e35be9b102b4b1354535a","benches/convert.rs":"f9ab13aa93f1fcef7e20700aab803e358494d97f17e0a853dfd11fb944be4860","benches/date.rs":"664d4e0cb21e7f0e902895543cb00ec2af166488847f14de2ae305bca799f93b","benches/datetime.rs":"03b49c3f427f87a9b44959ef60c17d41a87692bb44134f1fd099635ed9adfb7c","benches/fixtures/datetimes.json":"ad8db74905ad8837b5e75ddc0bb51c1f277a8aa20e486d6927438a8fc7f094a6","benches/fixtures/mod.rs":"fdb50c26990cf5e083fbf7b2d9ee6ddfbd8be0279f7f90952e823e0e2c568e0a","benches/fixtures/structs.rs":"c927a527009af7d29bb16fdb893704178ac577ec2688baffa924f24da7f6b040","benches/iso.rs":"c80fa2c7b2554ad3f63bb88e5b31481757dbd916de0be88ae6b8057c3390c33f","examples/iso_date_manipulations.rs":"ed178ed88aa9a900df2841ae1f443ee8a69932ff1119ab3fadba07085c0665b6","examples/iso_datetime_manipulations.rs":"e6f2063ef0e41dca9ab4efa12646ac1f88ddfce80e8a0aa41f77c68c5e759f91","src/any_calendar.rs":"06e4a65bc02cc57514b820f03db60bf019bc39faeb83c92471301a6658ebbae8","src/buddhist.rs":"faf54cf146147fd2789317a4d28fa7d8bdb6a134488732f060af2bf1b833af36","src/calendar.rs":"1b4a38d7d2715f6832abdcf8b4bf45c4762722fb6c784a48cfb335da33357e21","src/calendar_arithmetic.rs":"bb46b046edaf05f099f39c394278b36caf85467760268143cffde938530532bb","src/chinese.rs":"8e356604a79e6925b6d531ccb6a5b7efb1277de17505a8d662004d9cc7f15a74","src/chinese_based.rs":"bb485620170200d14cedba6d949660d6268c20af0ea5c366f3fb13d08d2c0325","src/chinese_data.rs":"95ac42336b97da6eb16412eb7c03c2b0bb22b4f61e12c71a28e1ff5ee6a51366","src/coptic.rs":"eb52f90acb0a5d84202708a40aa32b9d708123594ff47aa6e83e23a8290add54","src/dangi.rs":"391094ffef10d2a13a0e86b0cbb2c065b9b09e85fa86f7947e2a5aac46579cca","src/date.rs":"d5d2f2be614bd1b0cc77f717e3f216b57e8ce4a950a0052f12287f9647c50351","src/datetime.rs":"238defbf65abf5d21325a364677d1d8174e33f19e88a446a76203828b783cddc","src/duration.rs":"3eb6eacce53b83e5ff2177885b952ed9b8b5102772bf6849d0b7922d623136a5","src/error.rs":"23a9c356dd3bc2d9d8eb73845dd7e48370202dda0d60a6f84fdd74020d2252d6","src/ethiopian.rs":"df4cb822556bbe1c3bdf4e13d63e98123e4b5f324447e1c75f5dde4a43819bde","src/gregorian.rs":"b8d2ed36dce818288b576b226dbff86c0800fd170ebc9ab6df69edbd917ac4d4","src/hebrew.rs":"3298dabdb00c72e2793afef1cb390bceb4c75850cffa867ae4f42b30a6b85f29","src/indian.rs":"d4627a4fe56428b5c781bf2cbc79d7a9fc31af8ce64e56f4c99113901b83917b","src/islamic.rs":"7a4fad4f28fbcc69e62535993756e00b83952d806495c1ddc8f726ca8160aef7","src/iso.rs":"e9ea84f1c774123be55822805caf6006caa3f918348487fc7903ab2401a1e67d","src/japanese.rs":"e859c2c46038173c8082aa8c5d57e38f92bab4650f2ccd8f786757c89e07dd9d","src/julian.rs":"ac98eb7304ec439d7e284f1ee6251501b03bded2f70699e60fb916124afbfb72","src/lib.rs":"e06e108dfffcb2a6b4eac117210c71566065ff83058c28ed5c3bb9c9dc3cb5f3","src/persian.rs":"084dcafeed913e0083a0a1a2592a4eac27e09e977eb61bdab61e0557fc83e707","src/provider.rs":"f228c100919ccdb3d06b84151d5dc84331ce0c18d4dc5a5dd75beb0ab3d30d5f","src/roc.rs":"a941226bfc8ff076e343711d0f1697148d310ac1ed7fe1fcdc20bd3f1a486a07","src/types.rs":"68ee163d33a7daf7ab7b299c03c99efcfa7d2374ca0e9cd1aecb5433990d520d","src/week_of.rs":"48f1a3923a53e49b4790e3f371f23663aa0ab394c3ba61cb22833f6fa80cd2c6"},"package":"7eb932a690c92f87955e923106181ee0d5682e688ff37fb5c7b296e1fe806edb"}
|
|
@ -0,0 +1,845 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anes"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
|
||||
|
||||
[[package]]
|
||||
name = "calendrical_calculations"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8dfe3bc6a50b4667fafdb6d9cf26731c5418c457e317d8166c972014facf9a5d"
|
||||
dependencies = [
|
||||
"core_maths",
|
||||
"displaydoc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cast"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "ciborium"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926"
|
||||
dependencies = [
|
||||
"ciborium-io",
|
||||
"ciborium-ll",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ciborium-io"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656"
|
||||
|
||||
[[package]]
|
||||
name = "ciborium-ll"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b"
|
||||
dependencies = [
|
||||
"ciborium-io",
|
||||
"half",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "3.2.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"clap_lex",
|
||||
"indexmap",
|
||||
"textwrap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
|
||||
dependencies = [
|
||||
"os_str_bytes",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core_maths"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3b02505ccb8c50b0aa21ace0fc08c3e53adebd4e58caa18a36152803c7709a3"
|
||||
dependencies = [
|
||||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "criterion"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7c76e09c1aae2bc52b3d2f29e13c6572553b30c4aa1b8a49fd70de6412654cb"
|
||||
dependencies = [
|
||||
"anes",
|
||||
"atty",
|
||||
"cast",
|
||||
"ciborium",
|
||||
"clap",
|
||||
"criterion-plot",
|
||||
"itertools",
|
||||
"lazy_static",
|
||||
"num-traits",
|
||||
"oorandom",
|
||||
"plotters",
|
||||
"rayon",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"tinytemplate",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "criterion-plot"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
|
||||
dependencies = [
|
||||
"cast",
|
||||
"itertools",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
"memoffset",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "databake"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82175d72e69414ceafbe2b49686794d3a8bed846e0d50267355f83ea8fdd953a"
|
||||
dependencies = [
|
||||
"databake-derive",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "databake-derive"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "377af281d8f23663862a7c84623bc5dcf7f8c44b13c7496a590bdc157f941a43"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "1.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_calendar"
|
||||
version = "1.4.0"
|
||||
dependencies = [
|
||||
"calendrical_calculations",
|
||||
"criterion",
|
||||
"databake",
|
||||
"displaydoc",
|
||||
"icu_calendar_data",
|
||||
"icu_locid",
|
||||
"icu_locid_transform",
|
||||
"icu_provider",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tinystr",
|
||||
"writeable",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_calendar_data"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22aec7d032735d9acb256eeef72adcac43c3b7572f19b51576a63d664b524ca2"
|
||||
|
||||
[[package]]
|
||||
name = "icu_locid"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c0aa2536adc14c07e2a521e95512b75ed8ef832f0fdf9299d4a0a45d2be2a9d"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"litemap",
|
||||
"tinystr",
|
||||
"writeable",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_locid_transform"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c17d8f6524fdca4471101dd71f0a132eb6382b5d6d7f2970441cb25f6f435a"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"icu_locid",
|
||||
"icu_locid_transform_data",
|
||||
"icu_provider",
|
||||
"tinystr",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_locid_transform_data"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "545c6c3e8bf9580e2dafee8de6f9ec14826aaf359787789c7724f1f85f47d3dc"
|
||||
|
||||
[[package]]
|
||||
name = "icu_provider"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba58e782287eb6950247abbf11719f83f5d4e4a5c1f2cd490d30a334bc47c2f4"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"icu_locid",
|
||||
"icu_provider_macros",
|
||||
"serde",
|
||||
"stable_deref_trait",
|
||||
"tinystr",
|
||||
"writeable",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_provider_macros"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2abdd3a62551e8337af119c5899e600ca0c88ec8f23a46c60ba216c803dcf1a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.64"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.148"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b"
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4"
|
||||
|
||||
[[package]]
|
||||
name = "litemap"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9d642685b028806386b2b6e75685faadd3eb65a85fff7df711ce18446a422da"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
|
||||
|
||||
[[package]]
|
||||
name = "oorandom"
|
||||
version = "11.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
|
||||
|
||||
[[package]]
|
||||
name = "os_str_bytes"
|
||||
version = "6.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac"
|
||||
|
||||
[[package]]
|
||||
name = "plotters"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"plotters-backend",
|
||||
"plotters-svg",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "plotters-backend"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609"
|
||||
|
||||
[[package]]
|
||||
name = "plotters-svg"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab"
|
||||
dependencies = [
|
||||
"plotters-backend",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.67"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1"
|
||||
dependencies = [
|
||||
"either",
|
||||
"rayon-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon-core"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed"
|
||||
dependencies = [
|
||||
"crossbeam-deque",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
|
||||
|
||||
[[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 = "scopeguard"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.188"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.188"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "synstructure"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "285ba80e733fac80aa4270fbcdf83772a79b80aa35c97075320abfee4a915b06"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d"
|
||||
|
||||
[[package]]
|
||||
name = "tinystr"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83c02bf3c538ab32ba913408224323915f4ef9a6d61c0e85d493f355921c0ece"
|
||||
dependencies = [
|
||||
"databake",
|
||||
"displaydoc",
|
||||
"serde",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinytemplate"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.87"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.87"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.87"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.87"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.87"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1"
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.64"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "writeable"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dad7bb64b8ef9c0aa27b6da38b452b0ee9fd82beaf276a87dd796fb55cbae14e"
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65e71b2e4f287f467794c671e2b8f8a5f3716b3c829079a1c44740148eff07e4"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"stable_deref_trait",
|
||||
"yoke-derive",
|
||||
"zerofrom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yoke-derive"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e6936f0cce458098a201c245a11bef556c6a0181129c7034d10d76d1ec3a2b8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "655b0814c5c0b19ade497851070c640773304939a6c0fd5f5fb43da0696d05b7"
|
||||
dependencies = [
|
||||
"zerofrom-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom-derive"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6a647510471d372f2e6c2e6b7219e44d8c574d24fdc11c610a61455782f18c3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerovec"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eff4439ae91fb5c72b8abc12f3f2dbf51bd27e6eadb9f8a5bc8898dddb0e27ea"
|
||||
dependencies = [
|
||||
"databake",
|
||||
"serde",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerovec-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerovec-derive"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b4e5997cbf58990550ef1f0e5124a05e47e1ebd33a84af25739be6031a62c20"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
|
@ -0,0 +1,159 @@
|
|||
# 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.67"
|
||||
name = "icu_calendar"
|
||||
version = "1.4.0"
|
||||
authors = ["The ICU4X Project Developers"]
|
||||
include = [
|
||||
"data/**/*",
|
||||
"src/**/*",
|
||||
"examples/**/*",
|
||||
"benches/**/*",
|
||||
"tests/**/*",
|
||||
"Cargo.toml",
|
||||
"LICENSE",
|
||||
"README.md",
|
||||
]
|
||||
description = "API for supporting various types of calendars"
|
||||
homepage = "https://icu4x.unicode.org"
|
||||
readme = "README.md"
|
||||
categories = ["internationalization"]
|
||||
license-file = "LICENSE"
|
||||
repository = "https://github.com/unicode-org/icu4x"
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["bench"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
[[example]]
|
||||
name = "iso_date_manipulations"
|
||||
|
||||
[[example]]
|
||||
name = "iso_datetime_manipulations"
|
||||
|
||||
[[bench]]
|
||||
name = "date"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "datetime"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "iso"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "convert"
|
||||
harness = false
|
||||
|
||||
[dependencies.calendrical_calculations]
|
||||
version = "0.1.0"
|
||||
default-features = false
|
||||
|
||||
[dependencies.databake]
|
||||
version = "0.1.7"
|
||||
features = ["derive"]
|
||||
optional = true
|
||||
default-features = false
|
||||
|
||||
[dependencies.displaydoc]
|
||||
version = "0.2.3"
|
||||
default-features = false
|
||||
|
||||
[dependencies.icu_calendar_data]
|
||||
version = "~1.4.0"
|
||||
optional = true
|
||||
default-features = false
|
||||
|
||||
[dependencies.icu_locid]
|
||||
version = "~1.4.0"
|
||||
default-features = false
|
||||
|
||||
[dependencies.icu_locid_transform]
|
||||
version = "~1.4.0"
|
||||
features = ["compiled_data"]
|
||||
optional = true
|
||||
default-features = false
|
||||
|
||||
[dependencies.icu_provider]
|
||||
version = "~1.4.0"
|
||||
features = ["macros"]
|
||||
default-features = false
|
||||
|
||||
[dependencies.serde]
|
||||
version = "1.0"
|
||||
features = [
|
||||
"derive",
|
||||
"alloc",
|
||||
]
|
||||
optional = true
|
||||
default-features = false
|
||||
|
||||
[dependencies.tinystr]
|
||||
version = "0.7.4"
|
||||
features = [
|
||||
"alloc",
|
||||
"zerovec",
|
||||
]
|
||||
default-features = false
|
||||
|
||||
[dependencies.writeable]
|
||||
version = "0.5.4"
|
||||
default-features = false
|
||||
|
||||
[dependencies.zerovec]
|
||||
version = "0.10.1"
|
||||
features = ["derive"]
|
||||
default-features = false
|
||||
|
||||
[dev-dependencies.serde]
|
||||
version = "1.0"
|
||||
features = [
|
||||
"derive",
|
||||
"alloc",
|
||||
]
|
||||
|
||||
[dev-dependencies.serde_json]
|
||||
version = "1.0"
|
||||
|
||||
[features]
|
||||
bench = []
|
||||
compiled_data = [
|
||||
"dep:icu_calendar_data",
|
||||
"dep:icu_locid_transform",
|
||||
]
|
||||
datagen = [
|
||||
"serde",
|
||||
"dep:databake",
|
||||
"zerovec/databake",
|
||||
"tinystr/databake",
|
||||
]
|
||||
default = ["compiled_data"]
|
||||
serde = [
|
||||
"dep:serde",
|
||||
"zerovec/serde",
|
||||
"tinystr/serde",
|
||||
"icu_provider/serde",
|
||||
]
|
||||
std = [
|
||||
"icu_provider/std",
|
||||
"icu_locid/std",
|
||||
"calendrical_calculations/std",
|
||||
]
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dev-dependencies.criterion]
|
||||
version = "0.4"
|
|
@ -0,0 +1,44 @@
|
|||
UNICODE LICENSE V3
|
||||
|
||||
COPYRIGHT AND PERMISSION NOTICE
|
||||
|
||||
Copyright © 2020-2023 Unicode, Inc.
|
||||
|
||||
NOTICE TO USER: Carefully read the following legal agreement. BY
|
||||
DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR
|
||||
SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE
|
||||
TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT
|
||||
DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of data files and any associated documentation (the "Data Files") or
|
||||
software and any associated documentation (the "Software") to deal in the
|
||||
Data Files or Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, and/or sell
|
||||
copies of the Data Files or Software, and to permit persons to whom the
|
||||
Data Files or Software are furnished to do so, provided that either (a)
|
||||
this copyright and permission notice appear with all copies of the Data
|
||||
Files or Software, or (b) this copyright and permission notice appear in
|
||||
associated Documentation.
|
||||
|
||||
THE DATA FILES AND SOFTWARE ARE 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 OF
|
||||
THIRD PARTY RIGHTS.
|
||||
|
||||
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE
|
||||
BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES,
|
||||
OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
|
||||
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
||||
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA
|
||||
FILES OR SOFTWARE.
|
||||
|
||||
Except as contained in this notice, the name of a copyright holder shall
|
||||
not be used in advertising or otherwise to promote the sale, use or other
|
||||
dealings in these Data Files or Software without prior written
|
||||
authorization of the copyright holder.
|
||||
|
||||
—
|
||||
|
||||
Portions of ICU4X may have been adapted from ICU4C and/or ICU4J.
|
||||
ICU 1.8.1 to ICU 57.1 © 1995-2016 International Business Machines Corporation and others.
|
|
@ -0,0 +1,100 @@
|
|||
# icu_calendar [![crates.io](https://img.shields.io/crates/v/icu_calendar)](https://crates.io/crates/icu_calendar)
|
||||
|
||||
<!-- cargo-rdme start -->
|
||||
|
||||
Types for dealing with dates, times, and custom calendars.
|
||||
|
||||
This module is published as its own crate ([`icu_calendar`](https://docs.rs/icu_calendar/latest/icu_calendar/))
|
||||
and as part of the [`icu`](https://docs.rs/icu/latest/icu/) crate. See the latter for more details on the ICU4X project.
|
||||
The [`types`] module has a lot of common types for dealing with dates and times.
|
||||
|
||||
[`Calendar`] is a trait that allows one to define custom calendars, and [`Date`]
|
||||
can represent dates for arbitrary calendars.
|
||||
|
||||
The [`iso`] and [`gregorian`] modules contain implementations for the ISO and
|
||||
Gregorian calendars respectively. Further calendars can be found in modules like
|
||||
[`japanese`], [`julian`], [`coptic`], [`indian`], [`buddhist`], and [`ethiopian`].
|
||||
|
||||
Most interaction with this crate will be done via the [`Date`] and [`DateTime`] types.
|
||||
|
||||
Some of the algorithms implemented here are based on
|
||||
Dershowitz, Nachum, and Edward M. Reingold. _Calendrical calculations_. Cambridge University Press, 2008.
|
||||
with associated Lisp code found at <https://github.com/EdReingold/calendar-code2>.
|
||||
|
||||
## Examples
|
||||
|
||||
Examples of date manipulation using `Date` object. `Date` objects are useful
|
||||
for working with dates, encompassing information about the day, month, year,
|
||||
as well as the calendar type.
|
||||
|
||||
```rust
|
||||
use icu::calendar::{types::IsoWeekday, Date};
|
||||
|
||||
// Creating ISO date: 1992-09-02.
|
||||
let mut date_iso = Date::try_new_iso_date(1992, 9, 2)
|
||||
.expect("Failed to initialize ISO Date instance.");
|
||||
|
||||
assert_eq!(date_iso.day_of_week(), IsoWeekday::Wednesday);
|
||||
assert_eq!(date_iso.year().number, 1992);
|
||||
assert_eq!(date_iso.month().ordinal, 9);
|
||||
assert_eq!(date_iso.day_of_month().0, 2);
|
||||
|
||||
// Answering questions about days in month and year.
|
||||
assert_eq!(date_iso.days_in_year(), 366);
|
||||
assert_eq!(date_iso.days_in_month(), 30);
|
||||
```
|
||||
|
||||
Example of converting an ISO date across Indian and Buddhist calendars.
|
||||
|
||||
```rust
|
||||
use icu::calendar::{buddhist::Buddhist, indian::Indian, Date};
|
||||
|
||||
// Creating ISO date: 1992-09-02.
|
||||
let mut date_iso = Date::try_new_iso_date(1992, 9, 2)
|
||||
.expect("Failed to initialize ISO Date instance.");
|
||||
|
||||
assert_eq!(date_iso.year().number, 1992);
|
||||
assert_eq!(date_iso.month().ordinal, 9);
|
||||
assert_eq!(date_iso.day_of_month().0, 2);
|
||||
|
||||
// Conversion into Indian calendar: 1914-08-02.
|
||||
let date_indian = date_iso.to_calendar(Indian);
|
||||
assert_eq!(date_indian.year().number, 1914);
|
||||
assert_eq!(date_indian.month().ordinal, 6);
|
||||
assert_eq!(date_indian.day_of_month().0, 11);
|
||||
|
||||
// Conversion into Buddhist calendar: 2535-09-02.
|
||||
let date_buddhist = date_iso.to_calendar(Buddhist);
|
||||
assert_eq!(date_buddhist.year().number, 2535);
|
||||
assert_eq!(date_buddhist.month().ordinal, 9);
|
||||
assert_eq!(date_buddhist.day_of_month().0, 2);
|
||||
```
|
||||
|
||||
Example using `DateTime` object. Similar to `Date` objects, `DateTime` objects
|
||||
contain an accessible `Date` object containing information about the day, month,
|
||||
year, and calendar type. Additionally, `DateTime` objects contain an accessible
|
||||
`Time` object, including granularity of hour, minute, second, and nanosecond.
|
||||
|
||||
```rust
|
||||
use icu::calendar::{types::IsoWeekday, types::Time, DateTime};
|
||||
|
||||
// Creating ISO date: 1992-09-02 8:59
|
||||
let mut datetime_iso = DateTime::try_new_iso_datetime(1992, 9, 2, 8, 59, 0)
|
||||
.expect("Failed to initialize ISO DateTime instance.");
|
||||
|
||||
assert_eq!(datetime_iso.date.day_of_week(), IsoWeekday::Wednesday);
|
||||
assert_eq!(datetime_iso.date.year().number, 1992);
|
||||
assert_eq!(datetime_iso.date.month().ordinal, 9);
|
||||
assert_eq!(datetime_iso.date.day_of_month().0, 2);
|
||||
assert_eq!(datetime_iso.time.hour.number(), 8);
|
||||
assert_eq!(datetime_iso.time.minute.number(), 59);
|
||||
assert_eq!(datetime_iso.time.second.number(), 0);
|
||||
assert_eq!(datetime_iso.time.nanosecond.number(), 0);
|
||||
```
|
||||
[`ICU4X`]: ../icu/index.html
|
||||
|
||||
<!-- cargo-rdme end -->
|
||||
|
||||
## More Information
|
||||
|
||||
For more information on development, authorship, contributing etc. please visit [`ICU4X home page`](https://github.com/unicode-org/icu4x).
|
|
@ -0,0 +1,103 @@
|
|||
// This file is part of ICU4X. For terms of use, please see the file
|
||||
// called LICENSE at the top level of the ICU4X source tree
|
||||
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
|
||||
|
||||
mod fixtures;
|
||||
|
||||
use criterion::{
|
||||
black_box, criterion_group, criterion_main, measurement::WallTime, BenchmarkGroup, Criterion,
|
||||
};
|
||||
use icu_calendar::{Calendar, Date, Ref};
|
||||
|
||||
fn bench_calendar<C: Clone + Calendar>(
|
||||
group: &mut BenchmarkGroup<WallTime>,
|
||||
name: &str,
|
||||
calendar: C,
|
||||
) {
|
||||
let iso = Date::try_new_iso_date(2023, 8, 16).unwrap();
|
||||
group.bench_function(name, |b| {
|
||||
b.iter(|| {
|
||||
let converted = black_box(iso).to_calendar(Ref(&calendar));
|
||||
let year = black_box(converted.year().number);
|
||||
let month = black_box(converted.month().ordinal);
|
||||
let day = black_box(converted.day_of_month().0);
|
||||
black_box((converted, year, month, day))
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn convert_benches(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("convert");
|
||||
|
||||
bench_calendar(&mut group, "calendar/iso", icu::calendar::iso::Iso);
|
||||
|
||||
#[cfg(feature = "bench")]
|
||||
bench_calendar(
|
||||
&mut group,
|
||||
"calendar/buddhist",
|
||||
icu::calendar::buddhist::Buddhist,
|
||||
);
|
||||
|
||||
#[cfg(feature = "bench")]
|
||||
bench_calendar(&mut group, "calendar/coptic", icu::calendar::coptic::Coptic);
|
||||
|
||||
#[cfg(feature = "bench")]
|
||||
bench_calendar(
|
||||
&mut group,
|
||||
"calendar/ethiopic",
|
||||
icu::calendar::ethiopian::Ethiopian::new(),
|
||||
);
|
||||
|
||||
#[cfg(feature = "bench")]
|
||||
bench_calendar(&mut group, "calendar/indian", icu::calendar::indian::Indian);
|
||||
|
||||
#[cfg(feature = "bench")]
|
||||
bench_calendar(&mut group, "calendar/julian", icu::calendar::julian::Julian);
|
||||
|
||||
#[cfg(feature = "bench")]
|
||||
bench_calendar(
|
||||
&mut group,
|
||||
"calendar/chinese",
|
||||
icu::calendar::chinese::Chinese::new_always_calculating(),
|
||||
);
|
||||
|
||||
#[cfg(feature = "bench")]
|
||||
bench_calendar(
|
||||
&mut group,
|
||||
"calendar/gregorian",
|
||||
icu::calendar::gregorian::Gregorian,
|
||||
);
|
||||
|
||||
#[cfg(feature = "bench")]
|
||||
bench_calendar(
|
||||
&mut group,
|
||||
"calendar/islamic/observational",
|
||||
icu::calendar::islamic::IslamicObservational::new_always_calculating(),
|
||||
);
|
||||
|
||||
#[cfg(feature = "bench")]
|
||||
bench_calendar(
|
||||
&mut group,
|
||||
"calendar/islamic/civil",
|
||||
icu::calendar::islamic::IslamicCivil::new_always_calculating(),
|
||||
);
|
||||
|
||||
#[cfg(feature = "bench")]
|
||||
bench_calendar(
|
||||
&mut group,
|
||||
"calendar/islamic/ummalqura",
|
||||
icu::calendar::islamic::IslamicUmmAlQura::new_always_calculating(),
|
||||
);
|
||||
|
||||
#[cfg(feature = "bench")]
|
||||
bench_calendar(
|
||||
&mut group,
|
||||
"calendar/islamic/tabular",
|
||||
icu::calendar::islamic::IslamicTabular::new_always_calculating(),
|
||||
);
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(benches, convert_benches);
|
||||
criterion_main!(benches);
|
|
@ -0,0 +1,271 @@
|
|||
// This file is part of ICU4X. For terms of use, please see the file
|
||||
// called LICENSE at the top level of the ICU4X source tree
|
||||
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
|
||||
|
||||
mod fixtures;
|
||||
|
||||
use crate::fixtures::structs::DateFixture;
|
||||
use criterion::{
|
||||
black_box, criterion_group, criterion_main, measurement::WallTime, BenchmarkGroup, Criterion,
|
||||
};
|
||||
use icu_calendar::{AsCalendar, Calendar, Date, DateDuration};
|
||||
|
||||
fn bench_date<A: AsCalendar>(date: &mut Date<A>) {
|
||||
// black_box used to avoid compiler optimization.
|
||||
// Arithmetic
|
||||
date.add(DateDuration::new(
|
||||
black_box(1),
|
||||
black_box(2),
|
||||
black_box(3),
|
||||
black_box(4),
|
||||
));
|
||||
|
||||
// Retrieving vals
|
||||
let _ = black_box(date.year().number);
|
||||
let _ = black_box(date.month().ordinal);
|
||||
let _ = black_box(date.day_of_month().0);
|
||||
|
||||
// Conversion to ISO.
|
||||
let _ = black_box(date.to_iso());
|
||||
}
|
||||
|
||||
fn bench_calendar<C: Clone + Calendar>(
|
||||
group: &mut BenchmarkGroup<WallTime>,
|
||||
name: &str,
|
||||
fxs: &DateFixture,
|
||||
calendar: C,
|
||||
calendar_date_init: impl Fn(i32, u8, u8) -> Date<C>,
|
||||
) {
|
||||
group.bench_function(name, |b| {
|
||||
b.iter(|| {
|
||||
for fx in &fxs.0 {
|
||||
// Instantion from int
|
||||
let mut instantiated_date_calendar = calendar_date_init(fx.year, fx.month, fx.day);
|
||||
|
||||
// Conversion from ISO
|
||||
let date_iso = Date::try_new_iso_date(fx.year, fx.month, fx.day).unwrap();
|
||||
let mut converted_date_calendar = Date::new_from_iso(date_iso, calendar.clone());
|
||||
|
||||
bench_date(&mut instantiated_date_calendar);
|
||||
bench_date(&mut converted_date_calendar);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn date_benches(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("date");
|
||||
let fxs = fixtures::get_dates_fixture().unwrap();
|
||||
|
||||
bench_calendar(
|
||||
&mut group,
|
||||
"calendar/overview",
|
||||
&fxs,
|
||||
icu::calendar::iso::Iso,
|
||||
|y, m, d| Date::try_new_iso_date(y, m, d).unwrap(),
|
||||
);
|
||||
|
||||
#[cfg(feature = "bench")]
|
||||
bench_calendar(
|
||||
&mut group,
|
||||
"calendar/buddhist",
|
||||
&fxs,
|
||||
icu::calendar::buddhist::Buddhist,
|
||||
|y, m, d| Date::try_new_buddhist_date(y, m, d).unwrap(),
|
||||
);
|
||||
|
||||
#[cfg(feature = "bench")]
|
||||
bench_calendar(
|
||||
&mut group,
|
||||
"calendar/coptic",
|
||||
&fxs,
|
||||
icu::calendar::coptic::Coptic,
|
||||
|y, m, d| Date::try_new_coptic_date(y, m, d).unwrap(),
|
||||
);
|
||||
|
||||
#[cfg(feature = "bench")]
|
||||
bench_calendar(
|
||||
&mut group,
|
||||
"calendar/ethiopic",
|
||||
&fxs,
|
||||
icu::calendar::ethiopian::Ethiopian::new(),
|
||||
|y, m, d| {
|
||||
Date::try_new_ethiopian_date(
|
||||
icu::calendar::ethiopian::EthiopianEraStyle::AmeteMihret,
|
||||
y,
|
||||
m,
|
||||
d,
|
||||
)
|
||||
.unwrap()
|
||||
},
|
||||
);
|
||||
|
||||
#[cfg(feature = "bench")]
|
||||
bench_calendar(
|
||||
&mut group,
|
||||
"calendar/indian",
|
||||
&fxs,
|
||||
icu::calendar::indian::Indian,
|
||||
|y, m, d| Date::try_new_indian_date(y, m, d).unwrap(),
|
||||
);
|
||||
|
||||
#[cfg(feature = "bench")]
|
||||
bench_calendar(
|
||||
&mut group,
|
||||
"calendar/persian",
|
||||
&fxs,
|
||||
icu::calendar::persian::Persian,
|
||||
|y, m, d| Date::try_new_persian_date(y, m, d).unwrap(),
|
||||
);
|
||||
|
||||
#[cfg(feature = "bench")]
|
||||
bench_calendar(
|
||||
&mut group,
|
||||
"calendar/roc",
|
||||
&fxs,
|
||||
icu::calendar::roc::Roc,
|
||||
|y, m, d| Date::try_new_roc_date(y, m, d).unwrap(),
|
||||
);
|
||||
|
||||
#[cfg(feature = "bench")]
|
||||
bench_calendar(
|
||||
&mut group,
|
||||
"calendar/julian",
|
||||
&fxs,
|
||||
icu::calendar::julian::Julian,
|
||||
|y, m, d| Date::try_new_julian_date(y, m, d).unwrap(),
|
||||
);
|
||||
|
||||
#[cfg(feature = "bench")]
|
||||
bench_calendar(
|
||||
&mut group,
|
||||
"calendar/chinese",
|
||||
&fxs,
|
||||
icu::calendar::chinese::Chinese::new_always_calculating(),
|
||||
|y, m, d| {
|
||||
Date::try_new_chinese_date_with_calendar(
|
||||
y,
|
||||
m,
|
||||
d,
|
||||
icu::calendar::chinese::Chinese::new_always_calculating(),
|
||||
)
|
||||
.unwrap()
|
||||
},
|
||||
);
|
||||
|
||||
#[cfg(feature = "bench")]
|
||||
bench_calendar(
|
||||
&mut group,
|
||||
"calendar/dangi",
|
||||
&fxs,
|
||||
icu::calendar::dangi::Dangi::new_always_calculating(),
|
||||
|y, m, d| {
|
||||
Date::try_new_dangi_date_with_calendar(
|
||||
y,
|
||||
m,
|
||||
d,
|
||||
icu::calendar::dangi::Dangi::new_always_calculating(),
|
||||
)
|
||||
.unwrap()
|
||||
},
|
||||
);
|
||||
|
||||
#[cfg(feature = "bench")]
|
||||
bench_calendar(
|
||||
&mut group,
|
||||
"calendar/hebrew",
|
||||
&fxs,
|
||||
icu::calendar::hebrew::Hebrew::new_always_calculating(),
|
||||
|y, m, d| {
|
||||
Date::try_new_hebrew_date_with_calendar(
|
||||
y,
|
||||
m,
|
||||
d,
|
||||
icu::calendar::hebrew::Hebrew::new_always_calculating(),
|
||||
)
|
||||
.unwrap()
|
||||
},
|
||||
);
|
||||
|
||||
#[cfg(feature = "bench")]
|
||||
bench_calendar(
|
||||
&mut group,
|
||||
"calendar/gregorian",
|
||||
&fxs,
|
||||
icu::calendar::gregorian::Gregorian,
|
||||
|y, m, d| Date::try_new_gregorian_date(y, m, d).unwrap(),
|
||||
);
|
||||
|
||||
#[cfg(feature = "bench")]
|
||||
bench_calendar(
|
||||
&mut group,
|
||||
"calendar/islamic/civil",
|
||||
&fxs,
|
||||
icu::calendar::islamic::IslamicCivil::new_always_calculating(),
|
||||
|y, m, d| {
|
||||
Date::try_new_islamic_civil_date_with_calendar(
|
||||
y,
|
||||
m,
|
||||
d,
|
||||
icu::calendar::islamic::IslamicCivil::new_always_calculating(),
|
||||
)
|
||||
.unwrap()
|
||||
},
|
||||
);
|
||||
|
||||
#[cfg(feature = "bench")]
|
||||
bench_calendar(
|
||||
&mut group,
|
||||
"calendar/islamic/tabular",
|
||||
&fxs,
|
||||
icu::calendar::islamic::IslamicTabular::new_always_calculating(),
|
||||
|y, m, d| {
|
||||
Date::try_new_islamic_tabular_date_with_calendar(
|
||||
y,
|
||||
m,
|
||||
d,
|
||||
icu::calendar::islamic::IslamicTabular::new_always_calculating(),
|
||||
)
|
||||
.unwrap()
|
||||
},
|
||||
);
|
||||
|
||||
#[cfg(feature = "bench")]
|
||||
bench_calendar(
|
||||
&mut group,
|
||||
"calendar/islamic/ummalqura",
|
||||
&fxs,
|
||||
icu::calendar::islamic::IslamicUmmAlQura::new_always_calculating(),
|
||||
|y, m, d| {
|
||||
Date::try_new_ummalqura_date(
|
||||
y,
|
||||
m,
|
||||
d,
|
||||
icu::calendar::islamic::IslamicUmmAlQura::new_always_calculating(),
|
||||
)
|
||||
.unwrap()
|
||||
},
|
||||
);
|
||||
|
||||
#[cfg(feature = "bench")]
|
||||
bench_calendar(
|
||||
&mut group,
|
||||
"calendar/islamic/observational",
|
||||
&fxs,
|
||||
icu::calendar::islamic::IslamicObservational::new_always_calculating(),
|
||||
|y, m, d| {
|
||||
Date::try_new_observational_islamic_date(
|
||||
y,
|
||||
m,
|
||||
d,
|
||||
icu::calendar::islamic::IslamicObservational::new_always_calculating(),
|
||||
)
|
||||
.unwrap()
|
||||
},
|
||||
);
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(benches, date_benches);
|
||||
criterion_main!(benches);
|
|
@ -0,0 +1,248 @@
|
|||
// This file is part of ICU4X. For terms of use, please see the file
|
||||
// called LICENSE at the top level of the ICU4X source tree
|
||||
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
|
||||
|
||||
mod fixtures;
|
||||
|
||||
use crate::fixtures::structs::DateFixture;
|
||||
use criterion::{
|
||||
black_box, criterion_group, criterion_main, measurement::WallTime, BenchmarkGroup, Criterion,
|
||||
};
|
||||
use icu_calendar::{types::Time, AsCalendar, Calendar, DateDuration, DateTime};
|
||||
|
||||
fn bench_datetime<A: AsCalendar>(datetime: &mut DateTime<A>) {
|
||||
// black_box used to avoid compiler optimization.
|
||||
// Arithmetic.
|
||||
datetime.date.add(DateDuration::new(
|
||||
black_box(1),
|
||||
black_box(2),
|
||||
black_box(3),
|
||||
black_box(4),
|
||||
));
|
||||
datetime.time = Time::try_new(black_box(14), black_box(30), black_box(0), black_box(0))
|
||||
.expect("Failed to initialize Time instance.");
|
||||
|
||||
// Retrieving vals
|
||||
let _ = black_box(datetime.date.year().number);
|
||||
let _ = black_box(datetime.date.month().ordinal);
|
||||
let _ = black_box(datetime.date.day_of_month().0);
|
||||
let _ = black_box(datetime.time.hour);
|
||||
let _ = black_box(datetime.time.minute);
|
||||
let _ = black_box(datetime.time.second);
|
||||
|
||||
// Conversion to ISO.
|
||||
let _ = black_box(datetime.to_iso());
|
||||
}
|
||||
|
||||
fn bench_calendar<C: Clone + Calendar>(
|
||||
group: &mut BenchmarkGroup<WallTime>,
|
||||
name: &str,
|
||||
fxs: &DateFixture,
|
||||
calendar: C,
|
||||
calendar_datetime_init: impl Fn(i32, u8, u8, u8, u8, u8) -> DateTime<C>,
|
||||
) {
|
||||
group.bench_function(name, |b| {
|
||||
b.iter(|| {
|
||||
for fx in &fxs.0 {
|
||||
// Instantion from int
|
||||
let mut instantiated_datetime_calendar = calendar_datetime_init(
|
||||
fx.year, fx.month, fx.day, fx.hour, fx.minute, fx.second,
|
||||
);
|
||||
|
||||
// Conversion from ISO
|
||||
let datetime_iso = DateTime::try_new_iso_datetime(
|
||||
fx.year, fx.month, fx.day, fx.hour, fx.minute, fx.second,
|
||||
)
|
||||
.unwrap();
|
||||
let mut converted_datetime_calendar =
|
||||
DateTime::new_from_iso(datetime_iso, calendar.clone());
|
||||
|
||||
bench_datetime(&mut instantiated_datetime_calendar);
|
||||
bench_datetime(&mut converted_datetime_calendar);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn datetime_benches(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("datetime");
|
||||
let fxs = fixtures::get_dates_fixture().unwrap();
|
||||
|
||||
bench_calendar(
|
||||
&mut group,
|
||||
"calendar/overview",
|
||||
&fxs,
|
||||
icu::calendar::iso::Iso,
|
||||
|y, m, d, h, min, s| DateTime::try_new_iso_datetime(y, m, d, h, min, s).unwrap(),
|
||||
);
|
||||
|
||||
#[cfg(feature = "bench")]
|
||||
bench_calendar(
|
||||
&mut group,
|
||||
"calendar/buddhist",
|
||||
&fxs,
|
||||
icu::calendar::buddhist::Buddhist,
|
||||
|y, m, d, h, min, s| DateTime::try_new_buddhist_datetime(y, m, d, h, min, s).unwrap(),
|
||||
);
|
||||
|
||||
#[cfg(feature = "bench")]
|
||||
bench_calendar(
|
||||
&mut group,
|
||||
"calendar/coptic",
|
||||
&fxs,
|
||||
icu::calendar::coptic::Coptic,
|
||||
|y, m, d, h, min, s| DateTime::try_new_coptic_datetime(y, m, d, h, min, s).unwrap(),
|
||||
);
|
||||
|
||||
#[cfg(feature = "bench")]
|
||||
bench_calendar(
|
||||
&mut group,
|
||||
"calendar/ethiopic",
|
||||
&fxs,
|
||||
icu::calendar::ethiopian::Ethiopian::new(),
|
||||
|y, m, d, h, min, s| {
|
||||
DateTime::try_new_ethiopian_datetime(
|
||||
icu::calendar::ethiopian::EthiopianEraStyle::AmeteMihret,
|
||||
y,
|
||||
m,
|
||||
d,
|
||||
h,
|
||||
min,
|
||||
s,
|
||||
)
|
||||
.unwrap()
|
||||
},
|
||||
);
|
||||
|
||||
#[cfg(feature = "bench")]
|
||||
bench_calendar(
|
||||
&mut group,
|
||||
"calendar/chinese",
|
||||
&fxs,
|
||||
icu::calendar::chinese::Chinese::new_always_calculating(),
|
||||
|y, m, d, h, min, s| {
|
||||
DateTime::try_new_chinese_datetime_with_calendar(
|
||||
y,
|
||||
m,
|
||||
d,
|
||||
h,
|
||||
min,
|
||||
s,
|
||||
icu::calendar::chinese::Chinese::new_always_calculating(),
|
||||
)
|
||||
.unwrap()
|
||||
},
|
||||
);
|
||||
|
||||
#[cfg(feature = "bench")]
|
||||
bench_calendar(
|
||||
&mut group,
|
||||
"calendar/gregorian",
|
||||
&fxs,
|
||||
icu::calendar::gregorian::Gregorian,
|
||||
|y, m, d, h, min, s| DateTime::try_new_gregorian_datetime(y, m, d, h, min, s).unwrap(),
|
||||
);
|
||||
|
||||
#[cfg(feature = "bench")]
|
||||
bench_calendar(
|
||||
&mut group,
|
||||
"calendar/indian",
|
||||
&fxs,
|
||||
icu::calendar::indian::Indian,
|
||||
|y, m, d, h, min, s| DateTime::try_new_indian_datetime(y, m, d, h, min, s).unwrap(),
|
||||
);
|
||||
|
||||
#[cfg(feature = "bench")]
|
||||
bench_calendar(
|
||||
&mut group,
|
||||
"calendar/julian",
|
||||
&fxs,
|
||||
icu::calendar::julian::Julian,
|
||||
|y, m, d, h, min, s| DateTime::try_new_julian_datetime(y, m, d, h, min, s).unwrap(),
|
||||
);
|
||||
|
||||
#[cfg(feature = "bench")]
|
||||
bench_calendar(
|
||||
&mut group,
|
||||
"calendar/islamic/civil",
|
||||
&fxs,
|
||||
icu::calendar::islamic::IslamicCivil::new_always_calculating(),
|
||||
|y, m, d, h, min, s| {
|
||||
DateTime::try_new_islamic_civil_datetime_with_calendar(
|
||||
y,
|
||||
m,
|
||||
d,
|
||||
h,
|
||||
min,
|
||||
s,
|
||||
icu::calendar::islamic::IslamicCivil::new_always_calculating(),
|
||||
)
|
||||
.unwrap()
|
||||
},
|
||||
);
|
||||
|
||||
#[cfg(feature = "bench")]
|
||||
bench_calendar(
|
||||
&mut group,
|
||||
"calendar/islamic/tabular",
|
||||
&fxs,
|
||||
icu::calendar::islamic::IslamicTabular::new_always_calculating(),
|
||||
|y, m, d, h, min, s| {
|
||||
DateTime::try_new_islamic_tabular_datetime_with_calendar(
|
||||
y,
|
||||
m,
|
||||
d,
|
||||
h,
|
||||
min,
|
||||
s,
|
||||
icu::calendar::islamic::IslamicTabular::new_always_calculating(),
|
||||
)
|
||||
.unwrap()
|
||||
},
|
||||
);
|
||||
|
||||
#[cfg(feature = "bench")]
|
||||
bench_calendar(
|
||||
&mut group,
|
||||
"calendar/islamic/ummalqura",
|
||||
&fxs,
|
||||
icu::calendar::islamic::IslamicUmmAlQura::new_always_calculating(),
|
||||
|y, m, d, h, min, s| {
|
||||
DateTime::try_new_ummalqura_datetime(
|
||||
y,
|
||||
m,
|
||||
d,
|
||||
h,
|
||||
min,
|
||||
s,
|
||||
icu::calendar::islamic::IslamicUmmAlQura::new_always_calculating(),
|
||||
)
|
||||
.unwrap()
|
||||
},
|
||||
);
|
||||
|
||||
#[cfg(feature = "bench")]
|
||||
bench_calendar(
|
||||
&mut group,
|
||||
"calendar/islamic/observational",
|
||||
&fxs,
|
||||
icu::calendar::islamic::IslamicObservational::new_always_calculating(),
|
||||
|y, m, d, h, min, s| {
|
||||
DateTime::try_new_observational_islamic_datetime(
|
||||
y,
|
||||
m,
|
||||
d,
|
||||
h,
|
||||
min,
|
||||
s,
|
||||
icu::calendar::islamic::IslamicObservational::new_always_calculating(),
|
||||
)
|
||||
.unwrap()
|
||||
},
|
||||
);
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(benches, datetime_benches);
|
||||
criterion_main!(benches);
|
|
@ -0,0 +1,82 @@
|
|||
[
|
||||
{
|
||||
"year": 1990,
|
||||
"month": 1,
|
||||
"day": 20,
|
||||
"hour": 14,
|
||||
"minute": 12,
|
||||
"second": 1
|
||||
},
|
||||
{
|
||||
"year": 1996,
|
||||
"month": 3,
|
||||
"day": 20,
|
||||
"hour": 2,
|
||||
"minute": 55,
|
||||
"second": 12
|
||||
},
|
||||
{
|
||||
"year": 2000,
|
||||
"month": 3,
|
||||
"day": 23,
|
||||
"hour": 1,
|
||||
"minute": 34,
|
||||
"second": 35
|
||||
},
|
||||
{
|
||||
"year": 2010,
|
||||
"month": 5,
|
||||
"day": 10,
|
||||
"hour": 9,
|
||||
"minute": 31,
|
||||
"second": 12
|
||||
},
|
||||
{
|
||||
"year": 2020,
|
||||
"month": 6,
|
||||
"day": 13,
|
||||
"hour": 23,
|
||||
"minute": 1,
|
||||
"second": 12
|
||||
},
|
||||
{
|
||||
"year": 1881,
|
||||
"month": 6,
|
||||
"day": 10,
|
||||
"hour": 3,
|
||||
"minute": 27,
|
||||
"second": 59
|
||||
},
|
||||
{
|
||||
"year": 1720,
|
||||
"month": 2,
|
||||
"day": 1,
|
||||
"hour": 13,
|
||||
"minute": 24,
|
||||
"second": 13
|
||||
},
|
||||
{
|
||||
"year": 1630,
|
||||
"month": 3,
|
||||
"day": 4,
|
||||
"hour": 3,
|
||||
"minute": 1,
|
||||
"second": 1
|
||||
},
|
||||
{
|
||||
"year": 1540,
|
||||
"month": 3,
|
||||
"day": 4,
|
||||
"hour": 3,
|
||||
"minute": 2,
|
||||
"second": 12
|
||||
},
|
||||
{
|
||||
"year": 1024,
|
||||
"month": 5,
|
||||
"day": 10,
|
||||
"hour": 19,
|
||||
"minute": 31,
|
||||
"second": 2
|
||||
}
|
||||
]
|
|
@ -0,0 +1,16 @@
|
|||
// This file is part of ICU4X. For terms of use, please see the file
|
||||
// called LICENSE at the top level of the ICU4X source tree
|
||||
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
|
||||
|
||||
pub mod structs;
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::BufReader;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_dates_fixture() -> std::io::Result<structs::DateFixture> {
|
||||
let file = File::open("./benches/fixtures/datetimes.json")?;
|
||||
let reader = BufReader::new(file);
|
||||
|
||||
Ok(serde_json::from_reader(reader)?)
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
// This file is part of ICU4X. For terms of use, please see the file
|
||||
// called LICENSE at the top level of the ICU4X source tree
|
||||
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct DateFixture(pub Vec<Test>);
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Test {
|
||||
pub year: i32,
|
||||
pub month: u8,
|
||||
pub day: u8,
|
||||
pub hour: u8,
|
||||
pub minute: u8,
|
||||
pub second: u8,
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
// This file is part of ICU4X. For terms of use, please see the file
|
||||
// called LICENSE at the top level of the ICU4X source tree
|
||||
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use icu_calendar::{DateTime, Iso};
|
||||
|
||||
fn overview_bench(c: &mut Criterion) {
|
||||
c.bench_function("iso/from_minutes_since_local_unix_epoch", |b| {
|
||||
b.iter(|| {
|
||||
for i in -250..250 {
|
||||
let minutes = i * 10000;
|
||||
DateTime::<Iso>::from_minutes_since_local_unix_epoch(black_box(minutes));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(benches, overview_bench,);
|
||||
criterion_main!(benches);
|
|
@ -0,0 +1,62 @@
|
|||
// This file is part of ICU4X. For terms of use, please see the file
|
||||
// called LICENSE at the top level of the ICU4X source tree
|
||||
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
|
||||
|
||||
// An example application which uses icu_datetime to format entries
|
||||
// from a log into human readable dates and times.
|
||||
|
||||
#![no_main] // https://github.com/unicode-org/icu4x/issues/395
|
||||
|
||||
icu_benchmark_macros::static_setup!();
|
||||
|
||||
use icu_calendar::{Calendar, CalendarError, Date, Iso};
|
||||
|
||||
const DATES_ISO: &[(i32, u8, u8)] = &[
|
||||
(1970, 1, 1),
|
||||
(1982, 3, 11),
|
||||
(1999, 2, 21),
|
||||
(2000, 12, 29),
|
||||
(2001, 9, 8),
|
||||
(2017, 7, 12),
|
||||
(2020, 2, 29),
|
||||
(2021, 3, 21),
|
||||
(2021, 6, 10),
|
||||
(2021, 9, 2),
|
||||
(2022, 10, 8),
|
||||
(2022, 2, 9),
|
||||
(2033, 6, 10),
|
||||
];
|
||||
|
||||
fn print<A: Calendar>(_date_input: &Date<A>) {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
let formatted_date = format!(
|
||||
"Year: {}, Month: {}, Day: {}",
|
||||
_date_input.year().number,
|
||||
_date_input.month().ordinal,
|
||||
_date_input.day_of_month().0,
|
||||
);
|
||||
|
||||
println!("{formatted_date}");
|
||||
}
|
||||
}
|
||||
|
||||
fn tuple_to_iso_date(date: (i32, u8, u8)) -> Result<Date<Iso>, CalendarError> {
|
||||
Date::try_new_iso_date(date.0, date.1, date.2)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
||||
icu_benchmark_macros::main_setup!();
|
||||
|
||||
let dates = DATES_ISO
|
||||
.iter()
|
||||
.copied()
|
||||
.map(tuple_to_iso_date)
|
||||
.collect::<Result<Vec<Date<Iso>>, _>>()
|
||||
.expect("Failed to parse dates.");
|
||||
|
||||
dates.iter().map(print).for_each(drop);
|
||||
|
||||
0
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
// This file is part of ICU4X. For terms of use, please see the file
|
||||
// called LICENSE at the top level of the ICU4X source tree
|
||||
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
|
||||
|
||||
// An example application which uses icu_datetime to format entries
|
||||
// from a log into human readable dates and times.
|
||||
|
||||
#![no_main] // https://github.com/unicode-org/icu4x/issues/395
|
||||
|
||||
icu_benchmark_macros::static_setup!();
|
||||
|
||||
use icu_calendar::{Calendar, CalendarError, DateTime, Iso};
|
||||
|
||||
const DATETIMES_ISO: &[(i32, u8, u8, u8, u8, u8)] = &[
|
||||
(1970, 1, 1, 3, 5, 12),
|
||||
(1982, 3, 11, 2, 25, 59),
|
||||
(1999, 2, 21, 13, 12, 23),
|
||||
(2000, 12, 29, 10, 50, 23),
|
||||
(2001, 9, 8, 11, 5, 5),
|
||||
(2017, 7, 12, 3, 1, 1),
|
||||
(2020, 2, 29, 23, 12, 23),
|
||||
(2021, 3, 21, 18, 35, 34),
|
||||
(2021, 6, 10, 13, 12, 23),
|
||||
(2021, 9, 2, 5, 50, 22),
|
||||
(2022, 10, 8, 9, 45, 32),
|
||||
(2022, 2, 9, 10, 32, 45),
|
||||
(2033, 6, 10, 17, 22, 22),
|
||||
];
|
||||
|
||||
fn print<A: Calendar>(_datetime_input: &DateTime<A>) {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
let formatted_datetime = format!(
|
||||
"Year: {}, Month: {}, Day: {}, Hour: {}, Minute: {}, Second: {}",
|
||||
_datetime_input.date.year().number,
|
||||
_datetime_input.date.month().ordinal,
|
||||
_datetime_input.date.day_of_month().0,
|
||||
u8::from(_datetime_input.time.hour),
|
||||
u8::from(_datetime_input.time.minute),
|
||||
u8::from(_datetime_input.time.second),
|
||||
);
|
||||
|
||||
println!("{formatted_datetime}");
|
||||
}
|
||||
}
|
||||
|
||||
fn tuple_to_iso_datetime(date: (i32, u8, u8, u8, u8, u8)) -> Result<DateTime<Iso>, CalendarError> {
|
||||
DateTime::try_new_iso_datetime(date.0, date.1, date.2, date.3, date.4, date.5)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
||||
icu_benchmark_macros::main_setup!();
|
||||
|
||||
let datetimes = DATETIMES_ISO
|
||||
.iter()
|
||||
.copied()
|
||||
.map(tuple_to_iso_datetime)
|
||||
.collect::<Result<Vec<DateTime<Iso>>, _>>()
|
||||
.expect("Failed to parse datetimes.");
|
||||
|
||||
datetimes.iter().map(print).for_each(drop);
|
||||
|
||||
0
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,485 @@
|
|||
// This file is part of ICU4X. For terms of use, please see the file
|
||||
// called LICENSE at the top level of the ICU4X source tree
|
||||
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
|
||||
|
||||
//! This module contains types and implementations for the Buddhist calendar.
|
||||
//!
|
||||
//! ```rust
|
||||
//! use icu::calendar::{buddhist::Buddhist, Date, DateTime};
|
||||
//!
|
||||
//! // `Date` type
|
||||
//! let date_iso = Date::try_new_iso_date(1970, 1, 2)
|
||||
//! .expect("Failed to initialize ISO Date instance.");
|
||||
//! let date_buddhist = Date::new_from_iso(date_iso, Buddhist);
|
||||
//!
|
||||
//! // `DateTime` type
|
||||
//! let datetime_iso = DateTime::try_new_iso_datetime(1970, 1, 2, 13, 1, 0)
|
||||
//! .expect("Failed to initialize ISO DateTime instance.");
|
||||
//! let datetime_buddhist = DateTime::new_from_iso(datetime_iso, Buddhist);
|
||||
//!
|
||||
//! // `Date` checks
|
||||
//! assert_eq!(date_buddhist.year().number, 2513);
|
||||
//! assert_eq!(date_buddhist.month().ordinal, 1);
|
||||
//! assert_eq!(date_buddhist.day_of_month().0, 2);
|
||||
//!
|
||||
//! // `DateTime` type
|
||||
//! assert_eq!(datetime_buddhist.date.year().number, 2513);
|
||||
//! assert_eq!(datetime_buddhist.date.month().ordinal, 1);
|
||||
//! assert_eq!(datetime_buddhist.date.day_of_month().0, 2);
|
||||
//! assert_eq!(datetime_buddhist.time.hour.number(), 13);
|
||||
//! assert_eq!(datetime_buddhist.time.minute.number(), 1);
|
||||
//! assert_eq!(datetime_buddhist.time.second.number(), 0);
|
||||
//! ```
|
||||
|
||||
use crate::any_calendar::AnyCalendarKind;
|
||||
use crate::calendar_arithmetic::ArithmeticDate;
|
||||
use crate::iso::{Iso, IsoDateInner};
|
||||
use crate::{types, Calendar, CalendarError, Date, DateDuration, DateDurationUnit, DateTime};
|
||||
use tinystr::tinystr;
|
||||
|
||||
/// The number of years the Buddhist Era is ahead of C.E. by
|
||||
///
|
||||
/// (1 AD = 544 BE)
|
||||
const BUDDHIST_ERA_OFFSET: i32 = 543;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
/// The [Thai Solar Buddhist Calendar][cal]
|
||||
///
|
||||
/// The [Thai Solar Buddhist Calendar][cal] is a solar calendar used in Thailand, with twelve months.
|
||||
/// The months and days are identical to that of the Gregorian calendar, however the years are counted
|
||||
/// differently using the Buddhist Era.
|
||||
///
|
||||
/// This type can be used with [`Date`] or [`DateTime`] to represent dates in this calendar.
|
||||
///
|
||||
/// [cal]: https://en.wikipedia.org/wiki/Thai_solar_calendar
|
||||
///
|
||||
/// # Era codes
|
||||
///
|
||||
/// This calendar supports one era, `"be"`, with 1 B.E. being 543 BCE.
|
||||
///
|
||||
/// # Month codes
|
||||
///
|
||||
/// This calendar supports 12 solar month codes (`"M01" - "M12"`)
|
||||
#[allow(clippy::exhaustive_structs)] // this type is stable
|
||||
pub struct Buddhist;
|
||||
|
||||
impl Calendar for Buddhist {
|
||||
type DateInner = IsoDateInner;
|
||||
|
||||
fn date_from_codes(
|
||||
&self,
|
||||
era: types::Era,
|
||||
year: i32,
|
||||
month_code: types::MonthCode,
|
||||
day: u8,
|
||||
) -> Result<Self::DateInner, CalendarError> {
|
||||
if era.0 != tinystr!(16, "be") {
|
||||
return Err(CalendarError::UnknownEra(era.0, self.debug_name()));
|
||||
}
|
||||
let year = year - BUDDHIST_ERA_OFFSET;
|
||||
|
||||
ArithmeticDate::new_from_codes(self, year, month_code, day).map(IsoDateInner)
|
||||
}
|
||||
fn date_from_iso(&self, iso: Date<Iso>) -> IsoDateInner {
|
||||
*iso.inner()
|
||||
}
|
||||
|
||||
fn date_to_iso(&self, date: &Self::DateInner) -> Date<Iso> {
|
||||
Date::from_raw(*date, Iso)
|
||||
}
|
||||
|
||||
fn months_in_year(&self, date: &Self::DateInner) -> u8 {
|
||||
Iso.months_in_year(date)
|
||||
}
|
||||
|
||||
fn days_in_year(&self, date: &Self::DateInner) -> u16 {
|
||||
Iso.days_in_year(date)
|
||||
}
|
||||
|
||||
fn days_in_month(&self, date: &Self::DateInner) -> u8 {
|
||||
Iso.days_in_month(date)
|
||||
}
|
||||
|
||||
fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration<Self>) {
|
||||
Iso.offset_date(date, offset.cast_unit())
|
||||
}
|
||||
|
||||
#[allow(clippy::field_reassign_with_default)] // it's more clear this way
|
||||
fn until(
|
||||
&self,
|
||||
date1: &Self::DateInner,
|
||||
date2: &Self::DateInner,
|
||||
_calendar2: &Self,
|
||||
largest_unit: DateDurationUnit,
|
||||
smallest_unit: DateDurationUnit,
|
||||
) -> DateDuration<Self> {
|
||||
Iso.until(date1, date2, &Iso, largest_unit, smallest_unit)
|
||||
.cast_unit()
|
||||
}
|
||||
|
||||
/// The calendar-specific year represented by `date`
|
||||
fn year(&self, date: &Self::DateInner) -> types::FormattableYear {
|
||||
iso_year_as_buddhist(date.0.year)
|
||||
}
|
||||
|
||||
fn is_in_leap_year(&self, date: &Self::DateInner) -> bool {
|
||||
Iso.is_in_leap_year(date)
|
||||
}
|
||||
|
||||
/// The calendar-specific month represented by `date`
|
||||
fn month(&self, date: &Self::DateInner) -> types::FormattableMonth {
|
||||
Iso.month(date)
|
||||
}
|
||||
|
||||
/// The calendar-specific day-of-month represented by `date`
|
||||
fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth {
|
||||
Iso.day_of_month(date)
|
||||
}
|
||||
|
||||
/// Information of the day of the year
|
||||
fn day_of_year_info(&self, date: &Self::DateInner) -> types::DayOfYearInfo {
|
||||
let prev_year = date.0.year - 1;
|
||||
let next_year = date.0.year + 1;
|
||||
types::DayOfYearInfo {
|
||||
day_of_year: Iso::day_of_year(*date),
|
||||
days_in_year: Iso::days_in_year_direct(date.0.year),
|
||||
prev_year: iso_year_as_buddhist(prev_year),
|
||||
days_in_prev_year: Iso::days_in_year_direct(prev_year),
|
||||
next_year: iso_year_as_buddhist(next_year),
|
||||
}
|
||||
}
|
||||
|
||||
fn debug_name(&self) -> &'static str {
|
||||
"Buddhist"
|
||||
}
|
||||
|
||||
fn any_calendar_kind(&self) -> Option<AnyCalendarKind> {
|
||||
Some(AnyCalendarKind::Buddhist)
|
||||
}
|
||||
}
|
||||
|
||||
impl Date<Buddhist> {
|
||||
/// Construct a new Buddhist Date.
|
||||
///
|
||||
/// Years are specified as BE years.
|
||||
///
|
||||
/// ```rust
|
||||
/// use icu::calendar::Date;
|
||||
/// use std::convert::TryFrom;
|
||||
///
|
||||
/// let date_buddhist = Date::try_new_buddhist_date(1970, 1, 2)
|
||||
/// .expect("Failed to initialize Buddhist Date instance.");
|
||||
///
|
||||
/// assert_eq!(date_buddhist.year().number, 1970);
|
||||
/// assert_eq!(date_buddhist.month().ordinal, 1);
|
||||
/// assert_eq!(date_buddhist.day_of_month().0, 2);
|
||||
/// ```
|
||||
pub fn try_new_buddhist_date(
|
||||
year: i32,
|
||||
month: u8,
|
||||
day: u8,
|
||||
) -> Result<Date<Buddhist>, CalendarError> {
|
||||
Date::try_new_iso_date(year - BUDDHIST_ERA_OFFSET, month, day)
|
||||
.map(|d| Date::new_from_iso(d, Buddhist))
|
||||
}
|
||||
}
|
||||
|
||||
impl DateTime<Buddhist> {
|
||||
/// Construct a new Buddhist datetime from integers.
|
||||
///
|
||||
/// Years are specified as BE years.
|
||||
///
|
||||
/// ```rust
|
||||
/// use icu::calendar::DateTime;
|
||||
///
|
||||
/// let datetime_buddhist =
|
||||
/// DateTime::try_new_buddhist_datetime(1970, 1, 2, 13, 1, 0)
|
||||
/// .expect("Failed to initialize Buddhist DateTime instance.");
|
||||
///
|
||||
/// assert_eq!(datetime_buddhist.date.year().number, 1970);
|
||||
/// assert_eq!(datetime_buddhist.date.month().ordinal, 1);
|
||||
/// assert_eq!(datetime_buddhist.date.day_of_month().0, 2);
|
||||
/// assert_eq!(datetime_buddhist.time.hour.number(), 13);
|
||||
/// assert_eq!(datetime_buddhist.time.minute.number(), 1);
|
||||
/// assert_eq!(datetime_buddhist.time.second.number(), 0);
|
||||
/// ```
|
||||
pub fn try_new_buddhist_datetime(
|
||||
year: i32,
|
||||
month: u8,
|
||||
day: u8,
|
||||
hour: u8,
|
||||
minute: u8,
|
||||
second: u8,
|
||||
) -> Result<DateTime<Buddhist>, CalendarError> {
|
||||
Ok(DateTime {
|
||||
date: Date::try_new_buddhist_date(year, month, day)?,
|
||||
time: types::Time::try_new(hour, minute, second, 0)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn iso_year_as_buddhist(year: i32) -> types::FormattableYear {
|
||||
let buddhist_year = year + BUDDHIST_ERA_OFFSET;
|
||||
types::FormattableYear {
|
||||
era: types::Era(tinystr!(16, "be")),
|
||||
number: buddhist_year,
|
||||
cyclic: None,
|
||||
related_iso: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use calendrical_calculations::rata_die::RataDie;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_buddhist_roundtrip_near_rd_zero() {
|
||||
for i in -10000..=10000 {
|
||||
let rd = RataDie::new(i);
|
||||
let iso1 = Iso::iso_from_fixed(rd);
|
||||
let buddhist = iso1.to_calendar(Buddhist);
|
||||
let iso2 = buddhist.to_calendar(Iso);
|
||||
let result = Iso::fixed_from_iso(iso2.inner);
|
||||
assert_eq!(rd, result);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_buddhist_roundtrip_near_epoch() {
|
||||
// Buddhist epoch start RD: -198326
|
||||
for i in -208326..=-188326 {
|
||||
let rd = RataDie::new(i);
|
||||
let iso1 = Iso::iso_from_fixed(rd);
|
||||
let buddhist = iso1.to_calendar(Buddhist);
|
||||
let iso2 = buddhist.to_calendar(Iso);
|
||||
let result = Iso::fixed_from_iso(iso2.inner);
|
||||
assert_eq!(rd, result);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_buddhist_directionality_near_rd_zero() {
|
||||
for i in -100..=100 {
|
||||
for j in -100..=100 {
|
||||
let iso_i = Iso::iso_from_fixed(RataDie::new(i));
|
||||
let iso_j = Iso::iso_from_fixed(RataDie::new(j));
|
||||
|
||||
let buddhist_i = Date::new_from_iso(iso_i, Buddhist);
|
||||
let buddhist_j = Date::new_from_iso(iso_j, Buddhist);
|
||||
|
||||
assert_eq!(
|
||||
i.cmp(&j),
|
||||
iso_i.cmp(&iso_j),
|
||||
"ISO directionality inconsistent with directionality for i: {i}, j: {j}"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
i.cmp(&j),
|
||||
buddhist_i.cmp(&buddhist_j),
|
||||
"Buddhist directionality inconsistent with directionality for i: {i}, j: {j}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_buddhist_directionality_near_epoch() {
|
||||
// Buddhist epoch start RD: -198326
|
||||
for i in -198426..=-198226 {
|
||||
for j in -198426..=-198226 {
|
||||
let iso_i = Iso::iso_from_fixed(RataDie::new(i));
|
||||
let iso_j = Iso::iso_from_fixed(RataDie::new(j));
|
||||
|
||||
let buddhist_i = Date::new_from_iso(iso_i, Buddhist);
|
||||
let buddhist_j = Date::new_from_iso(iso_j, Buddhist);
|
||||
|
||||
assert_eq!(
|
||||
i.cmp(&j),
|
||||
iso_i.cmp(&iso_j),
|
||||
"ISO directionality inconsistent with directionality for i: {i}, j: {j}"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
i.cmp(&j),
|
||||
buddhist_i.cmp(&buddhist_j),
|
||||
"Buddhist directionality inconsistent with directionality for i: {i}, j: {j}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TestCase {
|
||||
iso_year: i32,
|
||||
iso_month: u8,
|
||||
iso_day: u8,
|
||||
buddhist_year: i32,
|
||||
buddhist_month: u8,
|
||||
buddhist_day: u8,
|
||||
}
|
||||
|
||||
fn check_test_case(case: TestCase) {
|
||||
let iso_year = case.iso_year;
|
||||
let iso_month = case.iso_month;
|
||||
let iso_day = case.iso_day;
|
||||
let buddhist_year = case.buddhist_year;
|
||||
let buddhist_month = case.buddhist_month;
|
||||
let buddhist_day = case.buddhist_day;
|
||||
|
||||
let iso1 = Date::try_new_iso_date(iso_year, iso_month, iso_day).unwrap();
|
||||
let buddhist1 = iso1.to_calendar(Buddhist);
|
||||
assert_eq!(
|
||||
buddhist1.year().number,
|
||||
buddhist_year,
|
||||
"Iso -> Buddhist year check failed for case: {case:?}"
|
||||
);
|
||||
assert_eq!(
|
||||
buddhist1.month().ordinal,
|
||||
buddhist_month as u32,
|
||||
"Iso -> Buddhist month check failed for case: {case:?}"
|
||||
);
|
||||
assert_eq!(
|
||||
buddhist1.day_of_month().0,
|
||||
buddhist_day as u32,
|
||||
"Iso -> Buddhist day check failed for case: {case:?}"
|
||||
);
|
||||
|
||||
let buddhist2 =
|
||||
Date::try_new_buddhist_date(buddhist_year, buddhist_month, buddhist_day).unwrap();
|
||||
let iso2 = buddhist2.to_calendar(Iso);
|
||||
assert_eq!(
|
||||
iso2.year().number,
|
||||
iso_year,
|
||||
"Buddhist -> Iso year check failed for case: {case:?}"
|
||||
);
|
||||
assert_eq!(
|
||||
iso2.month().ordinal,
|
||||
iso_month as u32,
|
||||
"Buddhist -> Iso month check failed for case: {case:?}"
|
||||
);
|
||||
assert_eq!(
|
||||
iso2.day_of_month().0,
|
||||
iso_day as u32,
|
||||
"Buddhist -> Iso day check failed for case: {case:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_buddhist_cases_near_rd_zero() {
|
||||
let cases = [
|
||||
TestCase {
|
||||
iso_year: -100,
|
||||
iso_month: 2,
|
||||
iso_day: 15,
|
||||
buddhist_year: 443,
|
||||
buddhist_month: 2,
|
||||
buddhist_day: 15,
|
||||
},
|
||||
TestCase {
|
||||
iso_year: -3,
|
||||
iso_month: 10,
|
||||
iso_day: 29,
|
||||
buddhist_year: 540,
|
||||
buddhist_month: 10,
|
||||
buddhist_day: 29,
|
||||
},
|
||||
TestCase {
|
||||
iso_year: 0,
|
||||
iso_month: 12,
|
||||
iso_day: 31,
|
||||
buddhist_year: 543,
|
||||
buddhist_month: 12,
|
||||
buddhist_day: 31,
|
||||
},
|
||||
TestCase {
|
||||
iso_year: 1,
|
||||
iso_month: 1,
|
||||
iso_day: 1,
|
||||
buddhist_year: 544,
|
||||
buddhist_month: 1,
|
||||
buddhist_day: 1,
|
||||
},
|
||||
TestCase {
|
||||
iso_year: 4,
|
||||
iso_month: 2,
|
||||
iso_day: 29,
|
||||
buddhist_year: 547,
|
||||
buddhist_month: 2,
|
||||
buddhist_day: 29,
|
||||
},
|
||||
];
|
||||
|
||||
for case in cases {
|
||||
check_test_case(case);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_buddhist_cases_near_epoch() {
|
||||
// 1 BE = 543 BCE = -542 ISO
|
||||
let cases = [
|
||||
TestCase {
|
||||
iso_year: -554,
|
||||
iso_month: 12,
|
||||
iso_day: 31,
|
||||
buddhist_year: -11,
|
||||
buddhist_month: 12,
|
||||
buddhist_day: 31,
|
||||
},
|
||||
TestCase {
|
||||
iso_year: -553,
|
||||
iso_month: 1,
|
||||
iso_day: 1,
|
||||
buddhist_year: -10,
|
||||
buddhist_month: 1,
|
||||
buddhist_day: 1,
|
||||
},
|
||||
TestCase {
|
||||
iso_year: -544,
|
||||
iso_month: 8,
|
||||
iso_day: 31,
|
||||
buddhist_year: -1,
|
||||
buddhist_month: 8,
|
||||
buddhist_day: 31,
|
||||
},
|
||||
TestCase {
|
||||
iso_year: -543,
|
||||
iso_month: 5,
|
||||
iso_day: 12,
|
||||
buddhist_year: 0,
|
||||
buddhist_month: 5,
|
||||
buddhist_day: 12,
|
||||
},
|
||||
TestCase {
|
||||
iso_year: -543,
|
||||
iso_month: 12,
|
||||
iso_day: 31,
|
||||
buddhist_year: 0,
|
||||
buddhist_month: 12,
|
||||
buddhist_day: 31,
|
||||
},
|
||||
TestCase {
|
||||
iso_year: -542,
|
||||
iso_month: 1,
|
||||
iso_day: 1,
|
||||
buddhist_year: 1,
|
||||
buddhist_month: 1,
|
||||
buddhist_day: 1,
|
||||
},
|
||||
TestCase {
|
||||
iso_year: -541,
|
||||
iso_month: 7,
|
||||
iso_day: 9,
|
||||
buddhist_year: 2,
|
||||
buddhist_month: 7,
|
||||
buddhist_day: 9,
|
||||
},
|
||||
];
|
||||
|
||||
for case in cases {
|
||||
check_test_case(case);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
// This file is part of ICU4X. For terms of use, please see the file
|
||||
// called LICENSE at the top level of the ICU4X source tree
|
||||
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
|
||||
|
||||
use crate::any_calendar::AnyCalendarKind;
|
||||
use crate::{types, CalendarError, Date, DateDuration, DateDurationUnit, Iso};
|
||||
use core::fmt;
|
||||
|
||||
/// A calendar implementation
|
||||
///
|
||||
/// Only implementors of [`Calendar`] should care about these methods, in general users of
|
||||
/// these calendars should use the methods on [`Date`] instead.
|
||||
///
|
||||
/// Individual [`Calendar`] implementations may have inherent utility methods
|
||||
/// allowing for direct construction, etc.
|
||||
///
|
||||
/// For ICU4X 1.0, implementing this trait or calling methods directly is considered
|
||||
/// unstable and prone to change, especially for `offset_date()` and `until()`.
|
||||
pub trait Calendar {
|
||||
/// The internal type used to represent dates
|
||||
type DateInner: PartialEq + Eq + Clone + fmt::Debug;
|
||||
/// Construct a date from era/month codes and fields
|
||||
fn date_from_codes(
|
||||
&self,
|
||||
era: types::Era,
|
||||
year: i32,
|
||||
month_code: types::MonthCode,
|
||||
day: u8,
|
||||
) -> Result<Self::DateInner, CalendarError>;
|
||||
/// Construct the date from an ISO date
|
||||
fn date_from_iso(&self, iso: Date<Iso>) -> Self::DateInner;
|
||||
/// Obtain an ISO date from this date
|
||||
fn date_to_iso(&self, date: &Self::DateInner) -> Date<Iso>;
|
||||
// fn validate_date(&self, e: Era, y: Year, m: MonthCode, d: Day) -> bool;
|
||||
// // similar validators for YearMonth, etc
|
||||
|
||||
// fn is_leap<A: AsCalendar<Calendar = Self>>(&self, date: &Date<A>) -> bool;
|
||||
/// Count the number of months in a given year, specified by providing a date
|
||||
/// from that year
|
||||
fn months_in_year(&self, date: &Self::DateInner) -> u8;
|
||||
/// Count the number of days in a given year, specified by providing a date
|
||||
/// from that year
|
||||
fn days_in_year(&self, date: &Self::DateInner) -> u16;
|
||||
/// Count the number of days in a given month, specified by providing a date
|
||||
/// from that year/month
|
||||
fn days_in_month(&self, date: &Self::DateInner) -> u8;
|
||||
/// Calculate the day of the week and return it
|
||||
fn day_of_week(&self, date: &Self::DateInner) -> types::IsoWeekday {
|
||||
self.date_to_iso(date).day_of_week()
|
||||
}
|
||||
// fn week_of_year(&self, date: &Self::DateInner) -> u8;
|
||||
|
||||
#[doc(hidden)] // unstable
|
||||
/// Add `offset` to `date`
|
||||
fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration<Self>);
|
||||
|
||||
#[doc(hidden)] // unstable
|
||||
/// Calculate `date2 - date` as a duration
|
||||
///
|
||||
/// `calendar2` is the calendar object associated with `date2`. In case the specific calendar objects
|
||||
/// differ on data, the data for the first calendar is used, and `date2` may be converted if necessary.
|
||||
fn until(
|
||||
&self,
|
||||
date1: &Self::DateInner,
|
||||
date2: &Self::DateInner,
|
||||
calendar2: &Self,
|
||||
largest_unit: DateDurationUnit,
|
||||
smallest_unit: DateDurationUnit,
|
||||
) -> DateDuration<Self>;
|
||||
|
||||
/// Obtain a name for the calendar for debug printing
|
||||
fn debug_name(&self) -> &'static str;
|
||||
// fn since(&self, from: &Date<Self>, to: &Date<Self>) -> Duration<Self>, Error;
|
||||
|
||||
/// The calendar-specific year represented by `date`
|
||||
fn year(&self, date: &Self::DateInner) -> types::FormattableYear;
|
||||
|
||||
/// Calculate if a date is in a leap year
|
||||
fn is_in_leap_year(&self, date: &Self::DateInner) -> bool;
|
||||
|
||||
/// The calendar-specific month represented by `date`
|
||||
fn month(&self, date: &Self::DateInner) -> types::FormattableMonth;
|
||||
|
||||
/// The calendar-specific day-of-month represented by `date`
|
||||
fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth;
|
||||
|
||||
/// Information of the day of the year
|
||||
fn day_of_year_info(&self, date: &Self::DateInner) -> types::DayOfYearInfo;
|
||||
|
||||
/// The [`AnyCalendarKind`] corresponding to this calendar,
|
||||
/// if one exists. Implementors outside of `icu_calendar` should return `None`
|
||||
fn any_calendar_kind(&self) -> Option<AnyCalendarKind> {
|
||||
None
|
||||
}
|
||||
}
|
|
@ -0,0 +1,336 @@
|
|||
// This file is part of ICU4X. For terms of use, please see the file
|
||||
// called LICENSE at the top level of the ICU4X source tree
|
||||
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
|
||||
|
||||
use crate::{types, Calendar, CalendarError, DateDuration, DateDurationUnit};
|
||||
use core::convert::TryInto;
|
||||
use core::marker::PhantomData;
|
||||
use tinystr::tinystr;
|
||||
|
||||
// Note: The Ord/PartialOrd impls can be derived because the fields are in the correct order.
|
||||
#[derive(Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
|
||||
#[allow(clippy::exhaustive_structs)] // this type is stable
|
||||
pub struct ArithmeticDate<C> {
|
||||
pub year: i32,
|
||||
/// 1-based month of year
|
||||
pub month: u8,
|
||||
/// 1-based day of month
|
||||
pub day: u8,
|
||||
marker: PhantomData<C>,
|
||||
}
|
||||
|
||||
impl<C> Copy for ArithmeticDate<C> {}
|
||||
impl<C> Clone for ArithmeticDate<C> {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
/// Maximum number of iterations when iterating through the days of a month; can be increased if necessary
|
||||
#[allow(dead_code)] // TODO: Remove dead code tag after use
|
||||
pub(crate) const MAX_ITERS_FOR_DAYS_OF_MONTH: u8 = 33;
|
||||
|
||||
pub trait CalendarArithmetic: Calendar {
|
||||
fn month_days(year: i32, month: u8) -> u8;
|
||||
fn months_for_every_year(year: i32) -> u8;
|
||||
fn is_leap_year(year: i32) -> bool;
|
||||
fn last_month_day_in_year(year: i32) -> (u8, u8);
|
||||
|
||||
/// Calculate the days in a given year
|
||||
/// Can be overridden with simpler implementations for solar calendars
|
||||
/// (typically, 366 in leap, 365 otherwise) Leave this as the default
|
||||
/// for lunar calendars
|
||||
///
|
||||
/// The name has `provided` in it to avoid clashes with Calendar
|
||||
fn days_in_provided_year(year: i32) -> u16 {
|
||||
let months_in_year = Self::months_for_every_year(year);
|
||||
let mut days: u16 = 0;
|
||||
for month in 1..=months_in_year {
|
||||
days += Self::month_days(year, month) as u16;
|
||||
}
|
||||
days
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: CalendarArithmetic> ArithmeticDate<C> {
|
||||
/// Create a new `ArithmeticDate` without checking that `month` and `day` are in bounds.
|
||||
#[inline]
|
||||
pub const fn new_unchecked(year: i32, month: u8, day: u8) -> Self {
|
||||
ArithmeticDate {
|
||||
year,
|
||||
month,
|
||||
day,
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn min_date() -> Self {
|
||||
ArithmeticDate {
|
||||
year: i32::MIN,
|
||||
month: 1,
|
||||
day: 1,
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn max_date() -> Self {
|
||||
let year = i32::MAX;
|
||||
let (month, day) = C::last_month_day_in_year(year);
|
||||
ArithmeticDate {
|
||||
year: i32::MAX,
|
||||
month,
|
||||
day,
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn offset_days(&mut self, mut day_offset: i32) {
|
||||
while day_offset != 0 {
|
||||
let month_days = C::month_days(self.year, self.month);
|
||||
if self.day as i32 + day_offset > month_days as i32 {
|
||||
self.offset_months(1);
|
||||
day_offset -= month_days as i32;
|
||||
} else if self.day as i32 + day_offset < 1 {
|
||||
self.offset_months(-1);
|
||||
day_offset += C::month_days(self.year, self.month) as i32;
|
||||
} else {
|
||||
self.day = (self.day as i32 + day_offset) as u8;
|
||||
day_offset = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn offset_months(&mut self, mut month_offset: i32) {
|
||||
while month_offset != 0 {
|
||||
let year_months = C::months_for_every_year(self.year);
|
||||
if self.month as i32 + month_offset > year_months as i32 {
|
||||
self.year += 1;
|
||||
month_offset -= year_months as i32;
|
||||
} else if self.month as i32 + month_offset < 1 {
|
||||
self.year -= 1;
|
||||
month_offset += C::months_for_every_year(self.year) as i32;
|
||||
} else {
|
||||
self.month = (self.month as i32 + month_offset) as u8;
|
||||
month_offset = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn offset_date(&mut self, offset: DateDuration<C>) {
|
||||
// For offset_date to work with lunar calendars, need to handle an edge case where the original month is not valid in the future year.
|
||||
self.year += offset.years;
|
||||
|
||||
self.offset_months(offset.months);
|
||||
|
||||
let day_offset = offset.days + offset.weeks * 7 + self.day as i32 - 1;
|
||||
self.day = 1;
|
||||
self.offset_days(day_offset);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn until(
|
||||
&self,
|
||||
date2: ArithmeticDate<C>,
|
||||
_largest_unit: DateDurationUnit,
|
||||
_smaller_unit: DateDurationUnit,
|
||||
) -> DateDuration<C> {
|
||||
DateDuration::new(
|
||||
self.year - date2.year,
|
||||
self.month as i32 - date2.month as i32,
|
||||
0,
|
||||
self.day as i32 - date2.day as i32,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn days_in_year(&self) -> u16 {
|
||||
C::days_in_provided_year(self.year)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn months_in_year(&self) -> u8 {
|
||||
C::months_for_every_year(self.year)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn days_in_month(&self) -> u8 {
|
||||
C::month_days(self.year, self.month)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn day_of_year(&self) -> u16 {
|
||||
let mut day_of_year = 0;
|
||||
for month in 1..self.month {
|
||||
day_of_year += C::month_days(self.year, month) as u16;
|
||||
}
|
||||
day_of_year + (self.day as u16)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn date_from_year_day(year: i32, year_day: u32) -> ArithmeticDate<C> {
|
||||
let mut month = 1;
|
||||
let mut day = year_day as i32;
|
||||
while month <= C::months_for_every_year(year) {
|
||||
let month_days = C::month_days(year, month) as i32;
|
||||
if day <= month_days {
|
||||
break;
|
||||
} else {
|
||||
day -= month_days;
|
||||
month += 1;
|
||||
}
|
||||
}
|
||||
|
||||
debug_assert!(day <= C::month_days(year, month) as i32);
|
||||
#[allow(clippy::unwrap_used)]
|
||||
// The day is expected to be within the range of month_days of C
|
||||
ArithmeticDate {
|
||||
year,
|
||||
month,
|
||||
day: day.try_into().unwrap_or(0),
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn day_of_month(&self) -> types::DayOfMonth {
|
||||
types::DayOfMonth(self.day.into())
|
||||
}
|
||||
|
||||
/// The [`types::FormattableMonth`] for the current month (with month code) for a solar calendar
|
||||
/// Lunar calendars should not use this method and instead manually implement a month code
|
||||
/// resolver.
|
||||
/// Originally "solar_month" but renamed because it can be used for some lunar calendars
|
||||
///
|
||||
/// Returns "und" if run with months that are out of bounds for the current
|
||||
/// calendar.
|
||||
#[inline]
|
||||
pub fn month(&self) -> types::FormattableMonth {
|
||||
let code = match self.month {
|
||||
a if a > C::months_for_every_year(self.year) => tinystr!(4, "und"),
|
||||
1 => tinystr!(4, "M01"),
|
||||
2 => tinystr!(4, "M02"),
|
||||
3 => tinystr!(4, "M03"),
|
||||
4 => tinystr!(4, "M04"),
|
||||
5 => tinystr!(4, "M05"),
|
||||
6 => tinystr!(4, "M06"),
|
||||
7 => tinystr!(4, "M07"),
|
||||
8 => tinystr!(4, "M08"),
|
||||
9 => tinystr!(4, "M09"),
|
||||
10 => tinystr!(4, "M10"),
|
||||
11 => tinystr!(4, "M11"),
|
||||
12 => tinystr!(4, "M12"),
|
||||
13 => tinystr!(4, "M13"),
|
||||
_ => tinystr!(4, "und"),
|
||||
};
|
||||
types::FormattableMonth {
|
||||
ordinal: self.month as u32,
|
||||
code: types::MonthCode(code),
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a new arithmetic date from a year, month code, and day, bounds checking
|
||||
/// the month and day
|
||||
/// Originally (new_from_solar_codes) but renamed because it works for some lunar calendars
|
||||
pub fn new_from_codes<C2: Calendar>(
|
||||
// Separate type since the debug_name() impl may differ when DateInner types
|
||||
// are nested (e.g. in GregorianDateInner)
|
||||
cal: &C2,
|
||||
year: i32,
|
||||
month_code: types::MonthCode,
|
||||
day: u8,
|
||||
) -> Result<Self, CalendarError> {
|
||||
let month = if let Some((ordinal, false)) = month_code.parsed() {
|
||||
ordinal
|
||||
} else {
|
||||
return Err(CalendarError::UnknownMonthCode(
|
||||
month_code.0,
|
||||
cal.debug_name(),
|
||||
));
|
||||
};
|
||||
|
||||
if month > C::months_for_every_year(year) {
|
||||
return Err(CalendarError::UnknownMonthCode(
|
||||
month_code.0,
|
||||
cal.debug_name(),
|
||||
));
|
||||
}
|
||||
|
||||
let max_day = C::month_days(year, month);
|
||||
if day > max_day {
|
||||
return Err(CalendarError::Overflow {
|
||||
field: "day",
|
||||
max: max_day as usize,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(Self::new_unchecked(year, month, day))
|
||||
}
|
||||
|
||||
/// Construct a new arithmetic date from a year, month ordinal, and day, bounds checking
|
||||
/// the month and day
|
||||
/// Originally (new_from_solar_ordinals) but renamed because it works for some lunar calendars
|
||||
pub fn new_from_ordinals(year: i32, month: u8, day: u8) -> Result<Self, CalendarError> {
|
||||
let max_month = C::months_for_every_year(year);
|
||||
if month > max_month {
|
||||
return Err(CalendarError::Overflow {
|
||||
field: "month",
|
||||
max: max_month as usize,
|
||||
});
|
||||
}
|
||||
let max_day = C::month_days(year, month);
|
||||
if day > max_day {
|
||||
return Err(CalendarError::Overflow {
|
||||
field: "day",
|
||||
max: max_day as usize,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(Self::new_unchecked(year, month, day))
|
||||
}
|
||||
|
||||
/// This fn currently just calls [`new_from_ordinals`], but exists separately for
|
||||
/// lunar calendars in case different logic needs to be implemented later.
|
||||
pub fn new_from_lunar_ordinals(year: i32, month: u8, day: u8) -> Result<Self, CalendarError> {
|
||||
Self::new_from_ordinals(year, month, day)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::Iso;
|
||||
|
||||
#[test]
|
||||
fn test_ord() {
|
||||
let dates_in_order = [
|
||||
ArithmeticDate::<Iso>::new_unchecked(-10, 1, 1),
|
||||
ArithmeticDate::<Iso>::new_unchecked(-10, 1, 2),
|
||||
ArithmeticDate::<Iso>::new_unchecked(-10, 2, 1),
|
||||
ArithmeticDate::<Iso>::new_unchecked(-1, 1, 1),
|
||||
ArithmeticDate::<Iso>::new_unchecked(-1, 1, 2),
|
||||
ArithmeticDate::<Iso>::new_unchecked(-1, 2, 1),
|
||||
ArithmeticDate::<Iso>::new_unchecked(0, 1, 1),
|
||||
ArithmeticDate::<Iso>::new_unchecked(0, 1, 2),
|
||||
ArithmeticDate::<Iso>::new_unchecked(0, 2, 1),
|
||||
ArithmeticDate::<Iso>::new_unchecked(1, 1, 1),
|
||||
ArithmeticDate::<Iso>::new_unchecked(1, 1, 2),
|
||||
ArithmeticDate::<Iso>::new_unchecked(1, 2, 1),
|
||||
ArithmeticDate::<Iso>::new_unchecked(10, 1, 1),
|
||||
ArithmeticDate::<Iso>::new_unchecked(10, 1, 2),
|
||||
ArithmeticDate::<Iso>::new_unchecked(10, 2, 1),
|
||||
];
|
||||
for (i, i_date) in dates_in_order.iter().enumerate() {
|
||||
for (j, j_date) in dates_in_order.iter().enumerate() {
|
||||
let result1 = i_date.cmp(j_date);
|
||||
let result2 = j_date.cmp(i_date);
|
||||
assert_eq!(result1.reverse(), result2);
|
||||
assert_eq!(i.cmp(&j), i_date.cmp(j_date));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,563 @@
|
|||
// This file is part of ICU4X. For terms of use, please see the file
|
||||
// called LICENSE at the top level of the ICU4X source tree
|
||||
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
|
||||
|
||||
//! This module contains types and traits for use in the Chinese traditional lunar calendar,
|
||||
//! as well as in related and derived calendars such as the Korean and Vietnamese lunar calendars.
|
||||
//!
|
||||
//! ```rust
|
||||
//! use icu::calendar::{chinese::Chinese, Date, Iso};
|
||||
//!
|
||||
//! let iso_date = Date::try_new_iso_date(2023, 6, 23).unwrap();
|
||||
//! let chinese_date =
|
||||
//! Date::new_from_iso(iso_date, Chinese::new_always_calculating());
|
||||
//!
|
||||
//! assert_eq!(chinese_date.year().number, 4660);
|
||||
//! assert_eq!(chinese_date.year().related_iso, Some(2023));
|
||||
//! assert_eq!(chinese_date.year().cyclic.unwrap().get(), 40);
|
||||
//! assert_eq!(chinese_date.month().ordinal, 6);
|
||||
//! assert_eq!(chinese_date.day_of_month().0, 6);
|
||||
//! ```
|
||||
|
||||
use crate::{
|
||||
calendar_arithmetic::{ArithmeticDate, CalendarArithmetic},
|
||||
types::MonthCode,
|
||||
CalendarError, Iso,
|
||||
};
|
||||
|
||||
use calendrical_calculations::chinese_based::{self, ChineseBased, YearBounds};
|
||||
use calendrical_calculations::rata_die::RataDie;
|
||||
use core::num::NonZeroU8;
|
||||
|
||||
/// The trait ChineseBased is used by Chinese-based calendars to perform computations shared by such calendar.
|
||||
///
|
||||
/// For an example of how to use this trait, see `impl ChineseBasedWithDataLoading for Chinese` in [`Chinese`].
|
||||
pub(crate) trait ChineseBasedWithDataLoading: CalendarArithmetic {
|
||||
type CB: ChineseBased;
|
||||
/// Get the compiled const data for a ChineseBased calendar; can return `None` if the given year
|
||||
/// does not correspond to any compiled data.
|
||||
fn get_compiled_data_for_year(extended_year: i32) -> Option<ChineseBasedCompiledData>;
|
||||
}
|
||||
|
||||
/// Chinese-based calendars define DateInner as a calendar-specific struct wrapping ChineseBasedDateInner.
|
||||
#[derive(Debug, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub(crate) struct ChineseBasedDateInner<C>(
|
||||
pub(crate) ArithmeticDate<C>,
|
||||
pub(crate) ChineseBasedYearInfo,
|
||||
);
|
||||
|
||||
// we want these impls without the `C: Copy/Clone` bounds
|
||||
impl<C> Copy for ChineseBasedDateInner<C> {}
|
||||
impl<C> Clone for ChineseBasedDateInner<C> {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
/// A `ChineseBasedDateInner` has additional information about the year corresponding to the Inner;
|
||||
/// if there is available data for that year, the ChineseBasedYearInfo will be in the form of `Data`,
|
||||
/// with a `ChineseBasedCompiledData` struct which contains more information; otherwise, a `Cache`
|
||||
/// with a `ChineseBasedCache`, which contains less information, but is faster to compute.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub(crate) enum ChineseBasedYearInfo {
|
||||
Cache(ChineseBasedCache),
|
||||
Data(ChineseBasedCompiledData),
|
||||
}
|
||||
|
||||
impl ChineseBasedYearInfo {
|
||||
pub(crate) fn get_new_year(&self) -> RataDie {
|
||||
match self {
|
||||
Self::Cache(cache) => cache.new_year,
|
||||
Self::Data(data) => data.new_year,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_next_new_year(&self) -> RataDie {
|
||||
match self {
|
||||
Self::Cache(cache) => cache.next_new_year,
|
||||
Self::Data(data) => data.next_new_year(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_leap_month(&self) -> Option<NonZeroU8> {
|
||||
match self {
|
||||
Self::Cache(cache) => cache.leap_month,
|
||||
Self::Data(data) => data.leap_month,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_year_info<C: ChineseBasedWithDataLoading>(year: i32) -> ChineseBasedYearInfo {
|
||||
if let Some(data) = C::get_compiled_data_for_year(year) {
|
||||
Self::Data(data)
|
||||
} else {
|
||||
Self::Cache(ChineseBasedDateInner::<C>::compute_cache(year))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A caching struct used to store information for ChineseBasedDates
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub(crate) struct ChineseBasedCache {
|
||||
pub(crate) new_year: RataDie,
|
||||
pub(crate) next_new_year: RataDie,
|
||||
pub(crate) leap_month: Option<NonZeroU8>,
|
||||
}
|
||||
|
||||
/// The struct containing compiled ChineseData
|
||||
///
|
||||
/// Bit structure:
|
||||
///
|
||||
/// ```text
|
||||
/// Bit: 7 6 5 4 3 2 1 0
|
||||
/// Byte 0: [new year offset] | [ month lengths ..
|
||||
/// Byte 1: ....... month lengths .......
|
||||
/// Byte 2: ... ] | [ leap month index ]
|
||||
/// ```
|
||||
///
|
||||
/// Where the New Year Offset is the offset from ISO Jan 21 of that year for Chinese New Year,
|
||||
/// the month lengths are stored as 1 = 30, 0 = 29 for each month including the leap month.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub(crate) struct PackedChineseBasedCompiledData(pub(crate) u8, pub(crate) u8, pub(crate) u8);
|
||||
|
||||
impl PackedChineseBasedCompiledData {
|
||||
pub(crate) fn unpack(self, related_iso: i32) -> ChineseBasedCompiledData {
|
||||
fn month_length(is_long: bool) -> u16 {
|
||||
if is_long {
|
||||
30
|
||||
} else {
|
||||
29
|
||||
}
|
||||
}
|
||||
|
||||
let new_year_offset = ((self.0 & 0b11111000) >> 3) as u16;
|
||||
let new_year =
|
||||
Iso::fixed_from_iso(Iso::iso_from_year_day(related_iso, 21 + new_year_offset).inner);
|
||||
|
||||
let mut last_day_of_month: [u16; 13] = [0; 13];
|
||||
let mut months_total = 0;
|
||||
|
||||
months_total += month_length(self.0 & 0b100 != 0);
|
||||
last_day_of_month[0] = months_total;
|
||||
months_total += month_length(self.0 & 0b010 != 0);
|
||||
last_day_of_month[1] = months_total;
|
||||
months_total += month_length(self.0 & 0b001 != 0);
|
||||
last_day_of_month[2] = months_total;
|
||||
months_total += month_length(self.1 & 0b10000000 != 0);
|
||||
last_day_of_month[3] = months_total;
|
||||
months_total += month_length(self.1 & 0b01000000 != 0);
|
||||
last_day_of_month[4] = months_total;
|
||||
months_total += month_length(self.1 & 0b00100000 != 0);
|
||||
last_day_of_month[5] = months_total;
|
||||
months_total += month_length(self.1 & 0b00010000 != 0);
|
||||
last_day_of_month[6] = months_total;
|
||||
months_total += month_length(self.1 & 0b00001000 != 0);
|
||||
last_day_of_month[7] = months_total;
|
||||
months_total += month_length(self.1 & 0b00000100 != 0);
|
||||
last_day_of_month[8] = months_total;
|
||||
months_total += month_length(self.1 & 0b00000010 != 0);
|
||||
last_day_of_month[9] = months_total;
|
||||
months_total += month_length(self.1 & 0b00000001 != 0);
|
||||
last_day_of_month[10] = months_total;
|
||||
months_total += month_length(self.2 & 0b10000000 != 0);
|
||||
last_day_of_month[11] = months_total;
|
||||
|
||||
let leap_month_bits = self.2 & 0b00111111;
|
||||
// Leap month is if the sentinel bit is set
|
||||
if leap_month_bits != 0 {
|
||||
months_total += month_length(self.2 & 0b01000000 != 0);
|
||||
}
|
||||
// In non-leap months, `last_day_of_month` will have identical entries at 12 and 11
|
||||
last_day_of_month[12] = months_total;
|
||||
|
||||
// Will automatically set to None when the leap month bits are zero
|
||||
let leap_month = NonZeroU8::new(leap_month_bits);
|
||||
|
||||
ChineseBasedCompiledData {
|
||||
new_year,
|
||||
last_day_of_month,
|
||||
leap_month,
|
||||
}
|
||||
}
|
||||
}
|
||||
/// A data struct used to load and use information for a set of ChineseBasedDates
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub(crate) struct ChineseBasedCompiledData {
|
||||
pub(crate) new_year: RataDie,
|
||||
/// last_day_of_month[12] = last_day_of_month[11] in non-leap years
|
||||
/// These days are 1-indexed: so the last day of month for a 30-day 一月 is 30
|
||||
/// The array itself is zero-indexed, be careful passing it self.0.month!
|
||||
last_day_of_month: [u16; 13],
|
||||
///
|
||||
pub(crate) leap_month: Option<NonZeroU8>,
|
||||
}
|
||||
|
||||
impl ChineseBasedCompiledData {
|
||||
fn next_new_year(self) -> RataDie {
|
||||
self.new_year + i64::from(self.last_day_of_month[12])
|
||||
}
|
||||
|
||||
/// The last day of year in the previous month.
|
||||
/// `month` is 1-indexed, and the returned value is also
|
||||
/// a 1-indexed day of year
|
||||
///
|
||||
/// Will be zero for the first month as the last day of the previous month
|
||||
/// is not in this year
|
||||
fn last_day_of_previous_month(self, month: u8) -> u16 {
|
||||
debug_assert!((1..=13).contains(&month), "Month out of bounds!");
|
||||
// Get the last day of the previous month.
|
||||
// Since `month` is 1-indexed, this needs to subtract *two* to get to the right index of the array
|
||||
if month < 2 {
|
||||
0
|
||||
} else {
|
||||
self.last_day_of_month
|
||||
.get(usize::from(month - 2))
|
||||
.copied()
|
||||
.unwrap_or(0)
|
||||
}
|
||||
}
|
||||
|
||||
/// The last day of year in the current month.
|
||||
/// `month` is 1-indexed, and the returned value is also
|
||||
/// a 1-indexed day of year
|
||||
///
|
||||
/// Will be zero for the first month as the last day of the previous month
|
||||
/// is not in this year
|
||||
fn last_day_of_month(self, month: u8) -> u16 {
|
||||
debug_assert!((1..=13).contains(&month), "Month out of bounds!");
|
||||
// Get the last day of the previous month.
|
||||
// Since `month` is 1-indexed, this needs to subtract one
|
||||
self.last_day_of_month
|
||||
.get(usize::from(month - 1))
|
||||
.copied()
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
fn days_in_month(self, month: u8) -> u8 {
|
||||
let ret =
|
||||
u8::try_from(self.last_day_of_month(month) - self.last_day_of_previous_month(month));
|
||||
debug_assert!(ret.is_ok(), "Month too big!");
|
||||
ret.unwrap_or(30)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: ChineseBasedWithDataLoading> ChineseBasedDateInner<C> {
|
||||
/// Given a 1-indexed chinese extended year, fetch its data from the cache.
|
||||
///
|
||||
/// If the actual year data that was fetched is for a different year, update the getter year
|
||||
fn get_compiled_data_for_year_helper(
|
||||
date: RataDie,
|
||||
getter_year: &mut i32,
|
||||
) -> Option<ChineseBasedCompiledData> {
|
||||
let data_option = C::get_compiled_data_for_year(*getter_year);
|
||||
// todo we should be able to do this without unpacking
|
||||
if let Some(data) = data_option {
|
||||
if date < data.new_year {
|
||||
*getter_year -= 1;
|
||||
C::get_compiled_data_for_year(*getter_year)
|
||||
} else if date >= data.next_new_year() {
|
||||
*getter_year += 1;
|
||||
C::get_compiled_data_for_year(*getter_year)
|
||||
} else {
|
||||
data_option
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a ChineseBasedDateInner from a fixed date and the cache/extended year associated with it
|
||||
fn chinese_based_date_from_cached(
|
||||
date: RataDie,
|
||||
data: ChineseBasedCompiledData,
|
||||
extended_year: i32,
|
||||
) -> ChineseBasedDateInner<C> {
|
||||
debug_assert!(
|
||||
date < data.next_new_year(),
|
||||
"Stored date {date:?} out of bounds!"
|
||||
);
|
||||
// 1-indexed day of year
|
||||
let day_of_year = u16::try_from(date - data.new_year + 1);
|
||||
debug_assert!(day_of_year.is_ok(), "Somehow got a very large year in data");
|
||||
let day_of_year = day_of_year.unwrap_or(1);
|
||||
let mut month = 1;
|
||||
// todo perhaps use a binary search
|
||||
for iter_month in 1..=13 {
|
||||
month = iter_month;
|
||||
if data.last_day_of_month(iter_month) >= day_of_year {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
debug_assert!((1..=13).contains(&month), "Month out of bounds!");
|
||||
|
||||
debug_assert!(
|
||||
month < 13 || data.leap_month.is_some(),
|
||||
"Cannot have 13 months in a non-leap year!"
|
||||
);
|
||||
let day_before_month_start = data.last_day_of_previous_month(month);
|
||||
let day_of_month = day_of_year - day_before_month_start;
|
||||
let day_of_month = u8::try_from(day_of_month);
|
||||
debug_assert!(day_of_month.is_ok(), "Month too big!");
|
||||
let day_of_month = day_of_month.unwrap_or(1);
|
||||
|
||||
// This can use `new_unchecked` because this function is only ever called from functions which
|
||||
// generate the year, month, and day; therefore, there should never be a situation where
|
||||
// creating this ArithmeticDate would fail, since the same algorithms used to generate the ymd
|
||||
// are also used to check for valid ymd.
|
||||
ChineseBasedDateInner(
|
||||
ArithmeticDate::new_unchecked(extended_year, month, day_of_month),
|
||||
ChineseBasedYearInfo::Data(data),
|
||||
)
|
||||
}
|
||||
|
||||
/// Get a ChineseBasedDateInner from a fixed date, with the related ISO year
|
||||
pub(crate) fn chinese_based_date_from_fixed(
|
||||
date: RataDie,
|
||||
iso_year: i32,
|
||||
) -> ChineseBasedDateInner<C> {
|
||||
// Get the 1-indexed Chinese extended year, used for fetching data from the cache
|
||||
let epoch_as_iso = Iso::iso_from_fixed(C::CB::EPOCH);
|
||||
let mut getter_year = iso_year - epoch_as_iso.year().number + 1;
|
||||
|
||||
let data_option = Self::get_compiled_data_for_year_helper(date, &mut getter_year);
|
||||
|
||||
if let Some(data) = data_option {
|
||||
// cache fetch successful, getter year is just the regular extended year
|
||||
Self::chinese_based_date_from_cached(date, data, getter_year)
|
||||
} else {
|
||||
let date = chinese_based::chinese_based_date_from_fixed::<C::CB>(date);
|
||||
|
||||
let cache = ChineseBasedCache {
|
||||
new_year: date.year_bounds.new_year,
|
||||
next_new_year: date.year_bounds.next_new_year,
|
||||
leap_month: date.leap_month,
|
||||
};
|
||||
|
||||
// This can use `new_unchecked` because this function is only ever called from functions which
|
||||
// generate the year, month, and day; therefore, there should never be a situation where
|
||||
// creating this ArithmeticDate would fail, since the same algorithms used to generate the ymd
|
||||
// are also used to check for valid ymd.
|
||||
ChineseBasedDateInner(
|
||||
ArithmeticDate::new_unchecked(date.year, date.month, date.day),
|
||||
ChineseBasedYearInfo::Cache(cache),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a RataDie from a ChineseBasedDateInner
|
||||
///
|
||||
/// This finds the RataDie of the new year of the year given, then finds the RataDie of the new moon
|
||||
/// (beginning of the month) of the month given, then adds the necessary number of days.
|
||||
pub(crate) fn fixed_from_chinese_based_date_inner(date: ChineseBasedDateInner<C>) -> RataDie {
|
||||
let first_day_of_year = date.1.get_new_year();
|
||||
let day_of_year = date.day_of_year(); // 1 indexed
|
||||
first_day_of_year + i64::from(day_of_year) - 1
|
||||
}
|
||||
|
||||
/// Create a new arithmetic date from a year, month ordinal, and day with bounds checking; returns the
|
||||
/// result of creating this arithmetic date, as well as a ChineseBasedYearInfo - either the one passed in
|
||||
/// optionally as an argument, or a new ChineseBasedYearInfo for the given year, month, and day args.
|
||||
pub(crate) fn new_from_ordinals(
|
||||
year: i32,
|
||||
month: u8,
|
||||
day: u8,
|
||||
year_info: &ChineseBasedYearInfo,
|
||||
) -> Result<ArithmeticDate<C>, CalendarError> {
|
||||
let max_month = Self::months_in_year_with_info(year_info);
|
||||
if !(1..=max_month).contains(&month) {
|
||||
return Err(CalendarError::Overflow {
|
||||
field: "month",
|
||||
max: max_month as usize,
|
||||
});
|
||||
}
|
||||
|
||||
let max_day = if let ChineseBasedYearInfo::Data(data) = year_info {
|
||||
data.days_in_month(month)
|
||||
} else {
|
||||
chinese_based::days_in_month::<C::CB>(month, year_info.get_new_year(), None).0
|
||||
};
|
||||
if day > max_day {
|
||||
return Err(CalendarError::Overflow {
|
||||
field: "day",
|
||||
max: max_day as usize,
|
||||
});
|
||||
}
|
||||
|
||||
// Unchecked can be used because month and day are already checked in this fn
|
||||
|
||||
Ok(ArithmeticDate::<C>::new_unchecked(year, month, day))
|
||||
}
|
||||
|
||||
/// Call `months_in_year_with_info` on a `ChineseBasedDateInner`
|
||||
pub(crate) fn months_in_year_inner(&self) -> u8 {
|
||||
Self::months_in_year_with_info(&self.1)
|
||||
}
|
||||
|
||||
/// Return the number of months in a given year, which is 13 in a leap year, and 12 in a common year.
|
||||
/// Also takes a `ChineseBasedCache` argument.
|
||||
fn months_in_year_with_info(year_info: &ChineseBasedYearInfo) -> u8 {
|
||||
if year_info.get_leap_month().is_some() {
|
||||
13
|
||||
} else {
|
||||
12
|
||||
}
|
||||
}
|
||||
|
||||
/// Calls `days_in_month` on an instance of ChineseBasedDateInner
|
||||
pub(crate) fn days_in_month_inner(&self) -> u8 {
|
||||
if let ChineseBasedYearInfo::Data(data) = self.1 {
|
||||
data.days_in_month(self.0.month)
|
||||
} else {
|
||||
chinese_based::days_in_month::<C::CB>(self.0.month, self.1.get_new_year(), None).0
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn fixed_mid_year_from_year(year: i32) -> RataDie {
|
||||
chinese_based::fixed_mid_year_from_year::<C::CB>(year)
|
||||
}
|
||||
|
||||
/// Calls days_in_year on an instance of ChineseBasedDateInner
|
||||
pub(crate) fn days_in_year_inner(&self) -> u16 {
|
||||
let next_new_year = self.1.get_next_new_year();
|
||||
let new_year = self.1.get_new_year();
|
||||
YearBounds {
|
||||
new_year,
|
||||
next_new_year,
|
||||
}
|
||||
.count_days()
|
||||
}
|
||||
|
||||
/// Calculate the number of days in the year so far for a ChineseBasedDate;
|
||||
/// similar to `CalendarArithmetic::day_of_year`
|
||||
pub(crate) fn day_of_year(&self) -> u16 {
|
||||
let days_until_month = if let ChineseBasedYearInfo::Data(data) = self.1 {
|
||||
data.last_day_of_previous_month(self.0.month)
|
||||
} else {
|
||||
let new_year = self.1.get_new_year();
|
||||
chinese_based::days_until_month::<C::CB>(new_year, self.0.month)
|
||||
};
|
||||
days_until_month + u16::from(self.0.day)
|
||||
}
|
||||
|
||||
/// Compute a `ChineseBasedCache` from a ChineseBased year
|
||||
pub(crate) fn compute_cache(year: i32) -> ChineseBasedCache {
|
||||
let mid_year = Self::fixed_mid_year_from_year(year);
|
||||
let year_bounds = YearBounds::compute::<C::CB>(mid_year);
|
||||
let YearBounds {
|
||||
new_year,
|
||||
next_new_year,
|
||||
..
|
||||
} = year_bounds;
|
||||
let is_leap_year = year_bounds.is_leap();
|
||||
let leap_month = if is_leap_year {
|
||||
// This doesn't need to be checked for None because `get_leap_month_from_new_year`
|
||||
// will always return a value between 1..=13
|
||||
NonZeroU8::new(chinese_based::get_leap_month_from_new_year::<C::CB>(
|
||||
new_year,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
ChineseBasedCache {
|
||||
new_year,
|
||||
next_new_year,
|
||||
leap_month,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: ChineseBasedWithDataLoading> CalendarArithmetic for C {
|
||||
fn month_days(year: i32, month: u8) -> u8 {
|
||||
chinese_based::month_days::<C::CB>(year, month)
|
||||
}
|
||||
|
||||
/// Returns the number of months in a given year, which is 13 in a leap year, and 12 in a common year.
|
||||
fn months_for_every_year(year: i32) -> u8 {
|
||||
if Self::is_leap_year(year) {
|
||||
13
|
||||
} else {
|
||||
12
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the given year is a leap year, and false if not.
|
||||
fn is_leap_year(year: i32) -> bool {
|
||||
if let Some(data) = C::get_compiled_data_for_year(year) {
|
||||
data.leap_month.is_some()
|
||||
} else {
|
||||
chinese_based::is_leap_year::<C::CB>(year)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the (month, day) of the last day in a Chinese year (the day before Chinese New Year).
|
||||
/// The last month in a year will always be 12 in a common year or 13 in a leap year. The day is
|
||||
/// determined by finding the day immediately before the next new year and calculating the number
|
||||
/// of days since the last new moon (beginning of the last month in the year).
|
||||
fn last_month_day_in_year(year: i32) -> (u8, u8) {
|
||||
if let Some(data) = C::get_compiled_data_for_year(year) {
|
||||
if data.leap_month.is_some() {
|
||||
(13, data.days_in_month(13))
|
||||
} else {
|
||||
(12, data.days_in_month(12))
|
||||
}
|
||||
} else {
|
||||
chinese_based::last_month_day_in_year::<C::CB>(year)
|
||||
}
|
||||
}
|
||||
|
||||
fn days_in_provided_year(year: i32) -> u16 {
|
||||
if let Some(data) = C::get_compiled_data_for_year(year) {
|
||||
data.last_day_of_month(13)
|
||||
} else {
|
||||
chinese_based::days_in_provided_year::<C::CB>(year)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the ordinal lunar month from a code for chinese-based calendars.
|
||||
pub(crate) fn chinese_based_ordinal_lunar_month_from_code(
|
||||
code: MonthCode,
|
||||
year_info: ChineseBasedYearInfo,
|
||||
) -> Option<u8> {
|
||||
let leap_month = if let Some(leap) = year_info.get_leap_month() {
|
||||
leap.get()
|
||||
} else {
|
||||
// 14 is a sentinel value, greater than all other months, for the purpose of computation only;
|
||||
// it is impossible to actually have 14 months in a year.
|
||||
14
|
||||
};
|
||||
|
||||
if code.0.len() < 3 {
|
||||
return None;
|
||||
}
|
||||
let bytes = code.0.all_bytes();
|
||||
if bytes[0] != b'M' {
|
||||
return None;
|
||||
}
|
||||
if code.0.len() == 4 && bytes[3] != b'L' {
|
||||
return None;
|
||||
}
|
||||
let mut unadjusted = 0;
|
||||
if bytes[1] == b'0' {
|
||||
if bytes[2] >= b'1' && bytes[2] <= b'9' {
|
||||
unadjusted = bytes[2] - b'0';
|
||||
}
|
||||
} else if bytes[1] == b'1' && bytes[2] >= b'0' && bytes[2] <= b'2' {
|
||||
unadjusted = 10 + bytes[2] - b'0';
|
||||
}
|
||||
if bytes[3] == b'L' {
|
||||
if unadjusted + 1 != leap_month {
|
||||
return None;
|
||||
} else {
|
||||
return Some(unadjusted + 1);
|
||||
}
|
||||
}
|
||||
if unadjusted != 0 {
|
||||
if unadjusted + 1 > leap_month {
|
||||
return Some(unadjusted + 1);
|
||||
} else {
|
||||
return Some(unadjusted);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
// This file is part of ICU4X. For terms of use, please see the file
|
||||
// called LICENSE at the top level of the ICU4X source tree
|
||||
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
|
||||
|
||||
//! Compiled and compressed data for Chinese calendar years; includes `RataDie` types for the beginning of years
|
||||
|
||||
use crate::chinese_based::PackedChineseBasedCompiledData;
|
||||
/// The minimum year present in CHINESE_DATA_ARRAY
|
||||
pub(crate) const MIN_YEAR: i32 = 4660;
|
||||
pub(crate) const MIN_YEAR_ISO: i32 = 2023;
|
||||
|
||||
/// The array of year data for Chinese years between MIN_YEAR and MAX_YEAR; currently, this array must also have
|
||||
/// an entry for the year after max year, since the function for unpacking this data also uses the next entry's new year.
|
||||
///
|
||||
/// Each data entry consists of a `ChineseData`, which in turn is composed of three bytes of data.
|
||||
/// The first 5 bits represents the offset of the Chinese New Year in the given year from Jan 21 of that year.
|
||||
/// The next 13 bits are used to indicate the month lengths of the 13 months; a 1 represents 30 days, and a 0 represents 29 days;
|
||||
/// if there is no 13th month, the 13th in this sequence will be set to 0, but this does not represent 29 days in this case.
|
||||
/// The final 6 bits indicate which ordinal month is a leap month, or is set to zero if the year is not a leap year.
|
||||
///
|
||||
/// TODO: Generate this data
|
||||
#[allow(clippy::unusual_byte_groupings)]
|
||||
pub(crate) const CHINESE_DATA_ARRAY: [PackedChineseBasedCompiledData; 2] = [
|
||||
PackedChineseBasedCompiledData(0b_00001_010, 0b_01101101, 0b_01_000011),
|
||||
PackedChineseBasedCompiledData(0b_10100_010, 0b_01011011, 0b_00_000000),
|
||||
];
|
|
@ -0,0 +1,325 @@
|
|||
// This file is part of ICU4X. For terms of use, please see the file
|
||||
// called LICENSE at the top level of the ICU4X source tree
|
||||
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
|
||||
|
||||
//! This module contains types and implementations for the Coptic calendar.
|
||||
//!
|
||||
//! ```rust
|
||||
//! use icu::calendar::{coptic::Coptic, Date, DateTime};
|
||||
//!
|
||||
//! // `Date` type
|
||||
//! let date_iso = Date::try_new_iso_date(1970, 1, 2)
|
||||
//! .expect("Failed to initialize ISO Date instance.");
|
||||
//! let date_coptic = Date::new_from_iso(date_iso, Coptic);
|
||||
//!
|
||||
//! // `DateTime` type
|
||||
//! let datetime_iso = DateTime::try_new_iso_datetime(1970, 1, 2, 13, 1, 0)
|
||||
//! .expect("Failed to initialize ISO DateTime instance.");
|
||||
//! let datetime_coptic = DateTime::new_from_iso(datetime_iso, Coptic);
|
||||
//!
|
||||
//! // `Date` checks
|
||||
//! assert_eq!(date_coptic.year().number, 1686);
|
||||
//! assert_eq!(date_coptic.month().ordinal, 4);
|
||||
//! assert_eq!(date_coptic.day_of_month().0, 24);
|
||||
//!
|
||||
//! // `DateTime` type
|
||||
//! assert_eq!(datetime_coptic.date.year().number, 1686);
|
||||
//! assert_eq!(datetime_coptic.date.month().ordinal, 4);
|
||||
//! assert_eq!(datetime_coptic.date.day_of_month().0, 24);
|
||||
//! assert_eq!(datetime_coptic.time.hour.number(), 13);
|
||||
//! assert_eq!(datetime_coptic.time.minute.number(), 1);
|
||||
//! assert_eq!(datetime_coptic.time.second.number(), 0);
|
||||
//! ```
|
||||
|
||||
use crate::any_calendar::AnyCalendarKind;
|
||||
use crate::calendar_arithmetic::{ArithmeticDate, CalendarArithmetic};
|
||||
use crate::iso::Iso;
|
||||
use crate::{types, Calendar, CalendarError, Date, DateDuration, DateDurationUnit, DateTime};
|
||||
use calendrical_calculations::helpers::I32CastError;
|
||||
use calendrical_calculations::rata_die::RataDie;
|
||||
use tinystr::tinystr;
|
||||
|
||||
/// The [Coptic Calendar]
|
||||
///
|
||||
/// The [Coptic calendar] is a solar calendar used by the Coptic Orthodox Church, with twelve normal months
|
||||
/// and a thirteenth small epagomenal month.
|
||||
///
|
||||
/// This type can be used with [`Date`] or [`DateTime`] to represent dates in this calendar.
|
||||
///
|
||||
/// [Coptic calendar]: https://en.wikipedia.org/wiki/Coptic_calendar
|
||||
///
|
||||
/// # Era codes
|
||||
///
|
||||
/// This calendar supports two era codes: `"bd"`, and `"ad"`, corresponding to the Before Diocletian and After Diocletian/Anno Martyrum
|
||||
/// eras. 1 A.M. is equivalent to 284 C.E.
|
||||
///
|
||||
/// # Month codes
|
||||
///
|
||||
/// This calendar supports 13 solar month codes (`"M01" - "M13"`), with `"M13"` being used for the short epagomenal month
|
||||
/// at the end of the year.
|
||||
#[derive(Copy, Clone, Debug, Hash, Default, Eq, PartialEq, PartialOrd, Ord)]
|
||||
#[allow(clippy::exhaustive_structs)] // this type is stable
|
||||
pub struct Coptic;
|
||||
|
||||
/// The inner date type used for representing [`Date`]s of [`Coptic`]. See [`Date`] and [`Coptic`] for more details.
|
||||
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub struct CopticDateInner(pub(crate) ArithmeticDate<Coptic>);
|
||||
|
||||
impl CalendarArithmetic for Coptic {
|
||||
fn month_days(year: i32, month: u8) -> u8 {
|
||||
if (1..=12).contains(&month) {
|
||||
30
|
||||
} else if month == 13 {
|
||||
if Self::is_leap_year(year) {
|
||||
6
|
||||
} else {
|
||||
5
|
||||
}
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
fn months_for_every_year(_: i32) -> u8 {
|
||||
13
|
||||
}
|
||||
|
||||
fn is_leap_year(year: i32) -> bool {
|
||||
year % 4 == 3
|
||||
}
|
||||
|
||||
fn last_month_day_in_year(year: i32) -> (u8, u8) {
|
||||
if Self::is_leap_year(year) {
|
||||
(13, 6)
|
||||
} else {
|
||||
(13, 5)
|
||||
}
|
||||
}
|
||||
|
||||
fn days_in_provided_year(year: i32) -> u16 {
|
||||
if Self::is_leap_year(year) {
|
||||
366
|
||||
} else {
|
||||
365
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Calendar for Coptic {
|
||||
type DateInner = CopticDateInner;
|
||||
fn date_from_codes(
|
||||
&self,
|
||||
era: types::Era,
|
||||
year: i32,
|
||||
month_code: types::MonthCode,
|
||||
day: u8,
|
||||
) -> Result<Self::DateInner, CalendarError> {
|
||||
let year = if era.0 == tinystr!(16, "ad") {
|
||||
if year <= 0 {
|
||||
return Err(CalendarError::OutOfRange);
|
||||
}
|
||||
year
|
||||
} else if era.0 == tinystr!(16, "bd") {
|
||||
if year <= 0 {
|
||||
return Err(CalendarError::OutOfRange);
|
||||
}
|
||||
1 - year
|
||||
} else {
|
||||
return Err(CalendarError::UnknownEra(era.0, self.debug_name()));
|
||||
};
|
||||
|
||||
ArithmeticDate::new_from_codes(self, year, month_code, day).map(CopticDateInner)
|
||||
}
|
||||
fn date_from_iso(&self, iso: Date<Iso>) -> CopticDateInner {
|
||||
let fixed_iso = Iso::fixed_from_iso(*iso.inner());
|
||||
Self::coptic_from_fixed(fixed_iso)
|
||||
}
|
||||
|
||||
fn date_to_iso(&self, date: &Self::DateInner) -> Date<Iso> {
|
||||
let fixed_coptic = Coptic::fixed_from_coptic(date.0);
|
||||
Iso::iso_from_fixed(fixed_coptic)
|
||||
}
|
||||
|
||||
fn months_in_year(&self, date: &Self::DateInner) -> u8 {
|
||||
date.0.months_in_year()
|
||||
}
|
||||
|
||||
fn days_in_year(&self, date: &Self::DateInner) -> u16 {
|
||||
date.0.days_in_year()
|
||||
}
|
||||
|
||||
fn days_in_month(&self, date: &Self::DateInner) -> u8 {
|
||||
date.0.days_in_month()
|
||||
}
|
||||
|
||||
fn day_of_week(&self, date: &Self::DateInner) -> types::IsoWeekday {
|
||||
Iso.day_of_week(Coptic.date_to_iso(date).inner())
|
||||
}
|
||||
|
||||
fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration<Self>) {
|
||||
date.0.offset_date(offset);
|
||||
}
|
||||
|
||||
#[allow(clippy::field_reassign_with_default)]
|
||||
fn until(
|
||||
&self,
|
||||
date1: &Self::DateInner,
|
||||
date2: &Self::DateInner,
|
||||
_calendar2: &Self,
|
||||
_largest_unit: DateDurationUnit,
|
||||
_smallest_unit: DateDurationUnit,
|
||||
) -> DateDuration<Self> {
|
||||
date1.0.until(date2.0, _largest_unit, _smallest_unit)
|
||||
}
|
||||
|
||||
fn year(&self, date: &Self::DateInner) -> types::FormattableYear {
|
||||
year_as_coptic(date.0.year)
|
||||
}
|
||||
|
||||
fn is_in_leap_year(&self, date: &Self::DateInner) -> bool {
|
||||
Self::is_leap_year(date.0.year)
|
||||
}
|
||||
|
||||
fn month(&self, date: &Self::DateInner) -> types::FormattableMonth {
|
||||
date.0.month()
|
||||
}
|
||||
|
||||
fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth {
|
||||
date.0.day_of_month()
|
||||
}
|
||||
|
||||
fn day_of_year_info(&self, date: &Self::DateInner) -> types::DayOfYearInfo {
|
||||
let prev_year = date.0.year - 1;
|
||||
let next_year = date.0.year + 1;
|
||||
types::DayOfYearInfo {
|
||||
day_of_year: date.0.day_of_year(),
|
||||
days_in_year: date.0.days_in_year(),
|
||||
prev_year: year_as_coptic(prev_year),
|
||||
days_in_prev_year: Coptic::days_in_year_direct(prev_year),
|
||||
next_year: year_as_coptic(next_year),
|
||||
}
|
||||
}
|
||||
|
||||
fn debug_name(&self) -> &'static str {
|
||||
"Coptic"
|
||||
}
|
||||
|
||||
fn any_calendar_kind(&self) -> Option<AnyCalendarKind> {
|
||||
Some(AnyCalendarKind::Coptic)
|
||||
}
|
||||
}
|
||||
|
||||
impl Coptic {
|
||||
fn fixed_from_coptic(date: ArithmeticDate<Coptic>) -> RataDie {
|
||||
calendrical_calculations::coptic::fixed_from_coptic(date.year, date.month, date.day)
|
||||
}
|
||||
|
||||
pub(crate) fn coptic_from_fixed(date: RataDie) -> CopticDateInner {
|
||||
let (year, month, day) = match calendrical_calculations::coptic::coptic_from_fixed(date) {
|
||||
Err(I32CastError::BelowMin) => return CopticDateInner(ArithmeticDate::min_date()),
|
||||
Err(I32CastError::AboveMax) => return CopticDateInner(ArithmeticDate::max_date()),
|
||||
Ok(ymd) => ymd,
|
||||
};
|
||||
|
||||
CopticDateInner(ArithmeticDate::new_unchecked(year, month, day))
|
||||
}
|
||||
|
||||
fn days_in_year_direct(year: i32) -> u16 {
|
||||
if Coptic::is_leap_year(year) {
|
||||
366
|
||||
} else {
|
||||
365
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Date<Coptic> {
|
||||
/// Construct new Coptic Date.
|
||||
///
|
||||
/// Negative years are in the B.D. era, starting with 0 = 1 B.D.
|
||||
///
|
||||
/// ```rust
|
||||
/// use icu::calendar::Date;
|
||||
///
|
||||
/// let date_coptic = Date::try_new_coptic_date(1686, 5, 6)
|
||||
/// .expect("Failed to initialize Coptic Date instance.");
|
||||
///
|
||||
/// assert_eq!(date_coptic.year().number, 1686);
|
||||
/// assert_eq!(date_coptic.month().ordinal, 5);
|
||||
/// assert_eq!(date_coptic.day_of_month().0, 6);
|
||||
/// ```
|
||||
pub fn try_new_coptic_date(
|
||||
year: i32,
|
||||
month: u8,
|
||||
day: u8,
|
||||
) -> Result<Date<Coptic>, CalendarError> {
|
||||
ArithmeticDate::new_from_ordinals(year, month, day)
|
||||
.map(CopticDateInner)
|
||||
.map(|inner| Date::from_raw(inner, Coptic))
|
||||
}
|
||||
}
|
||||
|
||||
impl DateTime<Coptic> {
|
||||
/// Construct a new Coptic datetime from integers.
|
||||
///
|
||||
/// Negative years are in the B.D. era, starting with 0 = 1 B.D.
|
||||
///
|
||||
/// ```rust
|
||||
/// use icu::calendar::DateTime;
|
||||
///
|
||||
/// let datetime_coptic =
|
||||
/// DateTime::try_new_coptic_datetime(1686, 5, 6, 13, 1, 0)
|
||||
/// .expect("Failed to initialize Coptic DateTime instance.");
|
||||
///
|
||||
/// assert_eq!(datetime_coptic.date.year().number, 1686);
|
||||
/// assert_eq!(datetime_coptic.date.month().ordinal, 5);
|
||||
/// assert_eq!(datetime_coptic.date.day_of_month().0, 6);
|
||||
/// assert_eq!(datetime_coptic.time.hour.number(), 13);
|
||||
/// assert_eq!(datetime_coptic.time.minute.number(), 1);
|
||||
/// assert_eq!(datetime_coptic.time.second.number(), 0);
|
||||
/// ```
|
||||
pub fn try_new_coptic_datetime(
|
||||
year: i32,
|
||||
month: u8,
|
||||
day: u8,
|
||||
hour: u8,
|
||||
minute: u8,
|
||||
second: u8,
|
||||
) -> Result<DateTime<Coptic>, CalendarError> {
|
||||
Ok(DateTime {
|
||||
date: Date::try_new_coptic_date(year, month, day)?,
|
||||
time: types::Time::try_new(hour, minute, second, 0)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn year_as_coptic(year: i32) -> types::FormattableYear {
|
||||
if year > 0 {
|
||||
types::FormattableYear {
|
||||
era: types::Era(tinystr!(16, "ad")),
|
||||
number: year,
|
||||
cyclic: None,
|
||||
related_iso: None,
|
||||
}
|
||||
} else {
|
||||
types::FormattableYear {
|
||||
era: types::Era(tinystr!(16, "bd")),
|
||||
number: 1 - year,
|
||||
cyclic: None,
|
||||
related_iso: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn test_coptic_regression() {
|
||||
// https://github.com/unicode-org/icu4x/issues/2254
|
||||
let iso_date = Date::try_new_iso_date(-100, 3, 3).unwrap();
|
||||
let coptic = iso_date.to_calendar(Coptic);
|
||||
let recovered_iso = coptic.to_iso();
|
||||
assert_eq!(iso_date, recovered_iso);
|
||||
}
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,446 @@
|
|||
// This file is part of ICU4X. For terms of use, please see the file
|
||||
// called LICENSE at the top level of the ICU4X source tree
|
||||
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
|
||||
|
||||
use crate::any_calendar::{AnyCalendar, IntoAnyCalendar};
|
||||
use crate::week::{WeekCalculator, WeekOf};
|
||||
use crate::{types, Calendar, CalendarError, DateDuration, DateDurationUnit, Iso};
|
||||
use alloc::rc::Rc;
|
||||
use alloc::sync::Arc;
|
||||
use core::fmt;
|
||||
use core::ops::Deref;
|
||||
|
||||
/// Types that contain a calendar
|
||||
///
|
||||
/// This allows one to use [`Date`] with wrappers around calendars,
|
||||
/// e.g. reference counted calendars.
|
||||
pub trait AsCalendar {
|
||||
/// The calendar being wrapped
|
||||
type Calendar: Calendar;
|
||||
/// Obtain the inner calendar
|
||||
fn as_calendar(&self) -> &Self::Calendar;
|
||||
}
|
||||
|
||||
impl<C: Calendar> AsCalendar for C {
|
||||
type Calendar = C;
|
||||
#[inline]
|
||||
fn as_calendar(&self) -> &Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Calendar> AsCalendar for Rc<C> {
|
||||
type Calendar = C;
|
||||
#[inline]
|
||||
fn as_calendar(&self) -> &C {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Calendar> AsCalendar for Arc<C> {
|
||||
type Calendar = C;
|
||||
#[inline]
|
||||
fn as_calendar(&self) -> &C {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// This exists as a wrapper around `&'a T` so that
|
||||
/// `Date<&'a C>` is possible for calendar `C`.
|
||||
///
|
||||
/// Unfortunately,
|
||||
/// [`AsCalendar`] cannot be implemented on `&'a T` directly because
|
||||
/// `&'a T` is `#[fundamental]` and the impl would clash with the one above with
|
||||
/// `AsCalendar` for `C: Calendar`.
|
||||
///
|
||||
/// Use `Date<Ref<'a, C>>` where you would use `Date<&'a C>`
|
||||
#[allow(clippy::exhaustive_structs)] // newtype
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
pub struct Ref<'a, C>(pub &'a C);
|
||||
|
||||
impl<C> Copy for Ref<'_, C> {}
|
||||
|
||||
impl<C> Clone for Ref<'_, C> {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Calendar> AsCalendar for Ref<'_, C> {
|
||||
type Calendar = C;
|
||||
#[inline]
|
||||
fn as_calendar(&self) -> &C {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, C> Deref for Ref<'a, C> {
|
||||
type Target = C;
|
||||
fn deref(&self) -> &C {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// A date for a given calendar.
|
||||
///
|
||||
/// This can work with wrappers around [`Calendar`] types,
|
||||
/// e.g. `Rc<C>`, via the [`AsCalendar`] trait.
|
||||
///
|
||||
/// This can be constructed constructed
|
||||
/// from its fields via [`Self::try_new_from_codes()`], or can be constructed with one of the
|
||||
/// `new_<calendar>_datetime()` per-calendar methods (and then freely converted between calendars).
|
||||
///
|
||||
/// ```rust
|
||||
/// use icu::calendar::Date;
|
||||
///
|
||||
/// // Example: creation of ISO date from integers.
|
||||
/// let date_iso = Date::try_new_iso_date(1970, 1, 2)
|
||||
/// .expect("Failed to initialize ISO Date instance.");
|
||||
///
|
||||
/// assert_eq!(date_iso.year().number, 1970);
|
||||
/// assert_eq!(date_iso.month().ordinal, 1);
|
||||
/// assert_eq!(date_iso.day_of_month().0, 2);
|
||||
/// ```
|
||||
pub struct Date<A: AsCalendar> {
|
||||
pub(crate) inner: <A::Calendar as Calendar>::DateInner,
|
||||
pub(crate) calendar: A,
|
||||
}
|
||||
|
||||
impl<A: AsCalendar> Date<A> {
|
||||
/// Construct a date from from era/month codes and fields, and some calendar representation
|
||||
#[inline]
|
||||
pub fn try_new_from_codes(
|
||||
era: types::Era,
|
||||
year: i32,
|
||||
month_code: types::MonthCode,
|
||||
day: u8,
|
||||
calendar: A,
|
||||
) -> Result<Self, CalendarError> {
|
||||
let inner = calendar
|
||||
.as_calendar()
|
||||
.date_from_codes(era, year, month_code, day)?;
|
||||
Ok(Date { inner, calendar })
|
||||
}
|
||||
|
||||
/// Construct a date from an ISO date and some calendar representation
|
||||
#[inline]
|
||||
pub fn new_from_iso(iso: Date<Iso>, calendar: A) -> Self {
|
||||
let inner = calendar.as_calendar().date_from_iso(iso);
|
||||
Date { inner, calendar }
|
||||
}
|
||||
|
||||
/// Convert the Date to an ISO Date
|
||||
#[inline]
|
||||
pub fn to_iso(&self) -> Date<Iso> {
|
||||
self.calendar.as_calendar().date_to_iso(self.inner())
|
||||
}
|
||||
|
||||
/// Convert the Date to a date in a different calendar
|
||||
#[inline]
|
||||
pub fn to_calendar<A2: AsCalendar>(&self, calendar: A2) -> Date<A2> {
|
||||
Date::new_from_iso(self.to_iso(), calendar)
|
||||
}
|
||||
|
||||
/// The number of months in the year of this date
|
||||
#[inline]
|
||||
pub fn months_in_year(&self) -> u8 {
|
||||
self.calendar.as_calendar().months_in_year(self.inner())
|
||||
}
|
||||
|
||||
/// The number of days in the year of this date
|
||||
#[inline]
|
||||
pub fn days_in_year(&self) -> u16 {
|
||||
self.calendar.as_calendar().days_in_year(self.inner())
|
||||
}
|
||||
|
||||
/// The number of days in the month of this date
|
||||
#[inline]
|
||||
pub fn days_in_month(&self) -> u8 {
|
||||
self.calendar.as_calendar().days_in_month(self.inner())
|
||||
}
|
||||
|
||||
/// The day of the week for this date
|
||||
///
|
||||
/// Monday is 1, Sunday is 7, according to ISO
|
||||
#[inline]
|
||||
pub fn day_of_week(&self) -> types::IsoWeekday {
|
||||
self.calendar.as_calendar().day_of_week(self.inner())
|
||||
}
|
||||
|
||||
/// Add a `duration` to this date, mutating it
|
||||
///
|
||||
/// Currently unstable for ICU4X 1.0
|
||||
#[doc(hidden)]
|
||||
#[inline]
|
||||
pub fn add(&mut self, duration: DateDuration<A::Calendar>) {
|
||||
self.calendar
|
||||
.as_calendar()
|
||||
.offset_date(&mut self.inner, duration)
|
||||
}
|
||||
|
||||
/// Add a `duration` to this date, returning the new one
|
||||
///
|
||||
/// Currently unstable for ICU4X 1.0
|
||||
#[doc(hidden)]
|
||||
#[inline]
|
||||
pub fn added(mut self, duration: DateDuration<A::Calendar>) -> Self {
|
||||
self.add(duration);
|
||||
self
|
||||
}
|
||||
|
||||
/// Calculating the duration between `other - self`
|
||||
///
|
||||
/// Currently unstable for ICU4X 1.0
|
||||
#[doc(hidden)]
|
||||
#[inline]
|
||||
pub fn until<B: AsCalendar<Calendar = A::Calendar>>(
|
||||
&self,
|
||||
other: &Date<B>,
|
||||
largest_unit: DateDurationUnit,
|
||||
smallest_unit: DateDurationUnit,
|
||||
) -> DateDuration<A::Calendar> {
|
||||
self.calendar.as_calendar().until(
|
||||
self.inner(),
|
||||
other.inner(),
|
||||
other.calendar.as_calendar(),
|
||||
largest_unit,
|
||||
smallest_unit,
|
||||
)
|
||||
}
|
||||
|
||||
/// The calendar-specific year represented by `self`
|
||||
#[inline]
|
||||
pub fn year(&self) -> types::FormattableYear {
|
||||
self.calendar.as_calendar().year(&self.inner)
|
||||
}
|
||||
|
||||
/// Returns whether `self` is in a calendar-specific leap year
|
||||
#[inline]
|
||||
pub fn is_in_leap_year(&self) -> bool {
|
||||
self.calendar.as_calendar().is_in_leap_year(&self.inner)
|
||||
}
|
||||
|
||||
/// The calendar-specific month represented by `self`
|
||||
#[inline]
|
||||
pub fn month(&self) -> types::FormattableMonth {
|
||||
self.calendar.as_calendar().month(&self.inner)
|
||||
}
|
||||
|
||||
/// The calendar-specific day-of-month represented by `self`
|
||||
#[inline]
|
||||
pub fn day_of_month(&self) -> types::DayOfMonth {
|
||||
self.calendar.as_calendar().day_of_month(&self.inner)
|
||||
}
|
||||
|
||||
/// The calendar-specific day-of-month represented by `self`
|
||||
#[inline]
|
||||
pub fn day_of_year_info(&self) -> types::DayOfYearInfo {
|
||||
self.calendar.as_calendar().day_of_year_info(&self.inner)
|
||||
}
|
||||
|
||||
/// The week of the month containing this date.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use icu::calendar::types::IsoWeekday;
|
||||
/// use icu::calendar::types::WeekOfMonth;
|
||||
/// use icu::calendar::Date;
|
||||
///
|
||||
/// let date = Date::try_new_iso_date(2022, 8, 10).unwrap(); // second Wednesday
|
||||
///
|
||||
/// // The following info is usually locale-specific
|
||||
/// let first_weekday = IsoWeekday::Sunday;
|
||||
///
|
||||
/// assert_eq!(date.week_of_month(first_weekday), WeekOfMonth(2));
|
||||
/// ```
|
||||
pub fn week_of_month(&self, first_weekday: types::IsoWeekday) -> types::WeekOfMonth {
|
||||
let config = WeekCalculator {
|
||||
first_weekday,
|
||||
min_week_days: 0, // ignored
|
||||
};
|
||||
config.week_of_month(self.day_of_month(), self.day_of_week())
|
||||
}
|
||||
|
||||
/// The week of the year containing this date.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use icu::calendar::types::IsoWeekday;
|
||||
/// use icu::calendar::week::RelativeUnit;
|
||||
/// use icu::calendar::week::WeekCalculator;
|
||||
/// use icu::calendar::week::WeekOf;
|
||||
/// use icu::calendar::Date;
|
||||
///
|
||||
/// let date = Date::try_new_iso_date(2022, 8, 26).unwrap();
|
||||
///
|
||||
/// // The following info is usually locale-specific
|
||||
/// let week_calculator = WeekCalculator::default();
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// date.week_of_year(&week_calculator),
|
||||
/// Ok(WeekOf {
|
||||
/// week: 35,
|
||||
/// unit: RelativeUnit::Current
|
||||
/// })
|
||||
/// );
|
||||
/// ```
|
||||
pub fn week_of_year(&self, config: &WeekCalculator) -> Result<WeekOf, CalendarError> {
|
||||
config.week_of_year(self.day_of_year_info(), self.day_of_week())
|
||||
}
|
||||
|
||||
/// Construct a date from raw values for a given calendar. This does not check any
|
||||
/// invariants for the date and calendar, and should only be called by calendar implementations.
|
||||
///
|
||||
/// Calling this outside of calendar implementations is sound, but calendar implementations are not
|
||||
/// expected to do anything sensible with such invalid dates.
|
||||
///
|
||||
/// AnyCalendar *will* panic if AnyCalendar [`Date`] objects with mismatching
|
||||
/// date and calendar types are constructed
|
||||
#[inline]
|
||||
pub fn from_raw(inner: <A::Calendar as Calendar>::DateInner, calendar: A) -> Self {
|
||||
Self { inner, calendar }
|
||||
}
|
||||
|
||||
/// Get the inner date implementation. Should not be called outside of calendar implementations
|
||||
#[inline]
|
||||
pub fn inner(&self) -> &<A::Calendar as Calendar>::DateInner {
|
||||
&self.inner
|
||||
}
|
||||
|
||||
/// Get a reference to the contained calendar
|
||||
#[inline]
|
||||
pub fn calendar(&self) -> &A::Calendar {
|
||||
self.calendar.as_calendar()
|
||||
}
|
||||
|
||||
/// Get a reference to the contained calendar wrapper
|
||||
///
|
||||
/// (Useful in case the user wishes to e.g. clone an Rc)
|
||||
#[inline]
|
||||
pub fn calendar_wrapper(&self) -> &A {
|
||||
&self.calendar
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: IntoAnyCalendar, A: AsCalendar<Calendar = C>> Date<A> {
|
||||
/// Type-erase the date, converting it to a date for [`AnyCalendar`]
|
||||
pub fn to_any(&self) -> Date<AnyCalendar> {
|
||||
let cal = self.calendar();
|
||||
Date::from_raw(cal.date_to_any(self.inner()), cal.to_any_cloned())
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Calendar> Date<C> {
|
||||
/// Wrap the calendar type in `Rc<T>`
|
||||
///
|
||||
/// Useful when paired with [`Self::to_any()`] to obtain a `Date<Rc<AnyCalendar>>`
|
||||
pub fn wrap_calendar_in_rc(self) -> Date<Rc<C>> {
|
||||
Date::from_raw(self.inner, Rc::new(self.calendar))
|
||||
}
|
||||
|
||||
/// Wrap the calendar type in `Arc<T>`
|
||||
///
|
||||
/// Useful when paired with [`Self::to_any()`] to obtain a `Date<Rc<AnyCalendar>>`
|
||||
pub fn wrap_calendar_in_arc(self) -> Date<Arc<C>> {
|
||||
Date::from_raw(self.inner, Arc::new(self.calendar))
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, A, B> PartialEq<Date<B>> for Date<A>
|
||||
where
|
||||
C: Calendar,
|
||||
A: AsCalendar<Calendar = C>,
|
||||
B: AsCalendar<Calendar = C>,
|
||||
{
|
||||
fn eq(&self, other: &Date<B>) -> bool {
|
||||
self.inner.eq(&other.inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: AsCalendar> Eq for Date<A> {}
|
||||
|
||||
impl<C, A, B> PartialOrd<Date<B>> for Date<A>
|
||||
where
|
||||
C: Calendar,
|
||||
C::DateInner: PartialOrd,
|
||||
A: AsCalendar<Calendar = C>,
|
||||
B: AsCalendar<Calendar = C>,
|
||||
{
|
||||
fn partial_cmp(&self, other: &Date<B>) -> Option<core::cmp::Ordering> {
|
||||
self.inner.partial_cmp(&other.inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, A> Ord for Date<A>
|
||||
where
|
||||
C: Calendar,
|
||||
C::DateInner: Ord,
|
||||
A: AsCalendar<Calendar = C>,
|
||||
{
|
||||
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
|
||||
self.inner.cmp(&other.inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: AsCalendar> fmt::Debug for Date<A> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
write!(
|
||||
f,
|
||||
"Date({:?}, for calendar {})",
|
||||
self.inner,
|
||||
self.calendar.as_calendar().debug_name()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: AsCalendar + Clone> Clone for Date<A> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
inner: self.inner.clone(),
|
||||
calendar: self.calendar.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<A> Copy for Date<A>
|
||||
where
|
||||
A: AsCalendar + Copy,
|
||||
<<A as AsCalendar>::Calendar as Calendar>::DateInner: Copy,
|
||||
{
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_ord() {
|
||||
let dates_in_order = [
|
||||
Date::try_new_iso_date(-10, 1, 1).unwrap(),
|
||||
Date::try_new_iso_date(-10, 1, 2).unwrap(),
|
||||
Date::try_new_iso_date(-10, 2, 1).unwrap(),
|
||||
Date::try_new_iso_date(-1, 1, 1).unwrap(),
|
||||
Date::try_new_iso_date(-1, 1, 2).unwrap(),
|
||||
Date::try_new_iso_date(-1, 2, 1).unwrap(),
|
||||
Date::try_new_iso_date(0, 1, 1).unwrap(),
|
||||
Date::try_new_iso_date(0, 1, 2).unwrap(),
|
||||
Date::try_new_iso_date(0, 2, 1).unwrap(),
|
||||
Date::try_new_iso_date(1, 1, 1).unwrap(),
|
||||
Date::try_new_iso_date(1, 1, 2).unwrap(),
|
||||
Date::try_new_iso_date(1, 2, 1).unwrap(),
|
||||
Date::try_new_iso_date(10, 1, 1).unwrap(),
|
||||
Date::try_new_iso_date(10, 1, 2).unwrap(),
|
||||
Date::try_new_iso_date(10, 2, 1).unwrap(),
|
||||
];
|
||||
for (i, i_date) in dates_in_order.iter().enumerate() {
|
||||
for (j, j_date) in dates_in_order.iter().enumerate() {
|
||||
let result1 = i_date.cmp(j_date);
|
||||
let result2 = j_date.cmp(i_date);
|
||||
assert_eq!(result1.reverse(), result2);
|
||||
assert_eq!(i.cmp(&j), i_date.cmp(j_date));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,206 @@
|
|||
// This file is part of ICU4X. For terms of use, please see the file
|
||||
// called LICENSE at the top level of the ICU4X source tree
|
||||
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
|
||||
|
||||
use crate::any_calendar::{AnyCalendar, IntoAnyCalendar};
|
||||
use crate::types::{self, Time};
|
||||
use crate::{AsCalendar, Calendar, CalendarError, Date, Iso};
|
||||
use alloc::rc::Rc;
|
||||
use alloc::sync::Arc;
|
||||
|
||||
/// A date+time for a given calendar.
|
||||
///
|
||||
/// This can work with wrappers around [`Calendar`](crate::Calendar) types,
|
||||
/// e.g. `Rc<C>`, via the [`AsCalendar`] trait, much like
|
||||
/// [`Date`].
|
||||
///
|
||||
/// This can be constructed manually from a [`Date`] and [`Time`], or can be constructed
|
||||
/// from its fields via [`Self::try_new_from_codes()`], or can be constructed with one of the
|
||||
/// `new_<calendar>_datetime()` per-calendar methods (and then freely converted between calendars).
|
||||
///
|
||||
/// ```rust
|
||||
/// use icu::calendar::DateTime;
|
||||
///
|
||||
/// // Example: Construction of ISO datetime from integers.
|
||||
/// let datetime_iso = DateTime::try_new_iso_datetime(1970, 1, 2, 13, 1, 0)
|
||||
/// .expect("Failed to initialize ISO DateTime instance.");
|
||||
///
|
||||
/// assert_eq!(datetime_iso.date.year().number, 1970);
|
||||
/// assert_eq!(datetime_iso.date.month().ordinal, 1);
|
||||
/// assert_eq!(datetime_iso.date.day_of_month().0, 2);
|
||||
/// assert_eq!(datetime_iso.time.hour.number(), 13);
|
||||
/// assert_eq!(datetime_iso.time.minute.number(), 1);
|
||||
/// assert_eq!(datetime_iso.time.second.number(), 0);
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
#[allow(clippy::exhaustive_structs)] // this type is stable
|
||||
pub struct DateTime<A: AsCalendar> {
|
||||
/// The date
|
||||
pub date: Date<A>,
|
||||
/// The time
|
||||
pub time: Time,
|
||||
}
|
||||
|
||||
impl<A: AsCalendar> DateTime<A> {
|
||||
/// Construct a [`DateTime`] for a given [`Date`] and [`Time`]
|
||||
pub fn new(date: Date<A>, time: Time) -> Self {
|
||||
DateTime { date, time }
|
||||
}
|
||||
|
||||
/// Construct a datetime from from era/month codes and fields,
|
||||
/// and some calendar representation
|
||||
#[inline]
|
||||
pub fn try_new_from_codes(
|
||||
era: types::Era,
|
||||
year: i32,
|
||||
month_code: types::MonthCode,
|
||||
day: u8,
|
||||
time: Time,
|
||||
calendar: A,
|
||||
) -> Result<Self, CalendarError> {
|
||||
let date = Date::try_new_from_codes(era, year, month_code, day, calendar)?;
|
||||
Ok(DateTime { date, time })
|
||||
}
|
||||
|
||||
/// Construct a DateTime from an ISO datetime and some calendar representation
|
||||
#[inline]
|
||||
pub fn new_from_iso(iso: DateTime<Iso>, calendar: A) -> Self {
|
||||
let date = Date::new_from_iso(iso.date, calendar);
|
||||
DateTime {
|
||||
date,
|
||||
time: iso.time,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the DateTime to an ISO DateTime
|
||||
#[inline]
|
||||
pub fn to_iso(&self) -> DateTime<Iso> {
|
||||
DateTime {
|
||||
date: self.date.to_iso(),
|
||||
time: self.time,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the DateTime to a DateTime in a different calendar
|
||||
#[inline]
|
||||
pub fn to_calendar<A2: AsCalendar>(&self, calendar: A2) -> DateTime<A2> {
|
||||
DateTime {
|
||||
date: self.date.to_calendar(calendar),
|
||||
time: self.time,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: IntoAnyCalendar, A: AsCalendar<Calendar = C>> DateTime<A> {
|
||||
/// Type-erase the date, converting it to a date for [`AnyCalendar`]
|
||||
pub fn to_any(&self) -> DateTime<AnyCalendar> {
|
||||
DateTime {
|
||||
date: self.date.to_any(),
|
||||
time: self.time,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Calendar> DateTime<C> {
|
||||
/// Wrap the calendar type in `Rc<T>`
|
||||
///
|
||||
/// Useful when paired with [`Self::to_any()`] to obtain a `DateTime<Rc<AnyCalendar>>`
|
||||
pub fn wrap_calendar_in_rc(self) -> DateTime<Rc<C>> {
|
||||
DateTime {
|
||||
date: self.date.wrap_calendar_in_rc(),
|
||||
time: self.time,
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrap the calendar type in `Arc<T>`
|
||||
///
|
||||
/// Useful when paired with [`Self::to_any()`] to obtain a `DateTime<Rc<AnyCalendar>>`
|
||||
pub fn wrap_calendar_in_arc(self) -> DateTime<Arc<C>> {
|
||||
DateTime {
|
||||
date: self.date.wrap_calendar_in_arc(),
|
||||
time: self.time,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, A, B> PartialEq<DateTime<B>> for DateTime<A>
|
||||
where
|
||||
C: Calendar,
|
||||
A: AsCalendar<Calendar = C>,
|
||||
B: AsCalendar<Calendar = C>,
|
||||
{
|
||||
fn eq(&self, other: &DateTime<B>) -> bool {
|
||||
self.date == other.date && self.time == other.time
|
||||
}
|
||||
}
|
||||
|
||||
// We can do this since DateInner is required to be Eq by the Calendar trait
|
||||
impl<A: AsCalendar> Eq for DateTime<A> {}
|
||||
|
||||
impl<C, A, B> PartialOrd<DateTime<B>> for DateTime<A>
|
||||
where
|
||||
C: Calendar,
|
||||
C::DateInner: PartialOrd,
|
||||
A: AsCalendar<Calendar = C>,
|
||||
B: AsCalendar<Calendar = C>,
|
||||
{
|
||||
fn partial_cmp(&self, other: &DateTime<B>) -> Option<core::cmp::Ordering> {
|
||||
match self.date.partial_cmp(&other.date) {
|
||||
Some(core::cmp::Ordering::Equal) => self.time.partial_cmp(&other.time),
|
||||
other => other,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, A> Ord for DateTime<A>
|
||||
where
|
||||
C: Calendar,
|
||||
C::DateInner: Ord,
|
||||
A: AsCalendar<Calendar = C>,
|
||||
{
|
||||
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
|
||||
(&self.date, &self.time).cmp(&(&other.date, &other.time))
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: AsCalendar + Clone> Clone for DateTime<A> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
date: self.date.clone(),
|
||||
time: self.time,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<A> Copy for DateTime<A>
|
||||
where
|
||||
A: AsCalendar + Copy,
|
||||
<<A as AsCalendar>::Calendar as Calendar>::DateInner: Copy,
|
||||
{
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_ord() {
|
||||
let dates_in_order = [
|
||||
DateTime::try_new_iso_datetime(0, 1, 1, 0, 0, 0).unwrap(),
|
||||
DateTime::try_new_iso_datetime(0, 1, 1, 0, 0, 1).unwrap(),
|
||||
DateTime::try_new_iso_datetime(0, 1, 1, 0, 1, 0).unwrap(),
|
||||
DateTime::try_new_iso_datetime(0, 1, 1, 1, 0, 0).unwrap(),
|
||||
DateTime::try_new_iso_datetime(0, 1, 2, 0, 0, 0).unwrap(),
|
||||
DateTime::try_new_iso_datetime(0, 2, 1, 0, 0, 0).unwrap(),
|
||||
DateTime::try_new_iso_datetime(1, 1, 1, 0, 0, 0).unwrap(),
|
||||
];
|
||||
for (i, i_date) in dates_in_order.iter().enumerate() {
|
||||
for (j, j_date) in dates_in_order.iter().enumerate() {
|
||||
let result1 = i_date.cmp(j_date);
|
||||
let result2 = j_date.cmp(i_date);
|
||||
assert_eq!(result1.reverse(), result2);
|
||||
assert_eq!(i.cmp(&j), i_date.cmp(j_date));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
// This file is part of ICU4X. For terms of use, please see the file
|
||||
// called LICENSE at the top level of the ICU4X source tree
|
||||
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
|
||||
|
||||
use crate::Calendar;
|
||||
use core::fmt;
|
||||
use core::marker::PhantomData;
|
||||
|
||||
/// A duration between two dates
|
||||
///
|
||||
/// Can be used to perform date arithmetic
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use icu_calendar::{
|
||||
/// types::IsoWeekday, Date, DateDuration, DateDurationUnit,
|
||||
/// };
|
||||
///
|
||||
/// // Creating ISO date: 1992-09-02.
|
||||
/// let mut date_iso = Date::try_new_iso_date(1992, 9, 2)
|
||||
/// .expect("Failed to initialize ISO Date instance.");
|
||||
///
|
||||
/// assert_eq!(date_iso.day_of_week(), IsoWeekday::Wednesday);
|
||||
/// assert_eq!(date_iso.year().number, 1992);
|
||||
/// assert_eq!(date_iso.month().ordinal, 9);
|
||||
/// assert_eq!(date_iso.day_of_month().0, 2);
|
||||
///
|
||||
/// // Answering questions about days in month and year.
|
||||
/// assert_eq!(date_iso.days_in_year(), 366);
|
||||
/// assert_eq!(date_iso.days_in_month(), 30);
|
||||
///
|
||||
/// // Advancing date in-place by 1 year, 2 months, 3 weeks, 4 days.
|
||||
/// date_iso.add(DateDuration::new(1, 2, 3, 4));
|
||||
/// assert_eq!(date_iso.year().number, 1993);
|
||||
/// assert_eq!(date_iso.month().ordinal, 11);
|
||||
/// assert_eq!(date_iso.day_of_month().0, 27);
|
||||
///
|
||||
/// // Reverse date advancement.
|
||||
/// date_iso.add(DateDuration::new(-1, -2, -3, -4));
|
||||
/// assert_eq!(date_iso.year().number, 1992);
|
||||
/// assert_eq!(date_iso.month().ordinal, 9);
|
||||
/// assert_eq!(date_iso.day_of_month().0, 2);
|
||||
///
|
||||
/// // Creating ISO date: 2022-01-30.
|
||||
/// let newer_date_iso = Date::try_new_iso_date(2022, 1, 30)
|
||||
/// .expect("Failed to initialize ISO Date instance.");
|
||||
///
|
||||
/// // Comparing dates: 2022-01-30 and 1992-09-02.
|
||||
/// let duration = newer_date_iso.until(
|
||||
/// &date_iso,
|
||||
/// DateDurationUnit::Years,
|
||||
/// DateDurationUnit::Days,
|
||||
/// );
|
||||
/// assert_eq!(duration.years, 30);
|
||||
/// assert_eq!(duration.months, -8);
|
||||
/// assert_eq!(duration.days, 28);
|
||||
///
|
||||
/// // Create new date with date advancement. Reassign to new variable.
|
||||
/// let mutated_date_iso = date_iso.added(DateDuration::new(1, 2, 3, 4));
|
||||
/// assert_eq!(mutated_date_iso.year().number, 1993);
|
||||
/// assert_eq!(mutated_date_iso.month().ordinal, 11);
|
||||
/// assert_eq!(mutated_date_iso.day_of_month().0, 27);
|
||||
/// ```
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
#[allow(clippy::exhaustive_structs)] // this type should be stable (and is intended to be constructed manually)
|
||||
pub struct DateDuration<C: Calendar + ?Sized> {
|
||||
/// The number of years
|
||||
pub years: i32,
|
||||
/// The number of months
|
||||
pub months: i32,
|
||||
/// The number of weeks
|
||||
pub weeks: i32,
|
||||
/// The number of days
|
||||
pub days: i32,
|
||||
/// A marker for the calendar
|
||||
pub marker: PhantomData<C>,
|
||||
}
|
||||
|
||||
/// A "duration unit" used to specify the minimum or maximum duration of time to
|
||||
/// care about
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
#[allow(clippy::exhaustive_enums)] // this type should be stable
|
||||
pub enum DateDurationUnit {
|
||||
/// Duration in years
|
||||
Years,
|
||||
/// Duration in months
|
||||
Months,
|
||||
/// Duration in weeks
|
||||
Weeks,
|
||||
/// Duration in days
|
||||
Days,
|
||||
}
|
||||
|
||||
impl<C: Calendar + ?Sized> Default for DateDuration<C> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
years: 0,
|
||||
months: 0,
|
||||
weeks: 0,
|
||||
days: 0,
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Calendar + ?Sized> DateDuration<C> {
|
||||
/// Construct a DateDuration
|
||||
///
|
||||
/// ```rust
|
||||
/// # use icu_calendar::*;
|
||||
/// // two years, three months, and five days
|
||||
/// let duration: DateDuration<Iso> = DateDuration::new(2, 3, 0, 5);
|
||||
/// ```
|
||||
pub fn new(years: i32, months: i32, weeks: i32, days: i32) -> Self {
|
||||
DateDuration {
|
||||
years,
|
||||
months,
|
||||
weeks,
|
||||
days,
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Explicitly cast duration to one for a different calendar
|
||||
pub fn cast_unit<C2: Calendar + ?Sized>(self) -> DateDuration<C2> {
|
||||
DateDuration {
|
||||
years: self.years,
|
||||
months: self.months,
|
||||
days: self.days,
|
||||
weeks: self.weeks,
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Calendar> fmt::Debug for DateDuration<C> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
f.debug_struct("DateDuration")
|
||||
.field("years", &self.years)
|
||||
.field("months", &self.months)
|
||||
.field("weeks", &self.weeks)
|
||||
.field("days", &self.days)
|
||||
.finish()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
// This file is part of ICU4X. For terms of use, please see the file
|
||||
// called LICENSE at the top level of the ICU4X source tree
|
||||
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
|
||||
|
||||
use displaydoc::Display;
|
||||
use icu_provider::DataError;
|
||||
use tinystr::{tinystr, TinyStr16, TinyStr4};
|
||||
use writeable::Writeable;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl std::error::Error for CalendarError {}
|
||||
|
||||
/// A list of error outcomes for various operations in this module.
|
||||
///
|
||||
/// Re-exported as [`Error`](crate::Error).
|
||||
#[derive(Display, Debug, Copy, Clone, PartialEq)]
|
||||
#[non_exhaustive]
|
||||
pub enum CalendarError {
|
||||
/// An input could not be parsed.
|
||||
#[displaydoc("Could not parse as integer")]
|
||||
Parse,
|
||||
/// An input overflowed its range.
|
||||
#[displaydoc("{field} must be between 0-{max}")]
|
||||
Overflow {
|
||||
/// The name of the field
|
||||
field: &'static str,
|
||||
/// The maximum value
|
||||
max: usize,
|
||||
},
|
||||
#[displaydoc("{field} must be between {min}-0")]
|
||||
/// An input underflowed its range.
|
||||
Underflow {
|
||||
/// The name of the field
|
||||
field: &'static str,
|
||||
/// The minimum value
|
||||
min: isize,
|
||||
},
|
||||
/// Out of range
|
||||
// TODO(Manishearth) turn this into a proper variant
|
||||
OutOfRange,
|
||||
/// Unknown era
|
||||
#[displaydoc("No era named {0} for calendar {1}")]
|
||||
UnknownEra(TinyStr16, &'static str),
|
||||
/// Unknown month code for a given calendar
|
||||
#[displaydoc("No month code named {0} for calendar {1}")]
|
||||
UnknownMonthCode(TinyStr4, &'static str),
|
||||
/// Missing required input field for formatting
|
||||
#[displaydoc("No value for {0}")]
|
||||
MissingInput(&'static str),
|
||||
/// No support for a given calendar in AnyCalendar
|
||||
#[displaydoc("AnyCalendar does not support calendar {0}")]
|
||||
UnknownAnyCalendarKind(TinyStr16),
|
||||
/// An operation required a calendar but a calendar was not provided.
|
||||
#[displaydoc("An operation required a calendar but a calendar was not provided")]
|
||||
MissingCalendar,
|
||||
/// An error originating inside of the [data provider](icu_provider).
|
||||
#[displaydoc("{0}")]
|
||||
Data(DataError),
|
||||
}
|
||||
|
||||
impl From<core::num::ParseIntError> for CalendarError {
|
||||
fn from(_: core::num::ParseIntError) -> Self {
|
||||
CalendarError::Parse
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DataError> for CalendarError {
|
||||
fn from(e: DataError) -> Self {
|
||||
CalendarError::Data(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl CalendarError {
|
||||
/// Create an error when an [`AnyCalendarKind`] is expected but not available.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use icu_calendar::AnyCalendarKind;
|
||||
/// use icu_calendar::CalendarError;
|
||||
///
|
||||
/// let cal_str = "maori";
|
||||
///
|
||||
/// AnyCalendarKind::get_for_bcp47_string(cal_str)
|
||||
/// .ok_or_else(|| CalendarError::unknown_any_calendar_kind(cal_str))
|
||||
/// .expect_err("Māori calendar is not yet supported");
|
||||
/// ```
|
||||
///
|
||||
/// [`AnyCalendarKind`]: crate::AnyCalendarKind
|
||||
pub fn unknown_any_calendar_kind(description: impl Writeable) -> Self {
|
||||
let tiny = description
|
||||
.write_to_string()
|
||||
.get(0..16)
|
||||
.and_then(|x| TinyStr16::from_str(x).ok())
|
||||
.unwrap_or(tinystr!(16, "invalid"));
|
||||
Self::UnknownAnyCalendarKind(tiny)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,433 @@
|
|||
// This file is part of ICU4X. For terms of use, please see the file
|
||||
// called LICENSE at the top level of the ICU4X source tree
|
||||
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
|
||||
|
||||
//! This module contains types and implementations for the Ethiopian calendar.
|
||||
//!
|
||||
//! ```rust
|
||||
//! use icu::calendar::{ethiopian::Ethiopian, Date, DateTime};
|
||||
//!
|
||||
//! // `Date` type
|
||||
//! let date_iso = Date::try_new_iso_date(1970, 1, 2)
|
||||
//! .expect("Failed to initialize ISO Date instance.");
|
||||
//! let date_ethiopian = Date::new_from_iso(date_iso, Ethiopian::new());
|
||||
//!
|
||||
//! // `DateTime` type
|
||||
//! let datetime_iso = DateTime::try_new_iso_datetime(1970, 1, 2, 13, 1, 0)
|
||||
//! .expect("Failed to initialize ISO DateTime instance.");
|
||||
//! let datetime_ethiopian =
|
||||
//! DateTime::new_from_iso(datetime_iso, Ethiopian::new());
|
||||
//!
|
||||
//! // `Date` checks
|
||||
//! assert_eq!(date_ethiopian.year().number, 1962);
|
||||
//! assert_eq!(date_ethiopian.month().ordinal, 4);
|
||||
//! assert_eq!(date_ethiopian.day_of_month().0, 24);
|
||||
//!
|
||||
//! // `DateTime` type
|
||||
//! assert_eq!(datetime_ethiopian.date.year().number, 1962);
|
||||
//! assert_eq!(datetime_ethiopian.date.month().ordinal, 4);
|
||||
//! assert_eq!(datetime_ethiopian.date.day_of_month().0, 24);
|
||||
//! assert_eq!(datetime_ethiopian.time.hour.number(), 13);
|
||||
//! assert_eq!(datetime_ethiopian.time.minute.number(), 1);
|
||||
//! assert_eq!(datetime_ethiopian.time.second.number(), 0);
|
||||
//! ```
|
||||
|
||||
use crate::any_calendar::AnyCalendarKind;
|
||||
use crate::calendar_arithmetic::{ArithmeticDate, CalendarArithmetic};
|
||||
use crate::iso::Iso;
|
||||
use crate::{types, Calendar, CalendarError, Date, DateDuration, DateDurationUnit, DateTime};
|
||||
use calendrical_calculations::helpers::I32CastError;
|
||||
use calendrical_calculations::rata_die::RataDie;
|
||||
use tinystr::tinystr;
|
||||
|
||||
/// The number of years the Amete Alem epoch precedes the Amete Mihret epoch
|
||||
const AMETE_ALEM_OFFSET: i32 = 5500;
|
||||
|
||||
/// Which era style the ethiopian calendar uses
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum EthiopianEraStyle {
|
||||
/// Use an era scheme of pre- and post- Incarnation eras,
|
||||
/// anchored at the date of the Incarnation of Jesus in this calendar
|
||||
AmeteMihret,
|
||||
/// Use an era scheme of the Anno Mundi era, anchored at the date of Creation
|
||||
/// in this calendar
|
||||
AmeteAlem,
|
||||
}
|
||||
|
||||
/// The [Ethiopian Calendar]
|
||||
///
|
||||
/// The [Ethiopian calendar] is a solar calendar used by the Coptic Orthodox Church, with twelve normal months
|
||||
/// and a thirteenth small epagomenal month.
|
||||
///
|
||||
/// This type can be used with [`Date`] or [`DateTime`] to represent dates in this calendar.
|
||||
///
|
||||
/// It can be constructed in two modes: using the Amete Alem era scheme, or the Amete Mihret era scheme (the default),
|
||||
/// see [`EthiopianEraStyle`] for more info.
|
||||
///
|
||||
/// [Ethiopian calendar]: https://en.wikipedia.org/wiki/Ethiopian_calendar
|
||||
///
|
||||
/// # Era codes
|
||||
///
|
||||
/// This calendar supports three era codes, based on what mode it is in. In the Amete Mihret scheme it has
|
||||
/// the `"incar"` and `"pre-incar"` eras, 1 Incarnation is 9 CE. In the Amete Alem scheme, it instead has a single era,
|
||||
/// `"mundi`, where 1 Anno Mundi is 5493 BCE. Dates before that use negative year numbers.
|
||||
///
|
||||
/// # Month codes
|
||||
///
|
||||
/// This calendar supports 13 solar month codes (`"M01" - "M13"`), with `"M13"` being used for the short epagomenal month
|
||||
/// at the end of the year.
|
||||
// The bool specifies whether dates should be in the Amete Alem era scheme
|
||||
#[derive(Copy, Clone, Debug, Hash, Default, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub struct Ethiopian(pub(crate) bool);
|
||||
|
||||
/// The inner date type used for representing [`Date`]s of [`Ethiopian`]. See [`Date`] and [`Ethiopian`] for more details.
|
||||
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub struct EthiopianDateInner(ArithmeticDate<Ethiopian>);
|
||||
|
||||
impl CalendarArithmetic for Ethiopian {
|
||||
fn month_days(year: i32, month: u8) -> u8 {
|
||||
if (1..=12).contains(&month) {
|
||||
30
|
||||
} else if month == 13 {
|
||||
if Self::is_leap_year(year) {
|
||||
6
|
||||
} else {
|
||||
5
|
||||
}
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
fn months_for_every_year(_: i32) -> u8 {
|
||||
13
|
||||
}
|
||||
|
||||
fn is_leap_year(year: i32) -> bool {
|
||||
year % 4 == 3
|
||||
}
|
||||
|
||||
fn last_month_day_in_year(year: i32) -> (u8, u8) {
|
||||
if Self::is_leap_year(year) {
|
||||
(13, 6)
|
||||
} else {
|
||||
(13, 5)
|
||||
}
|
||||
}
|
||||
|
||||
fn days_in_provided_year(year: i32) -> u16 {
|
||||
if Self::is_leap_year(year) {
|
||||
366
|
||||
} else {
|
||||
365
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Calendar for Ethiopian {
|
||||
type DateInner = EthiopianDateInner;
|
||||
fn date_from_codes(
|
||||
&self,
|
||||
era: types::Era,
|
||||
year: i32,
|
||||
month_code: types::MonthCode,
|
||||
day: u8,
|
||||
) -> Result<Self::DateInner, CalendarError> {
|
||||
let year = if era.0 == tinystr!(16, "incar") {
|
||||
if year <= 0 {
|
||||
return Err(CalendarError::OutOfRange);
|
||||
}
|
||||
year
|
||||
} else if era.0 == tinystr!(16, "pre-incar") {
|
||||
if year <= 0 {
|
||||
return Err(CalendarError::OutOfRange);
|
||||
}
|
||||
1 - year
|
||||
} else if era.0 == tinystr!(16, "mundi") {
|
||||
year - AMETE_ALEM_OFFSET
|
||||
} else {
|
||||
return Err(CalendarError::UnknownEra(era.0, self.debug_name()));
|
||||
};
|
||||
|
||||
ArithmeticDate::new_from_codes(self, year, month_code, day).map(EthiopianDateInner)
|
||||
}
|
||||
fn date_from_iso(&self, iso: Date<Iso>) -> EthiopianDateInner {
|
||||
let fixed_iso = Iso::fixed_from_iso(*iso.inner());
|
||||
Self::ethiopian_from_fixed(fixed_iso)
|
||||
}
|
||||
|
||||
fn date_to_iso(&self, date: &Self::DateInner) -> Date<Iso> {
|
||||
let fixed_ethiopian = Ethiopian::fixed_from_ethiopian(date.0);
|
||||
Iso::iso_from_fixed(fixed_ethiopian)
|
||||
}
|
||||
|
||||
fn months_in_year(&self, date: &Self::DateInner) -> u8 {
|
||||
date.0.months_in_year()
|
||||
}
|
||||
|
||||
fn days_in_year(&self, date: &Self::DateInner) -> u16 {
|
||||
date.0.days_in_year()
|
||||
}
|
||||
|
||||
fn days_in_month(&self, date: &Self::DateInner) -> u8 {
|
||||
date.0.days_in_month()
|
||||
}
|
||||
|
||||
fn day_of_week(&self, date: &Self::DateInner) -> types::IsoWeekday {
|
||||
Iso.day_of_week(self.date_to_iso(date).inner())
|
||||
}
|
||||
|
||||
fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration<Self>) {
|
||||
date.0.offset_date(offset);
|
||||
}
|
||||
|
||||
#[allow(clippy::field_reassign_with_default)]
|
||||
fn until(
|
||||
&self,
|
||||
date1: &Self::DateInner,
|
||||
date2: &Self::DateInner,
|
||||
_calendar2: &Self,
|
||||
_largest_unit: DateDurationUnit,
|
||||
_smallest_unit: DateDurationUnit,
|
||||
) -> DateDuration<Self> {
|
||||
date1.0.until(date2.0, _largest_unit, _smallest_unit)
|
||||
}
|
||||
|
||||
fn year(&self, date: &Self::DateInner) -> types::FormattableYear {
|
||||
Self::year_as_ethiopian(date.0.year, self.0)
|
||||
}
|
||||
|
||||
fn is_in_leap_year(&self, date: &Self::DateInner) -> bool {
|
||||
Self::is_leap_year(date.0.year)
|
||||
}
|
||||
|
||||
fn month(&self, date: &Self::DateInner) -> types::FormattableMonth {
|
||||
date.0.month()
|
||||
}
|
||||
|
||||
fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth {
|
||||
date.0.day_of_month()
|
||||
}
|
||||
|
||||
fn day_of_year_info(&self, date: &Self::DateInner) -> types::DayOfYearInfo {
|
||||
let prev_year = date.0.year - 1;
|
||||
let next_year = date.0.year + 1;
|
||||
types::DayOfYearInfo {
|
||||
day_of_year: date.0.day_of_year(),
|
||||
days_in_year: date.0.days_in_year(),
|
||||
prev_year: Self::year_as_ethiopian(prev_year, self.0),
|
||||
days_in_prev_year: Ethiopian::days_in_year_direct(prev_year),
|
||||
next_year: Self::year_as_ethiopian(next_year, self.0),
|
||||
}
|
||||
}
|
||||
|
||||
fn debug_name(&self) -> &'static str {
|
||||
"Ethiopian"
|
||||
}
|
||||
|
||||
fn any_calendar_kind(&self) -> Option<AnyCalendarKind> {
|
||||
if self.0 {
|
||||
Some(AnyCalendarKind::EthiopianAmeteAlem)
|
||||
} else {
|
||||
Some(AnyCalendarKind::Ethiopian)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Ethiopian {
|
||||
/// Construct a new Ethiopian Calendar for the Amete Mihret era naming scheme
|
||||
pub const fn new() -> Self {
|
||||
Self(false)
|
||||
}
|
||||
/// Construct a new Ethiopian Calendar with a value specifying whether or not it is Amete Alem
|
||||
pub const fn new_with_era_style(era_style: EthiopianEraStyle) -> Self {
|
||||
Self(matches!(era_style, EthiopianEraStyle::AmeteAlem))
|
||||
}
|
||||
/// Set whether or not this uses the Amete Alem era scheme
|
||||
pub fn set_era_style(&mut self, era_style: EthiopianEraStyle) {
|
||||
self.0 = era_style == EthiopianEraStyle::AmeteAlem
|
||||
}
|
||||
|
||||
/// Returns whether this has the Amete Alem era
|
||||
pub fn era_style(&self) -> EthiopianEraStyle {
|
||||
if self.0 {
|
||||
EthiopianEraStyle::AmeteAlem
|
||||
} else {
|
||||
EthiopianEraStyle::AmeteMihret
|
||||
}
|
||||
}
|
||||
|
||||
fn fixed_from_ethiopian(date: ArithmeticDate<Ethiopian>) -> RataDie {
|
||||
calendrical_calculations::ethiopian::fixed_from_ethiopian(date.year, date.month, date.day)
|
||||
}
|
||||
|
||||
fn ethiopian_from_fixed(date: RataDie) -> EthiopianDateInner {
|
||||
let (year, month, day) =
|
||||
match calendrical_calculations::ethiopian::ethiopian_from_fixed(date) {
|
||||
Err(I32CastError::BelowMin) => {
|
||||
return EthiopianDateInner(ArithmeticDate::min_date())
|
||||
}
|
||||
Err(I32CastError::AboveMax) => {
|
||||
return EthiopianDateInner(ArithmeticDate::max_date())
|
||||
}
|
||||
Ok(ymd) => ymd,
|
||||
};
|
||||
EthiopianDateInner(ArithmeticDate::new_unchecked(year, month, day))
|
||||
}
|
||||
|
||||
fn days_in_year_direct(year: i32) -> u16 {
|
||||
if Ethiopian::is_leap_year(year) {
|
||||
366
|
||||
} else {
|
||||
365
|
||||
}
|
||||
}
|
||||
|
||||
fn year_as_ethiopian(year: i32, amete_alem: bool) -> types::FormattableYear {
|
||||
if amete_alem {
|
||||
types::FormattableYear {
|
||||
era: types::Era(tinystr!(16, "mundi")),
|
||||
number: year + AMETE_ALEM_OFFSET,
|
||||
cyclic: None,
|
||||
related_iso: None,
|
||||
}
|
||||
} else if year > 0 {
|
||||
types::FormattableYear {
|
||||
era: types::Era(tinystr!(16, "incar")),
|
||||
number: year,
|
||||
cyclic: None,
|
||||
related_iso: None,
|
||||
}
|
||||
} else {
|
||||
types::FormattableYear {
|
||||
era: types::Era(tinystr!(16, "pre-incar")),
|
||||
number: 1 - year,
|
||||
cyclic: None,
|
||||
related_iso: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Date<Ethiopian> {
|
||||
/// Construct new Ethiopian Date.
|
||||
///
|
||||
/// For the Amete Mihret era style, negative years work with
|
||||
/// year 0 as 1 pre-Incarnation, year -1 as 2 pre-Incarnation,
|
||||
/// and so on.
|
||||
///
|
||||
/// ```rust
|
||||
/// use icu::calendar::ethiopian::EthiopianEraStyle;
|
||||
/// use icu::calendar::Date;
|
||||
///
|
||||
/// let date_ethiopian = Date::try_new_ethiopian_date(
|
||||
/// EthiopianEraStyle::AmeteMihret,
|
||||
/// 2014,
|
||||
/// 8,
|
||||
/// 25,
|
||||
/// )
|
||||
/// .expect("Failed to initialize Ethopic Date instance.");
|
||||
///
|
||||
/// assert_eq!(date_ethiopian.year().number, 2014);
|
||||
/// assert_eq!(date_ethiopian.month().ordinal, 8);
|
||||
/// assert_eq!(date_ethiopian.day_of_month().0, 25);
|
||||
/// ```
|
||||
pub fn try_new_ethiopian_date(
|
||||
era_style: EthiopianEraStyle,
|
||||
mut year: i32,
|
||||
month: u8,
|
||||
day: u8,
|
||||
) -> Result<Date<Ethiopian>, CalendarError> {
|
||||
if era_style == EthiopianEraStyle::AmeteAlem {
|
||||
year -= AMETE_ALEM_OFFSET;
|
||||
}
|
||||
ArithmeticDate::new_from_ordinals(year, month, day)
|
||||
.map(EthiopianDateInner)
|
||||
.map(|inner| Date::from_raw(inner, Ethiopian::new_with_era_style(era_style)))
|
||||
}
|
||||
}
|
||||
|
||||
impl DateTime<Ethiopian> {
|
||||
/// Construct a new Ethiopian datetime from integers.
|
||||
///
|
||||
/// For the Amete Mihret era style, negative years work with
|
||||
/// year 0 as 1 pre-Incarnation, year -1 as 2 pre-Incarnation,
|
||||
/// and so on.
|
||||
///
|
||||
/// ```rust
|
||||
/// use icu::calendar::ethiopian::EthiopianEraStyle;
|
||||
/// use icu::calendar::DateTime;
|
||||
///
|
||||
/// let datetime_ethiopian = DateTime::try_new_ethiopian_datetime(
|
||||
/// EthiopianEraStyle::AmeteMihret,
|
||||
/// 2014,
|
||||
/// 8,
|
||||
/// 25,
|
||||
/// 13,
|
||||
/// 1,
|
||||
/// 0,
|
||||
/// )
|
||||
/// .expect("Failed to initialize Ethiopian DateTime instance.");
|
||||
///
|
||||
/// assert_eq!(datetime_ethiopian.date.year().number, 2014);
|
||||
/// assert_eq!(datetime_ethiopian.date.month().ordinal, 8);
|
||||
/// assert_eq!(datetime_ethiopian.date.day_of_month().0, 25);
|
||||
/// assert_eq!(datetime_ethiopian.time.hour.number(), 13);
|
||||
/// assert_eq!(datetime_ethiopian.time.minute.number(), 1);
|
||||
/// assert_eq!(datetime_ethiopian.time.second.number(), 0);
|
||||
/// ```
|
||||
pub fn try_new_ethiopian_datetime(
|
||||
era_style: EthiopianEraStyle,
|
||||
year: i32,
|
||||
month: u8,
|
||||
day: u8,
|
||||
hour: u8,
|
||||
minute: u8,
|
||||
second: u8,
|
||||
) -> Result<DateTime<Ethiopian>, CalendarError> {
|
||||
Ok(DateTime {
|
||||
date: Date::try_new_ethiopian_date(era_style, year, month, day)?,
|
||||
time: types::Time::try_new(hour, minute, second, 0)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_leap_year() {
|
||||
// 11th September 2023 in gregorian is 6/13/2015 in ethiopian
|
||||
let iso_date = Date::try_new_iso_date(2023, 9, 11).unwrap();
|
||||
let ethiopian_date = Ethiopian::new().date_from_iso(iso_date);
|
||||
assert_eq!(ethiopian_date.0.year, 2015);
|
||||
assert_eq!(ethiopian_date.0.month, 13);
|
||||
assert_eq!(ethiopian_date.0.day, 6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iso_to_ethiopian_conversion_and_back() {
|
||||
let iso_date = Date::try_new_iso_date(1970, 1, 2).unwrap();
|
||||
let date_ethiopian = Date::new_from_iso(iso_date, Ethiopian::new());
|
||||
|
||||
assert_eq!(date_ethiopian.inner.0.year, 1962);
|
||||
assert_eq!(date_ethiopian.inner.0.month, 4);
|
||||
assert_eq!(date_ethiopian.inner.0.day, 24);
|
||||
|
||||
assert_eq!(
|
||||
date_ethiopian.to_iso(),
|
||||
Date::try_new_iso_date(1970, 1, 2).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_roundtrip_negative() {
|
||||
// https://github.com/unicode-org/icu4x/issues/2254
|
||||
let iso_date = Date::try_new_iso_date(-1000, 3, 3).unwrap();
|
||||
let ethiopian = iso_date.to_calendar(Ethiopian::new());
|
||||
let recovered_iso = ethiopian.to_iso();
|
||||
assert_eq!(iso_date, recovered_iso);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,650 @@
|
|||
// This file is part of ICU4X. For terms of use, please see the file
|
||||
// called LICENSE at the top level of the ICU4X source tree
|
||||
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
|
||||
|
||||
//! This module contains types and implementations for the Gregorian calendar.
|
||||
//!
|
||||
//! ```rust
|
||||
//! use icu::calendar::{gregorian::Gregorian, Date, DateTime};
|
||||
//!
|
||||
//! // `Date` type
|
||||
//! let date_iso = Date::try_new_iso_date(1970, 1, 2)
|
||||
//! .expect("Failed to initialize ISO Date instance.");
|
||||
//! let date_gregorian = Date::new_from_iso(date_iso, Gregorian);
|
||||
//!
|
||||
//! // `DateTime` type
|
||||
//! let datetime_iso = DateTime::try_new_iso_datetime(1970, 1, 2, 13, 1, 0)
|
||||
//! .expect("Failed to initialize ISO DateTime instance.");
|
||||
//! let datetime_gregorian = DateTime::new_from_iso(datetime_iso, Gregorian);
|
||||
//!
|
||||
//! // `Date` checks
|
||||
//! assert_eq!(date_gregorian.year().number, 1970);
|
||||
//! assert_eq!(date_gregorian.month().ordinal, 1);
|
||||
//! assert_eq!(date_gregorian.day_of_month().0, 2);
|
||||
//!
|
||||
//! // `DateTime` checks
|
||||
//! assert_eq!(datetime_gregorian.date.year().number, 1970);
|
||||
//! assert_eq!(datetime_gregorian.date.month().ordinal, 1);
|
||||
//! assert_eq!(datetime_gregorian.date.day_of_month().0, 2);
|
||||
//! assert_eq!(datetime_gregorian.time.hour.number(), 13);
|
||||
//! assert_eq!(datetime_gregorian.time.minute.number(), 1);
|
||||
//! assert_eq!(datetime_gregorian.time.second.number(), 0);
|
||||
//! ```
|
||||
|
||||
use crate::any_calendar::AnyCalendarKind;
|
||||
use crate::calendar_arithmetic::ArithmeticDate;
|
||||
use crate::iso::{Iso, IsoDateInner};
|
||||
use crate::{types, Calendar, CalendarError, Date, DateDuration, DateDurationUnit, DateTime};
|
||||
use tinystr::tinystr;
|
||||
|
||||
/// The Gregorian Calendar
|
||||
///
|
||||
/// The [Gregorian calendar] is a solar calendar used by most of the world, with twelve months.
|
||||
///
|
||||
/// This type can be used with [`Date`] or [`DateTime`] to represent dates in this calendar.
|
||||
///
|
||||
/// [Gregorian calendar]: https://en.wikipedia.org/wiki/Gregorian_calendar
|
||||
///
|
||||
/// # Era codes
|
||||
///
|
||||
/// This calendar supports two era codes: `"bce"`, and `"ce"`, corresponding to the BCE and CE eras
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
#[allow(clippy::exhaustive_structs)] // this type is stable
|
||||
pub struct Gregorian;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
|
||||
/// The inner date type used for representing [`Date`]s of [`Gregorian`]. See [`Date`] and [`Gregorian`] for more details.
|
||||
pub struct GregorianDateInner(IsoDateInner);
|
||||
|
||||
impl Calendar for Gregorian {
|
||||
type DateInner = GregorianDateInner;
|
||||
fn date_from_codes(
|
||||
&self,
|
||||
era: types::Era,
|
||||
year: i32,
|
||||
month_code: types::MonthCode,
|
||||
day: u8,
|
||||
) -> Result<Self::DateInner, CalendarError> {
|
||||
let year = if era.0 == tinystr!(16, "ce") {
|
||||
if year <= 0 {
|
||||
return Err(CalendarError::OutOfRange);
|
||||
}
|
||||
year
|
||||
} else if era.0 == tinystr!(16, "bce") {
|
||||
if year <= 0 {
|
||||
return Err(CalendarError::OutOfRange);
|
||||
}
|
||||
1 - year
|
||||
} else {
|
||||
return Err(CalendarError::UnknownEra(era.0, self.debug_name()));
|
||||
};
|
||||
|
||||
ArithmeticDate::new_from_codes(self, year, month_code, day)
|
||||
.map(IsoDateInner)
|
||||
.map(GregorianDateInner)
|
||||
}
|
||||
|
||||
fn date_from_iso(&self, iso: Date<Iso>) -> GregorianDateInner {
|
||||
GregorianDateInner(*iso.inner())
|
||||
}
|
||||
|
||||
fn date_to_iso(&self, date: &Self::DateInner) -> Date<Iso> {
|
||||
Date::from_raw(date.0, Iso)
|
||||
}
|
||||
|
||||
fn months_in_year(&self, date: &Self::DateInner) -> u8 {
|
||||
Iso.months_in_year(&date.0)
|
||||
}
|
||||
|
||||
fn days_in_year(&self, date: &Self::DateInner) -> u16 {
|
||||
Iso.days_in_year(&date.0)
|
||||
}
|
||||
|
||||
fn days_in_month(&self, date: &Self::DateInner) -> u8 {
|
||||
Iso.days_in_month(&date.0)
|
||||
}
|
||||
|
||||
fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration<Self>) {
|
||||
Iso.offset_date(&mut date.0, offset.cast_unit())
|
||||
}
|
||||
|
||||
#[allow(clippy::field_reassign_with_default)] // it's more clear this way
|
||||
fn until(
|
||||
&self,
|
||||
date1: &Self::DateInner,
|
||||
date2: &Self::DateInner,
|
||||
_calendar2: &Self,
|
||||
largest_unit: DateDurationUnit,
|
||||
smallest_unit: DateDurationUnit,
|
||||
) -> DateDuration<Self> {
|
||||
Iso.until(&date1.0, &date2.0, &Iso, largest_unit, smallest_unit)
|
||||
.cast_unit()
|
||||
}
|
||||
|
||||
/// The calendar-specific year represented by `date`
|
||||
fn year(&self, date: &Self::DateInner) -> types::FormattableYear {
|
||||
year_as_gregorian(date.0 .0.year)
|
||||
}
|
||||
|
||||
fn is_in_leap_year(&self, date: &Self::DateInner) -> bool {
|
||||
Iso.is_in_leap_year(&date.0)
|
||||
}
|
||||
|
||||
/// The calendar-specific month represented by `date`
|
||||
fn month(&self, date: &Self::DateInner) -> types::FormattableMonth {
|
||||
Iso.month(&date.0)
|
||||
}
|
||||
|
||||
/// The calendar-specific day-of-month represented by `date`
|
||||
fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth {
|
||||
Iso.day_of_month(&date.0)
|
||||
}
|
||||
|
||||
/// Information of the day of the year
|
||||
fn day_of_year_info(&self, date: &Self::DateInner) -> types::DayOfYearInfo {
|
||||
let prev_year = date.0 .0.year.saturating_sub(1);
|
||||
let next_year = date.0 .0.year.saturating_add(1);
|
||||
types::DayOfYearInfo {
|
||||
day_of_year: Iso::day_of_year(date.0),
|
||||
days_in_year: Iso::days_in_year_direct(date.0 .0.year),
|
||||
prev_year: year_as_gregorian(prev_year),
|
||||
days_in_prev_year: Iso::days_in_year_direct(prev_year),
|
||||
next_year: year_as_gregorian(next_year),
|
||||
}
|
||||
}
|
||||
|
||||
fn debug_name(&self) -> &'static str {
|
||||
"Gregorian"
|
||||
}
|
||||
|
||||
fn any_calendar_kind(&self) -> Option<AnyCalendarKind> {
|
||||
Some(AnyCalendarKind::Gregorian)
|
||||
}
|
||||
}
|
||||
|
||||
impl Date<Gregorian> {
|
||||
/// Construct a new Gregorian Date.
|
||||
///
|
||||
/// Years are specified as ISO years.
|
||||
///
|
||||
/// ```rust
|
||||
/// use icu::calendar::Date;
|
||||
///
|
||||
/// // Conversion from ISO to Gregorian
|
||||
/// let date_gregorian = Date::try_new_gregorian_date(1970, 1, 2)
|
||||
/// .expect("Failed to initialize Gregorian Date instance.");
|
||||
///
|
||||
/// assert_eq!(date_gregorian.year().number, 1970);
|
||||
/// assert_eq!(date_gregorian.month().ordinal, 1);
|
||||
/// assert_eq!(date_gregorian.day_of_month().0, 2);
|
||||
/// ```
|
||||
pub fn try_new_gregorian_date(
|
||||
year: i32,
|
||||
month: u8,
|
||||
day: u8,
|
||||
) -> Result<Date<Gregorian>, CalendarError> {
|
||||
Date::try_new_iso_date(year, month, day).map(|d| Date::new_from_iso(d, Gregorian))
|
||||
}
|
||||
}
|
||||
|
||||
impl DateTime<Gregorian> {
|
||||
/// Construct a new Gregorian datetime from integers.
|
||||
///
|
||||
/// Years are specified as ISO years.
|
||||
///
|
||||
/// ```rust
|
||||
/// use icu::calendar::DateTime;
|
||||
///
|
||||
/// let datetime_gregorian =
|
||||
/// DateTime::try_new_gregorian_datetime(1970, 1, 2, 13, 1, 0)
|
||||
/// .expect("Failed to initialize Gregorian DateTime instance.");
|
||||
///
|
||||
/// assert_eq!(datetime_gregorian.date.year().number, 1970);
|
||||
/// assert_eq!(datetime_gregorian.date.month().ordinal, 1);
|
||||
/// assert_eq!(datetime_gregorian.date.day_of_month().0, 2);
|
||||
/// assert_eq!(datetime_gregorian.time.hour.number(), 13);
|
||||
/// assert_eq!(datetime_gregorian.time.minute.number(), 1);
|
||||
/// assert_eq!(datetime_gregorian.time.second.number(), 0);
|
||||
/// ```
|
||||
pub fn try_new_gregorian_datetime(
|
||||
year: i32,
|
||||
month: u8,
|
||||
day: u8,
|
||||
hour: u8,
|
||||
minute: u8,
|
||||
second: u8,
|
||||
) -> Result<DateTime<Gregorian>, CalendarError> {
|
||||
Ok(DateTime {
|
||||
date: Date::try_new_gregorian_date(year, month, day)?,
|
||||
time: types::Time::try_new(hour, minute, second, 0)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn year_as_gregorian(year: i32) -> types::FormattableYear {
|
||||
if year > 0 {
|
||||
types::FormattableYear {
|
||||
era: types::Era(tinystr!(16, "ce")),
|
||||
number: year,
|
||||
cyclic: None,
|
||||
related_iso: None,
|
||||
}
|
||||
} else {
|
||||
types::FormattableYear {
|
||||
era: types::Era(tinystr!(16, "bce")),
|
||||
number: 1_i32.saturating_sub(year),
|
||||
cyclic: None,
|
||||
related_iso: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use calendrical_calculations::rata_die::RataDie;
|
||||
|
||||
use super::*;
|
||||
use types::Era;
|
||||
|
||||
#[test]
|
||||
fn day_of_year_info_max() {
|
||||
#[derive(Debug)]
|
||||
struct MaxCase {
|
||||
year: i32,
|
||||
month: u8,
|
||||
day: u8,
|
||||
next_era_year: i32,
|
||||
era: &'static str,
|
||||
}
|
||||
let cases = [
|
||||
MaxCase {
|
||||
year: i32::MAX,
|
||||
month: 7,
|
||||
day: 11,
|
||||
next_era_year: i32::MAX,
|
||||
era: "ce",
|
||||
},
|
||||
MaxCase {
|
||||
year: i32::MAX,
|
||||
month: 7,
|
||||
day: 12,
|
||||
next_era_year: i32::MAX,
|
||||
era: "ce",
|
||||
},
|
||||
MaxCase {
|
||||
year: i32::MAX,
|
||||
month: 8,
|
||||
day: 10,
|
||||
next_era_year: i32::MAX,
|
||||
era: "ce",
|
||||
},
|
||||
MaxCase {
|
||||
year: i32::MAX - 1,
|
||||
month: 7,
|
||||
day: 11,
|
||||
next_era_year: i32::MAX,
|
||||
era: "ce",
|
||||
},
|
||||
MaxCase {
|
||||
year: -2,
|
||||
month: 1,
|
||||
day: 1,
|
||||
next_era_year: 2,
|
||||
era: "bce",
|
||||
},
|
||||
MaxCase {
|
||||
year: -1,
|
||||
month: 1,
|
||||
day: 1,
|
||||
next_era_year: 1,
|
||||
era: "bce",
|
||||
},
|
||||
MaxCase {
|
||||
year: 0,
|
||||
month: 1,
|
||||
day: 1,
|
||||
next_era_year: 1,
|
||||
era: "ce",
|
||||
},
|
||||
MaxCase {
|
||||
year: 1,
|
||||
month: 1,
|
||||
day: 1,
|
||||
next_era_year: 2,
|
||||
era: "ce",
|
||||
},
|
||||
MaxCase {
|
||||
year: 2000,
|
||||
month: 6,
|
||||
day: 15,
|
||||
next_era_year: 2001,
|
||||
era: "ce",
|
||||
},
|
||||
MaxCase {
|
||||
year: 2020,
|
||||
month: 12,
|
||||
day: 31,
|
||||
next_era_year: 2021,
|
||||
era: "ce",
|
||||
},
|
||||
];
|
||||
|
||||
for case in cases {
|
||||
let date = Date::try_new_gregorian_date(case.year, case.month, case.day).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
Calendar::day_of_year_info(&Gregorian, &date.inner)
|
||||
.next_year
|
||||
.number,
|
||||
case.next_era_year,
|
||||
"{case:?}",
|
||||
);
|
||||
assert_eq!(
|
||||
Calendar::day_of_year_info(&Gregorian, &date.inner)
|
||||
.next_year
|
||||
.era
|
||||
.0,
|
||||
case.era,
|
||||
"{case:?}",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TestCase {
|
||||
fixed_date: RataDie,
|
||||
iso_year: i32,
|
||||
iso_month: u8,
|
||||
iso_day: u8,
|
||||
expected_year: i32,
|
||||
expected_era: Era,
|
||||
expected_month: u32,
|
||||
expected_day: u32,
|
||||
}
|
||||
|
||||
fn check_test_case(case: TestCase) {
|
||||
let iso_from_fixed: Date<Iso> = Iso::iso_from_fixed(case.fixed_date);
|
||||
let greg_date_from_fixed: Date<Gregorian> = Date::new_from_iso(iso_from_fixed, Gregorian);
|
||||
assert_eq!(greg_date_from_fixed.year().number, case.expected_year,
|
||||
"Failed year check from fixed: {case:?}\nISO: {iso_from_fixed:?}\nGreg: {greg_date_from_fixed:?}");
|
||||
assert_eq!(greg_date_from_fixed.year().era, case.expected_era,
|
||||
"Failed era check from fixed: {case:?}\nISO: {iso_from_fixed:?}\nGreg: {greg_date_from_fixed:?}");
|
||||
assert_eq!(greg_date_from_fixed.month().ordinal, case.expected_month,
|
||||
"Failed month check from fixed: {case:?}\nISO: {iso_from_fixed:?}\nGreg: {greg_date_from_fixed:?}");
|
||||
assert_eq!(greg_date_from_fixed.day_of_month().0, case.expected_day,
|
||||
"Failed day check from fixed: {case:?}\nISO: {iso_from_fixed:?}\nGreg: {greg_date_from_fixed:?}");
|
||||
|
||||
let iso_date_man: Date<Iso> =
|
||||
Date::try_new_iso_date(case.iso_year, case.iso_month, case.iso_day)
|
||||
.expect("Failed to initialize ISO date for {case:?}");
|
||||
let greg_date_man: Date<Gregorian> = Date::new_from_iso(iso_date_man, Gregorian);
|
||||
assert_eq!(iso_from_fixed, iso_date_man,
|
||||
"ISO from fixed not equal to ISO generated from manually-input ymd\nCase: {case:?}\nFixed: {iso_from_fixed:?}\nMan: {iso_date_man:?}");
|
||||
assert_eq!(greg_date_from_fixed, greg_date_man,
|
||||
"Greg. date from fixed not equal to Greg. generated from manually-input ymd\nCase: {case:?}\nFixed: {greg_date_from_fixed:?}\nMan: {greg_date_man:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gregorian_ce() {
|
||||
// Tests that the Gregorian calendar gives the correct expected
|
||||
// day, month, and year for positive years (AD/CE/gregory era)
|
||||
|
||||
let cases = [
|
||||
TestCase {
|
||||
fixed_date: RataDie::new(1),
|
||||
iso_year: 1,
|
||||
iso_month: 1,
|
||||
iso_day: 1,
|
||||
expected_year: 1,
|
||||
expected_era: Era(tinystr!(16, "ce")),
|
||||
expected_month: 1,
|
||||
expected_day: 1,
|
||||
},
|
||||
TestCase {
|
||||
fixed_date: RataDie::new(181),
|
||||
iso_year: 1,
|
||||
iso_month: 6,
|
||||
iso_day: 30,
|
||||
expected_year: 1,
|
||||
expected_era: Era(tinystr!(16, "ce")),
|
||||
expected_month: 6,
|
||||
expected_day: 30,
|
||||
},
|
||||
TestCase {
|
||||
fixed_date: RataDie::new(1155),
|
||||
iso_year: 4,
|
||||
iso_month: 2,
|
||||
iso_day: 29,
|
||||
expected_year: 4,
|
||||
expected_era: Era(tinystr!(16, "ce")),
|
||||
expected_month: 2,
|
||||
expected_day: 29,
|
||||
},
|
||||
TestCase {
|
||||
fixed_date: RataDie::new(1344),
|
||||
iso_year: 4,
|
||||
iso_month: 9,
|
||||
iso_day: 5,
|
||||
expected_year: 4,
|
||||
expected_era: Era(tinystr!(16, "ce")),
|
||||
expected_month: 9,
|
||||
expected_day: 5,
|
||||
},
|
||||
TestCase {
|
||||
fixed_date: RataDie::new(36219),
|
||||
iso_year: 100,
|
||||
iso_month: 3,
|
||||
iso_day: 1,
|
||||
expected_year: 100,
|
||||
expected_era: Era(tinystr!(16, "ce")),
|
||||
expected_month: 3,
|
||||
expected_day: 1,
|
||||
},
|
||||
];
|
||||
|
||||
for case in cases {
|
||||
check_test_case(case);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn day_of_year_info_min() {
|
||||
#[derive(Debug)]
|
||||
struct MinCase {
|
||||
year: i32,
|
||||
month: u8,
|
||||
day: u8,
|
||||
prev_era_year: i32,
|
||||
era: &'static str,
|
||||
}
|
||||
let cases = [
|
||||
MinCase {
|
||||
year: i32::MIN + 4,
|
||||
month: 1,
|
||||
day: 1,
|
||||
prev_era_year: i32::MAX - 1,
|
||||
era: "bce",
|
||||
},
|
||||
MinCase {
|
||||
year: i32::MIN + 3,
|
||||
month: 12,
|
||||
day: 31,
|
||||
prev_era_year: i32::MAX,
|
||||
era: "bce",
|
||||
},
|
||||
MinCase {
|
||||
year: i32::MIN + 2,
|
||||
month: 2,
|
||||
day: 2,
|
||||
prev_era_year: i32::MAX,
|
||||
era: "bce",
|
||||
},
|
||||
MinCase {
|
||||
year: i32::MIN + 1,
|
||||
month: 1,
|
||||
day: 1,
|
||||
prev_era_year: i32::MAX,
|
||||
era: "bce",
|
||||
},
|
||||
MinCase {
|
||||
year: i32::MIN,
|
||||
month: 1,
|
||||
day: 1,
|
||||
prev_era_year: i32::MAX,
|
||||
era: "bce",
|
||||
},
|
||||
MinCase {
|
||||
year: 3,
|
||||
month: 1,
|
||||
day: 1,
|
||||
prev_era_year: 2,
|
||||
era: "ce",
|
||||
},
|
||||
MinCase {
|
||||
year: 2,
|
||||
month: 1,
|
||||
day: 1,
|
||||
prev_era_year: 1,
|
||||
era: "ce",
|
||||
},
|
||||
MinCase {
|
||||
year: 1,
|
||||
month: 1,
|
||||
day: 1,
|
||||
prev_era_year: 1,
|
||||
era: "bce",
|
||||
},
|
||||
MinCase {
|
||||
year: 0,
|
||||
month: 1,
|
||||
day: 1,
|
||||
prev_era_year: 2,
|
||||
era: "bce",
|
||||
},
|
||||
MinCase {
|
||||
year: -2000,
|
||||
month: 6,
|
||||
day: 15,
|
||||
prev_era_year: 2002,
|
||||
era: "bce",
|
||||
},
|
||||
MinCase {
|
||||
year: 2020,
|
||||
month: 12,
|
||||
day: 31,
|
||||
prev_era_year: 2019,
|
||||
era: "ce",
|
||||
},
|
||||
];
|
||||
|
||||
for case in cases {
|
||||
let date = Date::try_new_gregorian_date(case.year, case.month, case.day).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
Calendar::day_of_year_info(&Gregorian, &date.inner)
|
||||
.prev_year
|
||||
.number,
|
||||
case.prev_era_year,
|
||||
"{case:?}",
|
||||
);
|
||||
assert_eq!(
|
||||
Calendar::day_of_year_info(&Gregorian, &date.inner)
|
||||
.prev_year
|
||||
.era
|
||||
.0,
|
||||
case.era,
|
||||
"{case:?}",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gregorian_bce() {
|
||||
// Tests that the Gregorian calendar gives the correct expected
|
||||
// day, month, and year for negative years (BC/BCE/pre-gregory era)
|
||||
|
||||
let cases = [
|
||||
TestCase {
|
||||
fixed_date: RataDie::new(0),
|
||||
iso_year: 0,
|
||||
iso_month: 12,
|
||||
iso_day: 31,
|
||||
expected_year: 1,
|
||||
expected_era: Era(tinystr!(16, "bce")),
|
||||
expected_month: 12,
|
||||
expected_day: 31,
|
||||
},
|
||||
TestCase {
|
||||
fixed_date: RataDie::new(-365), // This is a leap year
|
||||
iso_year: 0,
|
||||
iso_month: 1,
|
||||
iso_day: 1,
|
||||
expected_year: 1,
|
||||
expected_era: Era(tinystr!(16, "bce")),
|
||||
expected_month: 1,
|
||||
expected_day: 1,
|
||||
},
|
||||
TestCase {
|
||||
fixed_date: RataDie::new(-366),
|
||||
iso_year: -1,
|
||||
iso_month: 12,
|
||||
iso_day: 31,
|
||||
expected_year: 2,
|
||||
expected_era: Era(tinystr!(16, "bce")),
|
||||
expected_month: 12,
|
||||
expected_day: 31,
|
||||
},
|
||||
TestCase {
|
||||
fixed_date: RataDie::new(-1461),
|
||||
iso_year: -4,
|
||||
iso_month: 12,
|
||||
iso_day: 31,
|
||||
expected_year: 5,
|
||||
expected_era: Era(tinystr!(16, "bce")),
|
||||
expected_month: 12,
|
||||
expected_day: 31,
|
||||
},
|
||||
TestCase {
|
||||
fixed_date: RataDie::new(-1826),
|
||||
iso_year: -4,
|
||||
iso_month: 1,
|
||||
iso_day: 1,
|
||||
expected_year: 5,
|
||||
expected_era: Era(tinystr!(16, "bce")),
|
||||
expected_month: 1,
|
||||
expected_day: 1,
|
||||
},
|
||||
];
|
||||
|
||||
for case in cases {
|
||||
check_test_case(case);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_gregorian_directionality() {
|
||||
// Tests that for a large range of fixed dates, if a fixed date
|
||||
// is less than another, the corresponding YMD should also be less
|
||||
// than the other, without exception.
|
||||
for i in -100..100 {
|
||||
for j in -100..100 {
|
||||
let iso_i: Date<Iso> = Iso::iso_from_fixed(RataDie::new(i));
|
||||
let iso_j: Date<Iso> = Iso::iso_from_fixed(RataDie::new(j));
|
||||
|
||||
let greg_i: Date<Gregorian> = Date::new_from_iso(iso_i, Gregorian);
|
||||
let greg_j: Date<Gregorian> = Date::new_from_iso(iso_j, Gregorian);
|
||||
|
||||
assert_eq!(
|
||||
i.cmp(&j),
|
||||
iso_i.cmp(&iso_j),
|
||||
"ISO directionality inconsistent with directionality for i: {i}, j: {j}"
|
||||
);
|
||||
assert_eq!(
|
||||
i.cmp(&j),
|
||||
greg_i.cmp(&greg_j),
|
||||
"Gregorian directionality inconsistent with directionality for i: {i}, j: {j}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,619 @@
|
|||
// This file is part of ICU4X. For terms of use, please see the file
|
||||
// called LICENSE at the top level of the ICU4X source tree
|
||||
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
|
||||
|
||||
//! This module contains types and implementations for the Hebrew calendar.
|
||||
//!
|
||||
//! ```rust
|
||||
//! use icu::calendar::{hebrew::Hebrew, Date, DateTime, Ref};
|
||||
//!
|
||||
//! let hebrew = Hebrew::new_always_calculating();
|
||||
//! let hebrew = Ref(&hebrew); // to avoid cloning
|
||||
//!
|
||||
//! // `Date` type
|
||||
//! let hebrew_date =
|
||||
//! Date::try_new_hebrew_date_with_calendar(3425, 10, 11, hebrew)
|
||||
//! .expect("Failed to initialize hebrew Date instance.");
|
||||
//!
|
||||
//! // `DateTime` type
|
||||
//! let hebrew_datetime = DateTime::try_new_hebrew_datetime_with_calendar(
|
||||
//! 3425, 10, 11, 13, 1, 0, hebrew,
|
||||
//! )
|
||||
//! .expect("Failed to initialize hebrew DateTime instance.");
|
||||
//!
|
||||
//! // `Date` checks
|
||||
//! assert_eq!(hebrew_date.year().number, 3425);
|
||||
//! assert_eq!(hebrew_date.month().ordinal, 10);
|
||||
//! assert_eq!(hebrew_date.day_of_month().0, 11);
|
||||
//!
|
||||
//! // `DateTime` checks
|
||||
//! assert_eq!(hebrew_datetime.date.year().number, 3425);
|
||||
//! assert_eq!(hebrew_datetime.date.month().ordinal, 10);
|
||||
//! assert_eq!(hebrew_datetime.date.day_of_month().0, 11);
|
||||
//! assert_eq!(hebrew_datetime.time.hour.number(), 13);
|
||||
//! assert_eq!(hebrew_datetime.time.minute.number(), 1);
|
||||
//! assert_eq!(hebrew_datetime.time.second.number(), 0);
|
||||
//! ```
|
||||
|
||||
use crate::calendar_arithmetic::{ArithmeticDate, CalendarArithmetic};
|
||||
use crate::types::FormattableMonth;
|
||||
use crate::AnyCalendarKind;
|
||||
use crate::AsCalendar;
|
||||
use crate::Iso;
|
||||
use crate::{types, Calendar, CalendarError, Date, DateDuration, DateDurationUnit, DateTime};
|
||||
use ::tinystr::tinystr;
|
||||
use calendrical_calculations::hebrew::BookHebrew;
|
||||
use calendrical_calculations::rata_die::RataDie;
|
||||
|
||||
/// The Civil Hebrew Calendar
|
||||
///
|
||||
/// The [Hebrew calendar] is a lunisolar calendar used as the Jewish liturgical calendar
|
||||
/// as well as an official calendar in Israel.
|
||||
///
|
||||
/// This calendar is the _civil_ Hebrew calendar, with the year starting at in the month of Tishrei.
|
||||
///
|
||||
/// # Era codes
|
||||
///
|
||||
/// This calendar supports a single era code, Anno Mundi, with code `"am"`
|
||||
///
|
||||
/// # Month codes
|
||||
///
|
||||
/// This calendar is a lunisolar calendar and thus has a leap month. It supports codes `"M01"-"M12"`
|
||||
/// for regular months, and the leap month Adar I being coded as `"M05L"`.
|
||||
///
|
||||
/// [`FormattableMonth`] has slightly divergent behavior: because the regular month Adar is formatted
|
||||
/// as "Adar II" in a leap year, this calendar will produce the special code `"M06L"` in any [`FormattableMonth`]
|
||||
/// objects it creates.
|
||||
///
|
||||
/// [Hebrew calendar]: https://en.wikipedia.org/wiki/Hebrew_calendar
|
||||
#[derive(Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
|
||||
#[non_exhaustive] // we'll be adding precompiled data to this
|
||||
pub struct Hebrew;
|
||||
|
||||
/// The inner date type used for representing [`Date`]s of [`BookHebrew`]. See [`Date`] and [`BookHebrew`] for more details.
|
||||
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
|
||||
struct BookHebrewDateInner;
|
||||
/// The inner date type used for representing [`Date`]s of [`Hebrew`]. See [`Date`] and [`Hebrew`] for more details.
|
||||
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub struct HebrewDateInner(ArithmeticDate<Hebrew>);
|
||||
|
||||
impl Hebrew {
|
||||
/// Construct a new [`Hebrew`] without any precomputed calendrical calculations.
|
||||
///
|
||||
/// This is the only mode currently possible, but once precomputing is available (#3933)
|
||||
/// there will be additional constructors that load from data providers.
|
||||
pub fn new_always_calculating() -> Self {
|
||||
Hebrew
|
||||
}
|
||||
}
|
||||
|
||||
// HEBREW CALENDAR
|
||||
|
||||
impl CalendarArithmetic for Hebrew {
|
||||
fn month_days(civil_year: i32, civil_month: u8) -> u8 {
|
||||
Self::last_day_of_civil_hebrew_month(civil_year, civil_month)
|
||||
}
|
||||
|
||||
fn months_for_every_year(civil_year: i32) -> u8 {
|
||||
Self::last_month_of_civil_hebrew_year(civil_year)
|
||||
}
|
||||
|
||||
fn days_in_provided_year(civil_year: i32) -> u16 {
|
||||
BookHebrew::days_in_book_hebrew_year(civil_year) // number of days don't change between BookHebrew and Civil Hebrew
|
||||
}
|
||||
|
||||
fn is_leap_year(civil_year: i32) -> bool {
|
||||
// civil and book years are the same
|
||||
BookHebrew::is_hebrew_leap_year(civil_year)
|
||||
}
|
||||
|
||||
fn last_month_day_in_year(civil_year: i32) -> (u8, u8) {
|
||||
let civil_month = Self::last_month_of_civil_hebrew_year(civil_year);
|
||||
let civil_day = Self::last_day_of_civil_hebrew_month(civil_year, civil_month);
|
||||
|
||||
(civil_month, civil_day)
|
||||
}
|
||||
}
|
||||
|
||||
impl Calendar for Hebrew {
|
||||
type DateInner = HebrewDateInner;
|
||||
|
||||
fn date_from_codes(
|
||||
&self,
|
||||
era: types::Era,
|
||||
year: i32,
|
||||
month_code: types::MonthCode,
|
||||
day: u8,
|
||||
) -> Result<Self::DateInner, CalendarError> {
|
||||
let is_leap_year = Self::is_leap_year(year);
|
||||
let year = if era.0 == tinystr!(16, "hebrew") || era.0 == tinystr!(16, "am") {
|
||||
year
|
||||
} else {
|
||||
return Err(CalendarError::UnknownEra(era.0, self.debug_name()));
|
||||
};
|
||||
|
||||
let month_code_str = month_code.0.as_str();
|
||||
|
||||
let month_ordinal = if is_leap_year {
|
||||
match month_code_str {
|
||||
"M01" => 1,
|
||||
"M02" => 2,
|
||||
"M03" => 3,
|
||||
"M04" => 4,
|
||||
"M05" => 5,
|
||||
"M05L" => 6,
|
||||
// M06L is the formatting era code used for Adar II
|
||||
"M06" | "M06L" => 7,
|
||||
"M07" => 8,
|
||||
"M08" => 9,
|
||||
"M09" => 10,
|
||||
"M10" => 11,
|
||||
"M11" => 12,
|
||||
"M12" => 13,
|
||||
_ => {
|
||||
return Err(CalendarError::UnknownMonthCode(
|
||||
month_code.0,
|
||||
self.debug_name(),
|
||||
))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match month_code_str {
|
||||
"M01" => 1,
|
||||
"M02" => 2,
|
||||
"M03" => 3,
|
||||
"M04" => 4,
|
||||
"M05" => 5,
|
||||
"M06" => 6,
|
||||
"M07" => 7,
|
||||
"M08" => 8,
|
||||
"M09" => 9,
|
||||
"M10" => 10,
|
||||
"M11" => 11,
|
||||
"M12" => 12,
|
||||
_ => {
|
||||
return Err(CalendarError::UnknownMonthCode(
|
||||
month_code.0,
|
||||
self.debug_name(),
|
||||
))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ArithmeticDate::new_from_lunar_ordinals(year, month_ordinal, day).map(HebrewDateInner)
|
||||
}
|
||||
|
||||
fn date_from_iso(&self, iso: Date<Iso>) -> Self::DateInner {
|
||||
let fixed_iso = Iso::fixed_from_iso(*iso.inner());
|
||||
Self::civil_hebrew_from_fixed(fixed_iso).inner
|
||||
}
|
||||
|
||||
fn date_to_iso(&self, date: &Self::DateInner) -> Date<Iso> {
|
||||
let fixed_hebrew = Self::fixed_from_civil_hebrew(*date);
|
||||
Iso::iso_from_fixed(fixed_hebrew)
|
||||
}
|
||||
|
||||
fn months_in_year(&self, date: &Self::DateInner) -> u8 {
|
||||
date.0.months_in_year()
|
||||
}
|
||||
|
||||
fn days_in_year(&self, date: &Self::DateInner) -> u16 {
|
||||
date.0.days_in_year()
|
||||
}
|
||||
|
||||
fn days_in_month(&self, date: &Self::DateInner) -> u8 {
|
||||
date.0.days_in_month()
|
||||
}
|
||||
|
||||
fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration<Self>) {
|
||||
date.0.offset_date(offset)
|
||||
}
|
||||
|
||||
fn until(
|
||||
&self,
|
||||
date1: &Self::DateInner,
|
||||
date2: &Self::DateInner,
|
||||
_calendar2: &Self,
|
||||
_largest_unit: DateDurationUnit,
|
||||
_smallest_unit: DateDurationUnit,
|
||||
) -> DateDuration<Self> {
|
||||
date1.0.until(date2.0, _largest_unit, _smallest_unit)
|
||||
}
|
||||
|
||||
fn debug_name(&self) -> &'static str {
|
||||
"Hebrew"
|
||||
}
|
||||
|
||||
fn year(&self, date: &Self::DateInner) -> types::FormattableYear {
|
||||
Self::year_as_hebrew(date.0.year)
|
||||
}
|
||||
|
||||
fn is_in_leap_year(&self, date: &Self::DateInner) -> bool {
|
||||
Self::is_leap_year(date.0.year)
|
||||
}
|
||||
|
||||
fn month(&self, date: &Self::DateInner) -> FormattableMonth {
|
||||
let mut ordinal = date.0.month;
|
||||
let is_leap_year = Self::is_leap_year(date.0.year);
|
||||
|
||||
if is_leap_year {
|
||||
if ordinal == 6 {
|
||||
return types::FormattableMonth {
|
||||
ordinal: ordinal as u32,
|
||||
code: types::MonthCode(tinystr!(4, "M05L")),
|
||||
};
|
||||
} else if ordinal == 7 {
|
||||
return types::FormattableMonth {
|
||||
ordinal: ordinal as u32,
|
||||
code: types::MonthCode(tinystr!(4, "M06L")),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if is_leap_year && ordinal > 6 {
|
||||
ordinal -= 1;
|
||||
}
|
||||
|
||||
let code = match ordinal {
|
||||
1 => tinystr!(4, "M01"),
|
||||
2 => tinystr!(4, "M02"),
|
||||
3 => tinystr!(4, "M03"),
|
||||
4 => tinystr!(4, "M04"),
|
||||
5 => tinystr!(4, "M05"),
|
||||
6 => tinystr!(4, "M06"),
|
||||
7 => tinystr!(4, "M07"),
|
||||
8 => tinystr!(4, "M08"),
|
||||
9 => tinystr!(4, "M09"),
|
||||
10 => tinystr!(4, "M10"),
|
||||
11 => tinystr!(4, "M11"),
|
||||
12 => tinystr!(4, "M12"),
|
||||
_ => tinystr!(4, "und"),
|
||||
};
|
||||
|
||||
types::FormattableMonth {
|
||||
ordinal: date.0.month as u32,
|
||||
code: types::MonthCode(code),
|
||||
}
|
||||
}
|
||||
|
||||
fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth {
|
||||
date.0.day_of_month()
|
||||
}
|
||||
|
||||
fn day_of_year_info(&self, date: &Self::DateInner) -> types::DayOfYearInfo {
|
||||
let prev_year = date.0.year.saturating_sub(1);
|
||||
let next_year = date.0.year.saturating_add(1);
|
||||
types::DayOfYearInfo {
|
||||
day_of_year: date.0.day_of_year(),
|
||||
days_in_year: date.0.days_in_year(),
|
||||
prev_year: Self::year_as_hebrew(prev_year),
|
||||
days_in_prev_year: Self::days_in_provided_year(prev_year),
|
||||
next_year: Self::year_as_hebrew(next_year),
|
||||
}
|
||||
}
|
||||
fn any_calendar_kind(&self) -> Option<AnyCalendarKind> {
|
||||
Some(AnyCalendarKind::Hebrew)
|
||||
}
|
||||
}
|
||||
|
||||
impl Hebrew {
|
||||
// Converts a Biblical Hebrew Date to a Civil Hebrew Date
|
||||
fn biblical_to_civil_date(biblical_date: BookHebrew) -> HebrewDateInner {
|
||||
let (y, m, d) = biblical_date.to_civil_date();
|
||||
|
||||
debug_assert!(ArithmeticDate::<Hebrew>::new_from_lunar_ordinals(y, m, d,).is_ok());
|
||||
HebrewDateInner(ArithmeticDate::new_unchecked(y, m, d))
|
||||
}
|
||||
|
||||
// Converts a Civil Hebrew Date to a Biblical Hebrew Date
|
||||
fn civil_to_biblical_date(civil_date: HebrewDateInner) -> BookHebrew {
|
||||
BookHebrew::from_civil_date(civil_date.0.year, civil_date.0.month, civil_date.0.day)
|
||||
}
|
||||
|
||||
fn last_month_of_civil_hebrew_year(civil_year: i32) -> u8 {
|
||||
if Self::is_leap_year(civil_year) {
|
||||
13 // there are 13 months in a leap year
|
||||
} else {
|
||||
12
|
||||
}
|
||||
}
|
||||
|
||||
fn last_day_of_civil_hebrew_month(civil_year: i32, civil_month: u8) -> u8 {
|
||||
let book_date = Hebrew::civil_to_biblical_date(HebrewDateInner(
|
||||
ArithmeticDate::new_unchecked(civil_year, civil_month, 1),
|
||||
));
|
||||
BookHebrew::last_day_of_book_hebrew_month(book_date.year, book_date.month)
|
||||
}
|
||||
|
||||
// "Fixed" is a day count representation of calendars staring from Jan 1st of year 1 of the Georgian Calendar.
|
||||
fn fixed_from_civil_hebrew(date: HebrewDateInner) -> RataDie {
|
||||
let book_date = Hebrew::civil_to_biblical_date(date);
|
||||
BookHebrew::fixed_from_book_hebrew(book_date)
|
||||
}
|
||||
|
||||
fn civil_hebrew_from_fixed(date: RataDie) -> Date<Hebrew> {
|
||||
let book_hebrew = BookHebrew::book_hebrew_from_fixed(date);
|
||||
Date::from_raw(Hebrew::biblical_to_civil_date(book_hebrew), Hebrew)
|
||||
}
|
||||
|
||||
fn year_as_hebrew(civil_year: i32) -> types::FormattableYear {
|
||||
types::FormattableYear {
|
||||
era: types::Era(tinystr!(16, "hebrew")),
|
||||
number: civil_year,
|
||||
cyclic: None,
|
||||
related_iso: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: AsCalendar<Calendar = Hebrew>> Date<A> {
|
||||
/// Construct new Hebrew Date.
|
||||
///
|
||||
/// This datetime will not use any precomputed calendrical calculations,
|
||||
/// one that loads such data from a provider will be added in the future (#3933)
|
||||
///
|
||||
///
|
||||
/// ```rust
|
||||
/// use icu::calendar::hebrew::Hebrew;
|
||||
/// use icu::calendar::Date;
|
||||
///
|
||||
/// let hebrew = Hebrew::new_always_calculating();
|
||||
///
|
||||
/// let date_hebrew =
|
||||
/// Date::try_new_hebrew_date_with_calendar(3425, 4, 25, hebrew)
|
||||
/// .expect("Failed to initialize Hebrew Date instance.");
|
||||
///
|
||||
/// assert_eq!(date_hebrew.year().number, 3425);
|
||||
/// assert_eq!(date_hebrew.month().ordinal, 4);
|
||||
/// assert_eq!(date_hebrew.day_of_month().0, 25);
|
||||
/// ```
|
||||
pub fn try_new_hebrew_date_with_calendar(
|
||||
year: i32,
|
||||
month: u8,
|
||||
day: u8,
|
||||
calendar: A,
|
||||
) -> Result<Date<A>, CalendarError> {
|
||||
ArithmeticDate::new_from_lunar_ordinals(year, month, day)
|
||||
.map(HebrewDateInner)
|
||||
.map(|inner| Date::from_raw(inner, calendar))
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: AsCalendar<Calendar = Hebrew>> DateTime<A> {
|
||||
/// Construct a new Hebrew datetime from integers.
|
||||
///
|
||||
/// This datetime will not use any precomputed calendrical calculations,
|
||||
/// one that loads such data from a provider will be added in the future (#3933)
|
||||
///
|
||||
/// ```rust
|
||||
/// use icu::calendar::hebrew::Hebrew;
|
||||
/// use icu::calendar::DateTime;
|
||||
///
|
||||
/// let hebrew = Hebrew::new_always_calculating();
|
||||
///
|
||||
/// let datetime_hebrew = DateTime::try_new_hebrew_datetime_with_calendar(
|
||||
/// 4201, 10, 11, 13, 1, 0, hebrew,
|
||||
/// )
|
||||
/// .expect("Failed to initialize Hebrew DateTime instance");
|
||||
///
|
||||
/// assert_eq!(datetime_hebrew.date.year().number, 4201);
|
||||
/// assert_eq!(datetime_hebrew.date.month().ordinal, 10);
|
||||
/// assert_eq!(datetime_hebrew.date.day_of_month().0, 11);
|
||||
/// assert_eq!(datetime_hebrew.time.hour.number(), 13);
|
||||
/// assert_eq!(datetime_hebrew.time.minute.number(), 1);
|
||||
/// assert_eq!(datetime_hebrew.time.second.number(), 0);
|
||||
/// ```
|
||||
pub fn try_new_hebrew_datetime_with_calendar(
|
||||
year: i32,
|
||||
month: u8,
|
||||
day: u8,
|
||||
hour: u8,
|
||||
minute: u8,
|
||||
second: u8,
|
||||
calendar: A,
|
||||
) -> Result<DateTime<A>, CalendarError> {
|
||||
Ok(DateTime {
|
||||
date: Date::try_new_hebrew_date_with_calendar(year, month, day, calendar)?,
|
||||
time: types::Time::try_new(hour, minute, second, 0)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
use calendrical_calculations::hebrew::*;
|
||||
|
||||
#[test]
|
||||
fn test_conversions() {
|
||||
let iso_dates: [Date<Iso>; 48] = [
|
||||
Date::try_new_iso_date(2021, 1, 10).unwrap(),
|
||||
Date::try_new_iso_date(2021, 1, 25).unwrap(),
|
||||
Date::try_new_iso_date(2021, 2, 10).unwrap(),
|
||||
Date::try_new_iso_date(2021, 2, 25).unwrap(),
|
||||
Date::try_new_iso_date(2021, 3, 10).unwrap(),
|
||||
Date::try_new_iso_date(2021, 3, 25).unwrap(),
|
||||
Date::try_new_iso_date(2021, 4, 10).unwrap(),
|
||||
Date::try_new_iso_date(2021, 4, 25).unwrap(),
|
||||
Date::try_new_iso_date(2021, 5, 10).unwrap(),
|
||||
Date::try_new_iso_date(2021, 5, 25).unwrap(),
|
||||
Date::try_new_iso_date(2021, 6, 10).unwrap(),
|
||||
Date::try_new_iso_date(2021, 6, 25).unwrap(),
|
||||
Date::try_new_iso_date(2021, 7, 10).unwrap(),
|
||||
Date::try_new_iso_date(2021, 7, 25).unwrap(),
|
||||
Date::try_new_iso_date(2021, 8, 10).unwrap(),
|
||||
Date::try_new_iso_date(2021, 8, 25).unwrap(),
|
||||
Date::try_new_iso_date(2021, 9, 10).unwrap(),
|
||||
Date::try_new_iso_date(2021, 9, 25).unwrap(),
|
||||
Date::try_new_iso_date(2021, 10, 10).unwrap(),
|
||||
Date::try_new_iso_date(2021, 10, 25).unwrap(),
|
||||
Date::try_new_iso_date(2021, 11, 10).unwrap(),
|
||||
Date::try_new_iso_date(2021, 11, 25).unwrap(),
|
||||
Date::try_new_iso_date(2021, 12, 10).unwrap(),
|
||||
Date::try_new_iso_date(2021, 12, 25).unwrap(),
|
||||
Date::try_new_iso_date(2022, 1, 10).unwrap(),
|
||||
Date::try_new_iso_date(2022, 1, 25).unwrap(),
|
||||
Date::try_new_iso_date(2022, 2, 10).unwrap(),
|
||||
Date::try_new_iso_date(2022, 2, 25).unwrap(),
|
||||
Date::try_new_iso_date(2022, 3, 10).unwrap(),
|
||||
Date::try_new_iso_date(2022, 3, 25).unwrap(),
|
||||
Date::try_new_iso_date(2022, 4, 10).unwrap(),
|
||||
Date::try_new_iso_date(2022, 4, 25).unwrap(),
|
||||
Date::try_new_iso_date(2022, 5, 10).unwrap(),
|
||||
Date::try_new_iso_date(2022, 5, 25).unwrap(),
|
||||
Date::try_new_iso_date(2022, 6, 10).unwrap(),
|
||||
Date::try_new_iso_date(2022, 6, 25).unwrap(),
|
||||
Date::try_new_iso_date(2022, 7, 10).unwrap(),
|
||||
Date::try_new_iso_date(2022, 7, 25).unwrap(),
|
||||
Date::try_new_iso_date(2022, 8, 10).unwrap(),
|
||||
Date::try_new_iso_date(2022, 8, 25).unwrap(),
|
||||
Date::try_new_iso_date(2022, 9, 10).unwrap(),
|
||||
Date::try_new_iso_date(2022, 9, 25).unwrap(),
|
||||
Date::try_new_iso_date(2022, 10, 10).unwrap(),
|
||||
Date::try_new_iso_date(2022, 10, 25).unwrap(),
|
||||
Date::try_new_iso_date(2022, 11, 10).unwrap(),
|
||||
Date::try_new_iso_date(2022, 11, 25).unwrap(),
|
||||
Date::try_new_iso_date(2022, 12, 10).unwrap(),
|
||||
Date::try_new_iso_date(2022, 12, 25).unwrap(),
|
||||
];
|
||||
|
||||
let book_hebrew_dates: [(u8, u8, i32); 48] = [
|
||||
(26, TEVET, 5781),
|
||||
(12, SHEVAT, 5781),
|
||||
(28, SHEVAT, 5781),
|
||||
(13, ADAR, 5781),
|
||||
(26, ADAR, 5781),
|
||||
(12, NISAN, 5781),
|
||||
(28, NISAN, 5781),
|
||||
(13, IYYAR, 5781),
|
||||
(28, IYYAR, 5781),
|
||||
(14, SIVAN, 5781),
|
||||
(30, SIVAN, 5781),
|
||||
(15, TAMMUZ, 5781),
|
||||
(1, AV, 5781),
|
||||
(16, AV, 5781),
|
||||
(2, ELUL, 5781),
|
||||
(17, ELUL, 5781),
|
||||
(4, TISHRI, 5782),
|
||||
(19, TISHRI, 5782),
|
||||
(4, MARHESHVAN, 5782),
|
||||
(19, MARHESHVAN, 5782),
|
||||
(6, KISLEV, 5782),
|
||||
(21, KISLEV, 5782),
|
||||
(6, TEVET, 5782),
|
||||
(21, TEVET, 5782),
|
||||
(8, SHEVAT, 5782),
|
||||
(23, SHEVAT, 5782),
|
||||
(9, ADAR, 5782),
|
||||
(24, ADAR, 5782),
|
||||
(7, ADARII, 5782),
|
||||
(22, ADARII, 5782),
|
||||
(9, NISAN, 5782),
|
||||
(24, NISAN, 5782),
|
||||
(9, IYYAR, 5782),
|
||||
(24, IYYAR, 5782),
|
||||
(11, SIVAN, 5782),
|
||||
(26, SIVAN, 5782),
|
||||
(11, TAMMUZ, 5782),
|
||||
(26, TAMMUZ, 5782),
|
||||
(13, AV, 5782),
|
||||
(28, AV, 5782),
|
||||
(14, ELUL, 5782),
|
||||
(29, ELUL, 5782),
|
||||
(15, TISHRI, 5783),
|
||||
(30, TISHRI, 5783),
|
||||
(16, MARHESHVAN, 5783),
|
||||
(1, KISLEV, 5783),
|
||||
(16, KISLEV, 5783),
|
||||
(1, TEVET, 5783),
|
||||
];
|
||||
|
||||
let civil_hebrew_dates: [(u8, u8, i32); 48] = [
|
||||
(26, 4, 5781),
|
||||
(12, 5, 5781),
|
||||
(28, 5, 5781),
|
||||
(13, 6, 5781),
|
||||
(26, 6, 5781),
|
||||
(12, 7, 5781),
|
||||
(28, 7, 5781),
|
||||
(13, 8, 5781),
|
||||
(28, 8, 5781),
|
||||
(14, 9, 5781),
|
||||
(30, 9, 5781),
|
||||
(15, 10, 5781),
|
||||
(1, 11, 5781),
|
||||
(16, 11, 5781),
|
||||
(2, 12, 5781),
|
||||
(17, 12, 5781),
|
||||
(4, 1, 5782),
|
||||
(19, 1, 5782),
|
||||
(4, 2, 5782),
|
||||
(19, 2, 5782),
|
||||
(6, 3, 5782),
|
||||
(21, 3, 5782),
|
||||
(6, 4, 5782),
|
||||
(21, 4, 5782),
|
||||
(8, 5, 5782),
|
||||
(23, 5, 5782),
|
||||
(9, 6, 5782),
|
||||
(24, 6, 5782),
|
||||
(7, 7, 5782),
|
||||
(22, 7, 5782),
|
||||
(9, 8, 5782),
|
||||
(24, 8, 5782),
|
||||
(9, 9, 5782),
|
||||
(24, 9, 5782),
|
||||
(11, 10, 5782),
|
||||
(26, 10, 5782),
|
||||
(11, 11, 5782),
|
||||
(26, 11, 5782),
|
||||
(13, 12, 5782),
|
||||
(28, 12, 5782),
|
||||
(14, 13, 5782),
|
||||
(29, 13, 5782),
|
||||
(15, 1, 5783),
|
||||
(30, 1, 5783),
|
||||
(16, 2, 5783),
|
||||
(1, 3, 5783),
|
||||
(16, 3, 5783),
|
||||
(1, 4, 5783),
|
||||
];
|
||||
|
||||
for (iso_date, (book_date_nums, civil_date_nums)) in iso_dates
|
||||
.iter()
|
||||
.zip(book_hebrew_dates.iter().zip(civil_hebrew_dates.iter()))
|
||||
{
|
||||
let book_date = BookHebrew {
|
||||
year: book_date_nums.2,
|
||||
month: book_date_nums.1,
|
||||
day: book_date_nums.0,
|
||||
};
|
||||
let civil_date: HebrewDateInner = HebrewDateInner(ArithmeticDate::new_unchecked(
|
||||
civil_date_nums.2,
|
||||
civil_date_nums.1,
|
||||
civil_date_nums.0,
|
||||
));
|
||||
|
||||
let book_to_civil = Hebrew::biblical_to_civil_date(book_date);
|
||||
let civil_to_book = Hebrew::civil_to_biblical_date(civil_date);
|
||||
|
||||
assert_eq!(civil_date, book_to_civil);
|
||||
assert_eq!(book_date, civil_to_book);
|
||||
|
||||
let iso_to_fixed = Iso::fixed_from_iso(iso_date.inner);
|
||||
let fixed_to_hebrew = Hebrew::civil_hebrew_from_fixed(iso_to_fixed);
|
||||
|
||||
let hebrew_to_fixed = Hebrew::fixed_from_civil_hebrew(civil_date);
|
||||
let fixed_to_iso = Iso::iso_from_fixed(hebrew_to_fixed);
|
||||
|
||||
assert_eq!(fixed_to_hebrew.inner, civil_date);
|
||||
assert_eq!(fixed_to_iso.inner, iso_date.inner);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_icu_bug_22441() {
|
||||
assert_eq!(BookHebrew::days_in_book_hebrew_year(88369), 383);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,577 @@
|
|||
// This file is part of ICU4X. For terms of use, please see the file
|
||||
// called LICENSE at the top level of the ICU4X source tree
|
||||
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
|
||||
|
||||
//! This module contains types and implementations for the Indian national calendar.
|
||||
//!
|
||||
//! ```rust
|
||||
//! use icu::calendar::{indian::Indian, Date, DateTime};
|
||||
//!
|
||||
//! // `Date` type
|
||||
//! let date_iso = Date::try_new_iso_date(1970, 1, 2)
|
||||
//! .expect("Failed to initialize ISO Date instance.");
|
||||
//! let date_indian = Date::new_from_iso(date_iso, Indian);
|
||||
//!
|
||||
//! // `DateTime` type
|
||||
//! let datetime_iso = DateTime::try_new_iso_datetime(1970, 1, 2, 13, 1, 0)
|
||||
//! .expect("Failed to initialize ISO DateTime instance.");
|
||||
//! let datetime_indian = DateTime::new_from_iso(datetime_iso, Indian);
|
||||
//!
|
||||
//! // `Date` checks
|
||||
//! assert_eq!(date_indian.year().number, 1891);
|
||||
//! assert_eq!(date_indian.month().ordinal, 10);
|
||||
//! assert_eq!(date_indian.day_of_month().0, 12);
|
||||
//!
|
||||
//! // `DateTime` type
|
||||
//! assert_eq!(datetime_indian.date.year().number, 1891);
|
||||
//! assert_eq!(datetime_indian.date.month().ordinal, 10);
|
||||
//! assert_eq!(datetime_indian.date.day_of_month().0, 12);
|
||||
//! assert_eq!(datetime_indian.time.hour.number(), 13);
|
||||
//! assert_eq!(datetime_indian.time.minute.number(), 1);
|
||||
//! assert_eq!(datetime_indian.time.second.number(), 0);
|
||||
//! ```
|
||||
|
||||
use crate::any_calendar::AnyCalendarKind;
|
||||
use crate::calendar_arithmetic::{ArithmeticDate, CalendarArithmetic};
|
||||
use crate::iso::Iso;
|
||||
use crate::{types, Calendar, CalendarError, Date, DateDuration, DateDurationUnit, DateTime};
|
||||
use tinystr::tinystr;
|
||||
|
||||
/// The Indian National Calendar (aka the Saka calendar)
|
||||
///
|
||||
/// The [Indian National calendar] is a solar calendar used by the Indian government, with twelve months.
|
||||
///
|
||||
/// This type can be used with [`Date`] or [`DateTime`] to represent dates in this calendar.
|
||||
///
|
||||
/// [Indian National calendar]: https://en.wikipedia.org/wiki/Indian_national_calendar
|
||||
///
|
||||
/// # Era codes
|
||||
///
|
||||
/// This calendar has a single era: `"saka"`, with Saka 0 being 78 CE. Dates before this era use negative years.
|
||||
///
|
||||
/// # Month codes
|
||||
///
|
||||
/// This calendar supports 12 solar month codes (`"M01" - "M12"`)
|
||||
#[derive(Copy, Clone, Debug, Hash, Default, Eq, PartialEq, PartialOrd, Ord)]
|
||||
#[allow(clippy::exhaustive_structs)] // this type is stable
|
||||
pub struct Indian;
|
||||
|
||||
/// The inner date type used for representing [`Date`]s of [`Indian`]. See [`Date`] and [`Indian`] for more details.
|
||||
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub struct IndianDateInner(ArithmeticDate<Indian>);
|
||||
|
||||
impl CalendarArithmetic for Indian {
|
||||
fn month_days(year: i32, month: u8) -> u8 {
|
||||
if month == 1 {
|
||||
if Self::is_leap_year(year) {
|
||||
31
|
||||
} else {
|
||||
30
|
||||
}
|
||||
} else if (2..=6).contains(&month) {
|
||||
31
|
||||
} else if (7..=12).contains(&month) {
|
||||
30
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
fn months_for_every_year(_: i32) -> u8 {
|
||||
12
|
||||
}
|
||||
|
||||
fn is_leap_year(year: i32) -> bool {
|
||||
Iso::is_leap_year(year + 78)
|
||||
}
|
||||
|
||||
fn last_month_day_in_year(_year: i32) -> (u8, u8) {
|
||||
(12, 30)
|
||||
}
|
||||
|
||||
fn days_in_provided_year(year: i32) -> u16 {
|
||||
if Self::is_leap_year(year) {
|
||||
366
|
||||
} else {
|
||||
365
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The Saka calendar starts on the 81st day of the Gregorian year (March 22 or 21)
|
||||
/// which is an 80 day offset. This number should be subtracted from Gregorian dates
|
||||
const DAY_OFFSET: u16 = 80;
|
||||
/// The Saka calendar is 78 years behind Gregorian. This number should be added to Gregorian dates
|
||||
const YEAR_OFFSET: i32 = 78;
|
||||
|
||||
impl Calendar for Indian {
|
||||
type DateInner = IndianDateInner;
|
||||
fn date_from_codes(
|
||||
&self,
|
||||
era: types::Era,
|
||||
year: i32,
|
||||
month_code: types::MonthCode,
|
||||
day: u8,
|
||||
) -> Result<Self::DateInner, CalendarError> {
|
||||
if era.0 != tinystr!(16, "saka") && era.0 != tinystr!(16, "indian") {
|
||||
return Err(CalendarError::UnknownEra(era.0, self.debug_name()));
|
||||
}
|
||||
|
||||
ArithmeticDate::new_from_codes(self, year, month_code, day).map(IndianDateInner)
|
||||
}
|
||||
|
||||
// Algorithms directly implemented in icu_calendar since they're not from the book
|
||||
fn date_from_iso(&self, iso: Date<Iso>) -> IndianDateInner {
|
||||
// Get day number in year (1 indexed)
|
||||
let day_of_year_iso = Iso::day_of_year(*iso.inner());
|
||||
// Convert to Saka year
|
||||
let mut year = iso.inner().0.year - YEAR_OFFSET;
|
||||
// This is in the previous Indian year
|
||||
let day_of_year_indian = if day_of_year_iso <= DAY_OFFSET {
|
||||
year -= 1;
|
||||
let n_days = Self::days_in_provided_year(year);
|
||||
|
||||
// calculate day of year in previous year
|
||||
n_days + day_of_year_iso - DAY_OFFSET
|
||||
} else {
|
||||
day_of_year_iso - DAY_OFFSET
|
||||
};
|
||||
IndianDateInner(ArithmeticDate::date_from_year_day(
|
||||
year,
|
||||
day_of_year_indian as u32,
|
||||
))
|
||||
}
|
||||
|
||||
// Algorithms directly implemented in icu_calendar since they're not from the book
|
||||
fn date_to_iso(&self, date: &Self::DateInner) -> Date<Iso> {
|
||||
let day_of_year_indian = date.0.day_of_year();
|
||||
let days_in_year = date.0.days_in_year();
|
||||
|
||||
let mut year = date.0.year + YEAR_OFFSET;
|
||||
let day_of_year_iso = if day_of_year_indian + DAY_OFFSET >= days_in_year {
|
||||
year += 1;
|
||||
// calculate day of year in next year
|
||||
day_of_year_indian + DAY_OFFSET - days_in_year
|
||||
} else {
|
||||
day_of_year_indian + DAY_OFFSET
|
||||
};
|
||||
|
||||
Iso::iso_from_year_day(year, day_of_year_iso)
|
||||
}
|
||||
|
||||
fn months_in_year(&self, date: &Self::DateInner) -> u8 {
|
||||
date.0.months_in_year()
|
||||
}
|
||||
|
||||
fn days_in_year(&self, date: &Self::DateInner) -> u16 {
|
||||
date.0.days_in_year()
|
||||
}
|
||||
|
||||
fn days_in_month(&self, date: &Self::DateInner) -> u8 {
|
||||
date.0.days_in_month()
|
||||
}
|
||||
|
||||
fn day_of_week(&self, date: &Self::DateInner) -> types::IsoWeekday {
|
||||
Iso.day_of_week(Indian.date_to_iso(date).inner())
|
||||
}
|
||||
|
||||
fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration<Self>) {
|
||||
date.0.offset_date(offset);
|
||||
}
|
||||
|
||||
#[allow(clippy::field_reassign_with_default)]
|
||||
fn until(
|
||||
&self,
|
||||
date1: &Self::DateInner,
|
||||
date2: &Self::DateInner,
|
||||
_calendar2: &Self,
|
||||
_largest_unit: DateDurationUnit,
|
||||
_smallest_unit: DateDurationUnit,
|
||||
) -> DateDuration<Self> {
|
||||
date1.0.until(date2.0, _largest_unit, _smallest_unit)
|
||||
}
|
||||
|
||||
fn year(&self, date: &Self::DateInner) -> types::FormattableYear {
|
||||
types::FormattableYear {
|
||||
era: types::Era(tinystr!(16, "saka")),
|
||||
number: date.0.year,
|
||||
cyclic: None,
|
||||
related_iso: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_in_leap_year(&self, date: &Self::DateInner) -> bool {
|
||||
Self::is_leap_year(date.0.year)
|
||||
}
|
||||
|
||||
fn month(&self, date: &Self::DateInner) -> types::FormattableMonth {
|
||||
date.0.month()
|
||||
}
|
||||
|
||||
fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth {
|
||||
date.0.day_of_month()
|
||||
}
|
||||
|
||||
fn day_of_year_info(&self, date: &Self::DateInner) -> types::DayOfYearInfo {
|
||||
let prev_year = types::FormattableYear {
|
||||
era: types::Era(tinystr!(16, "saka")),
|
||||
number: date.0.year - 1,
|
||||
cyclic: None,
|
||||
related_iso: None,
|
||||
};
|
||||
let next_year = types::FormattableYear {
|
||||
era: types::Era(tinystr!(16, "saka")),
|
||||
number: date.0.year + 1,
|
||||
cyclic: None,
|
||||
related_iso: None,
|
||||
};
|
||||
types::DayOfYearInfo {
|
||||
day_of_year: date.0.day_of_year(),
|
||||
days_in_year: date.0.days_in_year(),
|
||||
prev_year,
|
||||
days_in_prev_year: Indian::days_in_year_direct(date.0.year - 1),
|
||||
next_year,
|
||||
}
|
||||
}
|
||||
|
||||
fn debug_name(&self) -> &'static str {
|
||||
"Indian"
|
||||
}
|
||||
|
||||
fn any_calendar_kind(&self) -> Option<AnyCalendarKind> {
|
||||
Some(AnyCalendarKind::Indian)
|
||||
}
|
||||
}
|
||||
|
||||
impl Indian {
|
||||
/// Construct a new Indian Calendar
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn days_in_year_direct(year: i32) -> u16 {
|
||||
if Indian::is_leap_year(year) {
|
||||
366
|
||||
} else {
|
||||
365
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Date<Indian> {
|
||||
/// Construct new Indian Date, with year provided in the Śaka era.
|
||||
///
|
||||
/// ```rust
|
||||
/// use icu::calendar::Date;
|
||||
///
|
||||
/// let date_indian = Date::try_new_indian_date(1891, 10, 12)
|
||||
/// .expect("Failed to initialize Indian Date instance.");
|
||||
///
|
||||
/// assert_eq!(date_indian.year().number, 1891);
|
||||
/// assert_eq!(date_indian.month().ordinal, 10);
|
||||
/// assert_eq!(date_indian.day_of_month().0, 12);
|
||||
/// ```
|
||||
pub fn try_new_indian_date(
|
||||
year: i32,
|
||||
month: u8,
|
||||
day: u8,
|
||||
) -> Result<Date<Indian>, CalendarError> {
|
||||
ArithmeticDate::new_from_ordinals(year, month, day)
|
||||
.map(IndianDateInner)
|
||||
.map(|inner| Date::from_raw(inner, Indian))
|
||||
}
|
||||
}
|
||||
|
||||
impl DateTime<Indian> {
|
||||
/// Construct a new Indian datetime from integers, with year provided in the Śaka era.
|
||||
///
|
||||
/// ```rust
|
||||
/// use icu::calendar::DateTime;
|
||||
///
|
||||
/// let datetime_indian =
|
||||
/// DateTime::try_new_indian_datetime(1891, 10, 12, 13, 1, 0)
|
||||
/// .expect("Failed to initialize Indian DateTime instance.");
|
||||
///
|
||||
/// assert_eq!(datetime_indian.date.year().number, 1891);
|
||||
/// assert_eq!(datetime_indian.date.month().ordinal, 10);
|
||||
/// assert_eq!(datetime_indian.date.day_of_month().0, 12);
|
||||
/// assert_eq!(datetime_indian.time.hour.number(), 13);
|
||||
/// assert_eq!(datetime_indian.time.minute.number(), 1);
|
||||
/// assert_eq!(datetime_indian.time.second.number(), 0);
|
||||
/// ```
|
||||
pub fn try_new_indian_datetime(
|
||||
year: i32,
|
||||
month: u8,
|
||||
day: u8,
|
||||
hour: u8,
|
||||
minute: u8,
|
||||
second: u8,
|
||||
) -> Result<DateTime<Indian>, CalendarError> {
|
||||
Ok(DateTime {
|
||||
date: Date::try_new_indian_date(year, month, day)?,
|
||||
time: types::Time::try_new(hour, minute, second, 0)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use calendrical_calculations::rata_die::RataDie;
|
||||
fn assert_roundtrip(y: i32, m: u8, d: u8, iso_y: i32, iso_m: u8, iso_d: u8) {
|
||||
let indian =
|
||||
Date::try_new_indian_date(y, m, d).expect("Indian date should construct successfully");
|
||||
let iso = indian.to_iso();
|
||||
|
||||
assert_eq!(
|
||||
iso.year().number,
|
||||
iso_y,
|
||||
"{y}-{m}-{d}: ISO year did not match"
|
||||
);
|
||||
assert_eq!(
|
||||
iso.month().ordinal as u8,
|
||||
iso_m,
|
||||
"{y}-{m}-{d}: ISO month did not match"
|
||||
);
|
||||
assert_eq!(
|
||||
iso.day_of_month().0 as u8,
|
||||
iso_d,
|
||||
"{y}-{m}-{d}: ISO day did not match"
|
||||
);
|
||||
|
||||
let roundtrip = iso.to_calendar(Indian);
|
||||
|
||||
assert_eq!(
|
||||
roundtrip.year().number,
|
||||
indian.year().number,
|
||||
"{y}-{m}-{d}: roundtrip year did not match"
|
||||
);
|
||||
assert_eq!(
|
||||
roundtrip.month().ordinal,
|
||||
indian.month().ordinal,
|
||||
"{y}-{m}-{d}: roundtrip month did not match"
|
||||
);
|
||||
assert_eq!(
|
||||
roundtrip.day_of_month(),
|
||||
indian.day_of_month(),
|
||||
"{y}-{m}-{d}: roundtrip day did not match"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn roundtrip_indian() {
|
||||
// Ultimately the day of the year will always be identical regardless of it
|
||||
// being a leap year or not
|
||||
// Test dates that occur after and before Chaitra 1 (March 22/21), in all years of
|
||||
// a four-year leap cycle, to ensure that all code paths are tested
|
||||
assert_roundtrip(1944, 6, 7, 2022, 8, 29);
|
||||
assert_roundtrip(1943, 6, 7, 2021, 8, 29);
|
||||
assert_roundtrip(1942, 6, 7, 2020, 8, 29);
|
||||
assert_roundtrip(1941, 6, 7, 2019, 8, 29);
|
||||
assert_roundtrip(1944, 11, 7, 2023, 1, 27);
|
||||
assert_roundtrip(1943, 11, 7, 2022, 1, 27);
|
||||
assert_roundtrip(1942, 11, 7, 2021, 1, 27);
|
||||
assert_roundtrip(1941, 11, 7, 2020, 1, 27);
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TestCase {
|
||||
iso_year: i32,
|
||||
iso_month: u8,
|
||||
iso_day: u8,
|
||||
expected_year: i32,
|
||||
expected_month: u32,
|
||||
expected_day: u32,
|
||||
}
|
||||
|
||||
fn check_case(case: TestCase) {
|
||||
let iso = Date::try_new_iso_date(case.iso_year, case.iso_month, case.iso_day).unwrap();
|
||||
let saka = iso.to_calendar(Indian);
|
||||
assert_eq!(
|
||||
saka.year().number,
|
||||
case.expected_year,
|
||||
"Year check failed for case: {case:?}"
|
||||
);
|
||||
assert_eq!(
|
||||
saka.month().ordinal,
|
||||
case.expected_month,
|
||||
"Month check failed for case: {case:?}"
|
||||
);
|
||||
assert_eq!(
|
||||
saka.day_of_month().0,
|
||||
case.expected_day,
|
||||
"Day check failed for case: {case:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cases_near_epoch_start() {
|
||||
let cases = [
|
||||
TestCase {
|
||||
iso_year: 79,
|
||||
iso_month: 3,
|
||||
iso_day: 23,
|
||||
expected_year: 1,
|
||||
expected_month: 1,
|
||||
expected_day: 2,
|
||||
},
|
||||
TestCase {
|
||||
iso_year: 79,
|
||||
iso_month: 3,
|
||||
iso_day: 22,
|
||||
expected_year: 1,
|
||||
expected_month: 1,
|
||||
expected_day: 1,
|
||||
},
|
||||
TestCase {
|
||||
iso_year: 79,
|
||||
iso_month: 3,
|
||||
iso_day: 21,
|
||||
expected_year: 0,
|
||||
expected_month: 12,
|
||||
expected_day: 30,
|
||||
},
|
||||
TestCase {
|
||||
iso_year: 79,
|
||||
iso_month: 3,
|
||||
iso_day: 20,
|
||||
expected_year: 0,
|
||||
expected_month: 12,
|
||||
expected_day: 29,
|
||||
},
|
||||
TestCase {
|
||||
iso_year: 78,
|
||||
iso_month: 3,
|
||||
iso_day: 21,
|
||||
expected_year: -1,
|
||||
expected_month: 12,
|
||||
expected_day: 30,
|
||||
},
|
||||
];
|
||||
|
||||
for case in cases {
|
||||
check_case(case);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cases_near_rd_zero() {
|
||||
let cases = [
|
||||
TestCase {
|
||||
iso_year: 1,
|
||||
iso_month: 3,
|
||||
iso_day: 22,
|
||||
expected_year: -77,
|
||||
expected_month: 1,
|
||||
expected_day: 1,
|
||||
},
|
||||
TestCase {
|
||||
iso_year: 1,
|
||||
iso_month: 3,
|
||||
iso_day: 21,
|
||||
expected_year: -78,
|
||||
expected_month: 12,
|
||||
expected_day: 30,
|
||||
},
|
||||
TestCase {
|
||||
iso_year: 1,
|
||||
iso_month: 1,
|
||||
iso_day: 1,
|
||||
expected_year: -78,
|
||||
expected_month: 10,
|
||||
expected_day: 11,
|
||||
},
|
||||
TestCase {
|
||||
iso_year: 0,
|
||||
iso_month: 3,
|
||||
iso_day: 21,
|
||||
expected_year: -78,
|
||||
expected_month: 1,
|
||||
expected_day: 1,
|
||||
},
|
||||
TestCase {
|
||||
iso_year: 0,
|
||||
iso_month: 1,
|
||||
iso_day: 1,
|
||||
expected_year: -79,
|
||||
expected_month: 10,
|
||||
expected_day: 11,
|
||||
},
|
||||
TestCase {
|
||||
iso_year: -1,
|
||||
iso_month: 3,
|
||||
iso_day: 21,
|
||||
expected_year: -80,
|
||||
expected_month: 12,
|
||||
expected_day: 30,
|
||||
},
|
||||
];
|
||||
|
||||
for case in cases {
|
||||
check_case(case);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_roundtrip_near_rd_zero() {
|
||||
for i in -1000..=1000 {
|
||||
let initial = RataDie::new(i);
|
||||
let result = Iso::fixed_from_iso(
|
||||
Iso::iso_from_fixed(initial)
|
||||
.to_calendar(Indian)
|
||||
.to_calendar(Iso)
|
||||
.inner,
|
||||
);
|
||||
assert_eq!(
|
||||
initial, result,
|
||||
"Roundtrip failed for initial: {initial:?}, result: {result:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_roundtrip_near_epoch_start() {
|
||||
// Epoch start: RD 28570
|
||||
for i in 27570..=29570 {
|
||||
let initial = RataDie::new(i);
|
||||
let result = Iso::fixed_from_iso(
|
||||
Iso::iso_from_fixed(initial)
|
||||
.to_calendar(Indian)
|
||||
.to_calendar(Iso)
|
||||
.inner,
|
||||
);
|
||||
assert_eq!(
|
||||
initial, result,
|
||||
"Roundtrip failed for initial: {initial:?}, result: {result:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_directionality_near_rd_zero() {
|
||||
for i in -100..=100 {
|
||||
for j in -100..=100 {
|
||||
let rd_i = RataDie::new(i);
|
||||
let rd_j = RataDie::new(j);
|
||||
|
||||
let indian_i = Iso::iso_from_fixed(rd_i).to_calendar(Indian);
|
||||
let indian_j = Iso::iso_from_fixed(rd_j).to_calendar(Indian);
|
||||
|
||||
assert_eq!(i.cmp(&j), indian_i.cmp(&indian_j), "Directionality test failed for i: {i}, j: {j}, indian_i: {indian_i:?}, indian_j: {indian_j:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_directionality_near_epoch_start() {
|
||||
// Epoch start: RD 28570
|
||||
for i in 28470..=28670 {
|
||||
for j in 28470..=28670 {
|
||||
let indian_i = Iso::iso_from_fixed(RataDie::new(i)).to_calendar(Indian);
|
||||
let indian_j = Iso::iso_from_fixed(RataDie::new(j)).to_calendar(Indian);
|
||||
|
||||
assert_eq!(i.cmp(&j), indian_i.cmp(&indian_j), "Directionality test failed for i: {i}, j: {j}, indian_i: {indian_i:?}, indian_j: {indian_j:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,824 @@
|
|||
// This file is part of ICU4X. For terms of use, please see the file
|
||||
// called LICENSE at the top level of the ICU4X source tree
|
||||
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
|
||||
|
||||
//! This module contains types and implementations for the ISO calendar.
|
||||
//!
|
||||
//! ```rust
|
||||
//! use icu::calendar::{Date, DateTime};
|
||||
//!
|
||||
//! // `Date` type
|
||||
//! let date_iso = Date::try_new_iso_date(1970, 1, 2)
|
||||
//! .expect("Failed to initialize ISO Date instance.");
|
||||
//!
|
||||
//! // `DateTime` type
|
||||
//! let datetime_iso = DateTime::try_new_iso_datetime(1970, 1, 2, 13, 1, 0)
|
||||
//! .expect("Failed to initialize ISO DateTime instance.");
|
||||
//!
|
||||
//! // `Date` checks
|
||||
//! assert_eq!(date_iso.year().number, 1970);
|
||||
//! assert_eq!(date_iso.month().ordinal, 1);
|
||||
//! assert_eq!(date_iso.day_of_month().0, 2);
|
||||
//!
|
||||
//! // `DateTime` type
|
||||
//! assert_eq!(datetime_iso.date.year().number, 1970);
|
||||
//! assert_eq!(datetime_iso.date.month().ordinal, 1);
|
||||
//! assert_eq!(datetime_iso.date.day_of_month().0, 2);
|
||||
//! assert_eq!(datetime_iso.time.hour.number(), 13);
|
||||
//! assert_eq!(datetime_iso.time.minute.number(), 1);
|
||||
//! assert_eq!(datetime_iso.time.second.number(), 0);
|
||||
//! ```
|
||||
|
||||
use crate::any_calendar::AnyCalendarKind;
|
||||
use crate::calendar_arithmetic::{ArithmeticDate, CalendarArithmetic};
|
||||
use crate::{types, Calendar, CalendarError, Date, DateDuration, DateDurationUnit, DateTime};
|
||||
use calendrical_calculations::helpers::{i64_to_saturated_i32, I32CastError};
|
||||
use calendrical_calculations::rata_die::RataDie;
|
||||
use tinystr::tinystr;
|
||||
|
||||
/// The [ISO Calendar]
|
||||
///
|
||||
/// The [ISO Calendar] is a standardized solar calendar with twelve months.
|
||||
/// It is identical to the Gregorian calendar, except it uses negative years for years before 1 CE,
|
||||
/// and may have differing formatting data for a given locale.
|
||||
///
|
||||
/// This type can be used with [`Date`] or [`DateTime`] to represent dates in this calendar.
|
||||
///
|
||||
/// [ISO Calendar]: https://en.wikipedia.org/wiki/ISO_8601#Dates
|
||||
///
|
||||
/// # Era codes
|
||||
///
|
||||
/// This calendar supports one era, `"default"`
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[allow(clippy::exhaustive_structs)] // this type is stable
|
||||
pub struct Iso;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
|
||||
/// The inner date type used for representing [`Date`]s of [`Iso`]. See [`Date`] and [`Iso`] for more details.
|
||||
pub struct IsoDateInner(pub(crate) ArithmeticDate<Iso>);
|
||||
|
||||
impl CalendarArithmetic for Iso {
|
||||
fn month_days(year: i32, month: u8) -> u8 {
|
||||
match month {
|
||||
4 | 6 | 9 | 11 => 30,
|
||||
2 if Self::is_leap_year(year) => 29,
|
||||
2 => 28,
|
||||
1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn months_for_every_year(_: i32) -> u8 {
|
||||
12
|
||||
}
|
||||
|
||||
fn is_leap_year(year: i32) -> bool {
|
||||
calendrical_calculations::iso::is_leap_year(year)
|
||||
}
|
||||
|
||||
fn last_month_day_in_year(_year: i32) -> (u8, u8) {
|
||||
(12, 31)
|
||||
}
|
||||
|
||||
fn days_in_provided_year(year: i32) -> u16 {
|
||||
if Self::is_leap_year(year) {
|
||||
366
|
||||
} else {
|
||||
365
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Calendar for Iso {
|
||||
type DateInner = IsoDateInner;
|
||||
/// Construct a date from era/month codes and fields
|
||||
fn date_from_codes(
|
||||
&self,
|
||||
era: types::Era,
|
||||
year: i32,
|
||||
month_code: types::MonthCode,
|
||||
day: u8,
|
||||
) -> Result<Self::DateInner, CalendarError> {
|
||||
if era.0 != tinystr!(16, "default") {
|
||||
return Err(CalendarError::UnknownEra(era.0, self.debug_name()));
|
||||
}
|
||||
|
||||
ArithmeticDate::new_from_codes(self, year, month_code, day).map(IsoDateInner)
|
||||
}
|
||||
|
||||
fn date_from_iso(&self, iso: Date<Iso>) -> IsoDateInner {
|
||||
*iso.inner()
|
||||
}
|
||||
|
||||
fn date_to_iso(&self, date: &Self::DateInner) -> Date<Iso> {
|
||||
Date::from_raw(*date, Iso)
|
||||
}
|
||||
|
||||
fn months_in_year(&self, date: &Self::DateInner) -> u8 {
|
||||
date.0.months_in_year()
|
||||
}
|
||||
|
||||
fn days_in_year(&self, date: &Self::DateInner) -> u16 {
|
||||
date.0.days_in_year()
|
||||
}
|
||||
|
||||
fn days_in_month(&self, date: &Self::DateInner) -> u8 {
|
||||
date.0.days_in_month()
|
||||
}
|
||||
|
||||
fn day_of_week(&self, date: &Self::DateInner) -> types::IsoWeekday {
|
||||
// For the purposes of the calculation here, Monday is 0, Sunday is 6
|
||||
// ISO has Monday=1, Sunday=7, which we transform in the last step
|
||||
|
||||
// The days of the week are the same every 400 years
|
||||
// so we normalize to the nearest multiple of 400
|
||||
let years_since_400 = date.0.year % 400;
|
||||
let leap_years_since_400 = years_since_400 / 4 - years_since_400 / 100;
|
||||
// The number of days to the current year
|
||||
// Can never cause an overflow because years_since_400 has a maximum value of 399.
|
||||
let days_to_current_year = 365 * years_since_400 + leap_years_since_400;
|
||||
// The weekday offset from January 1 this year and January 1 2000
|
||||
let year_offset = days_to_current_year % 7;
|
||||
|
||||
// Corresponding months from
|
||||
// https://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week#Corresponding_months
|
||||
let month_offset = if Self::is_leap_year(date.0.year) {
|
||||
match date.0.month {
|
||||
10 => 0,
|
||||
5 => 1,
|
||||
2 | 8 => 2,
|
||||
3 | 11 => 3,
|
||||
6 => 4,
|
||||
9 | 12 => 5,
|
||||
1 | 4 | 7 => 6,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
} else {
|
||||
match date.0.month {
|
||||
1 | 10 => 0,
|
||||
5 => 1,
|
||||
8 => 2,
|
||||
2 | 3 | 11 => 3,
|
||||
6 => 4,
|
||||
9 | 12 => 5,
|
||||
4 | 7 => 6,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
};
|
||||
let january_1_2000 = 5; // Saturday
|
||||
let day_offset = (january_1_2000 + year_offset + month_offset + date.0.day as i32) % 7;
|
||||
|
||||
// We calculated in a zero-indexed fashion, but ISO specifies one-indexed
|
||||
types::IsoWeekday::from((day_offset + 1) as usize)
|
||||
}
|
||||
|
||||
fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration<Self>) {
|
||||
date.0.offset_date(offset);
|
||||
}
|
||||
|
||||
#[allow(clippy::field_reassign_with_default)]
|
||||
fn until(
|
||||
&self,
|
||||
date1: &Self::DateInner,
|
||||
date2: &Self::DateInner,
|
||||
_calendar2: &Self,
|
||||
_largest_unit: DateDurationUnit,
|
||||
_smallest_unit: DateDurationUnit,
|
||||
) -> DateDuration<Self> {
|
||||
date1.0.until(date2.0, _largest_unit, _smallest_unit)
|
||||
}
|
||||
|
||||
/// The calendar-specific year represented by `date`
|
||||
fn year(&self, date: &Self::DateInner) -> types::FormattableYear {
|
||||
Self::year_as_iso(date.0.year)
|
||||
}
|
||||
|
||||
fn is_in_leap_year(&self, date: &Self::DateInner) -> bool {
|
||||
Self::is_leap_year(date.0.year)
|
||||
}
|
||||
|
||||
/// The calendar-specific month represented by `date`
|
||||
fn month(&self, date: &Self::DateInner) -> types::FormattableMonth {
|
||||
date.0.month()
|
||||
}
|
||||
|
||||
/// The calendar-specific day-of-month represented by `date`
|
||||
fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth {
|
||||
date.0.day_of_month()
|
||||
}
|
||||
|
||||
fn day_of_year_info(&self, date: &Self::DateInner) -> types::DayOfYearInfo {
|
||||
let prev_year = date.0.year.saturating_sub(1);
|
||||
let next_year = date.0.year.saturating_add(1);
|
||||
types::DayOfYearInfo {
|
||||
day_of_year: date.0.day_of_year(),
|
||||
days_in_year: date.0.days_in_year(),
|
||||
prev_year: Self::year_as_iso(prev_year),
|
||||
days_in_prev_year: Iso::days_in_year_direct(prev_year),
|
||||
next_year: Self::year_as_iso(next_year),
|
||||
}
|
||||
}
|
||||
|
||||
fn debug_name(&self) -> &'static str {
|
||||
"ISO"
|
||||
}
|
||||
|
||||
fn any_calendar_kind(&self) -> Option<AnyCalendarKind> {
|
||||
Some(AnyCalendarKind::Iso)
|
||||
}
|
||||
}
|
||||
|
||||
impl Date<Iso> {
|
||||
/// Construct a new ISO date from integers.
|
||||
///
|
||||
/// ```rust
|
||||
/// use icu::calendar::Date;
|
||||
///
|
||||
/// let date_iso = Date::try_new_iso_date(1970, 1, 2)
|
||||
/// .expect("Failed to initialize ISO Date instance.");
|
||||
///
|
||||
/// assert_eq!(date_iso.year().number, 1970);
|
||||
/// assert_eq!(date_iso.month().ordinal, 1);
|
||||
/// assert_eq!(date_iso.day_of_month().0, 2);
|
||||
/// ```
|
||||
pub fn try_new_iso_date(year: i32, month: u8, day: u8) -> Result<Date<Iso>, CalendarError> {
|
||||
ArithmeticDate::new_from_ordinals(year, month, day)
|
||||
.map(IsoDateInner)
|
||||
.map(|inner| Date::from_raw(inner, Iso))
|
||||
}
|
||||
|
||||
/// Constructs an ISO date representing the UNIX epoch on January 1, 1970.
|
||||
pub fn unix_epoch() -> Self {
|
||||
Date::from_raw(IsoDateInner(ArithmeticDate::new_unchecked(1970, 1, 1)), Iso)
|
||||
}
|
||||
}
|
||||
|
||||
impl DateTime<Iso> {
|
||||
/// Construct a new ISO datetime from integers.
|
||||
///
|
||||
/// ```rust
|
||||
/// use icu::calendar::DateTime;
|
||||
///
|
||||
/// let datetime_iso = DateTime::try_new_iso_datetime(1970, 1, 2, 13, 1, 0)
|
||||
/// .expect("Failed to initialize ISO DateTime instance.");
|
||||
///
|
||||
/// assert_eq!(datetime_iso.date.year().number, 1970);
|
||||
/// assert_eq!(datetime_iso.date.month().ordinal, 1);
|
||||
/// assert_eq!(datetime_iso.date.day_of_month().0, 2);
|
||||
/// assert_eq!(datetime_iso.time.hour.number(), 13);
|
||||
/// assert_eq!(datetime_iso.time.minute.number(), 1);
|
||||
/// assert_eq!(datetime_iso.time.second.number(), 0);
|
||||
/// ```
|
||||
pub fn try_new_iso_datetime(
|
||||
year: i32,
|
||||
month: u8,
|
||||
day: u8,
|
||||
hour: u8,
|
||||
minute: u8,
|
||||
second: u8,
|
||||
) -> Result<DateTime<Iso>, CalendarError> {
|
||||
Ok(DateTime {
|
||||
date: Date::try_new_iso_date(year, month, day)?,
|
||||
time: types::Time::try_new(hour, minute, second, 0)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Minute count representation of calendars starting from 00:00:00 on Jan 1st, 1970.
|
||||
///
|
||||
/// ```rust
|
||||
/// use icu::calendar::DateTime;
|
||||
///
|
||||
/// let today = DateTime::try_new_iso_datetime(2020, 2, 29, 0, 0, 0).unwrap();
|
||||
///
|
||||
/// assert_eq!(today.minutes_since_local_unix_epoch(), 26382240);
|
||||
/// assert_eq!(
|
||||
/// DateTime::from_minutes_since_local_unix_epoch(26382240),
|
||||
/// today
|
||||
/// );
|
||||
///
|
||||
/// let today = DateTime::try_new_iso_datetime(1970, 1, 1, 0, 0, 0).unwrap();
|
||||
///
|
||||
/// assert_eq!(today.minutes_since_local_unix_epoch(), 0);
|
||||
/// assert_eq!(DateTime::from_minutes_since_local_unix_epoch(0), today);
|
||||
/// ```
|
||||
pub fn minutes_since_local_unix_epoch(&self) -> i32 {
|
||||
let minutes_a_hour = 60;
|
||||
let hours_a_day = 24;
|
||||
let minutes_a_day = minutes_a_hour * hours_a_day;
|
||||
let unix_epoch = Iso::fixed_from_iso(Date::unix_epoch().inner);
|
||||
let result = (Iso::fixed_from_iso(*self.date.inner()) - unix_epoch) * minutes_a_day
|
||||
+ i64::from(self.time.hour.number()) * minutes_a_hour
|
||||
+ i64::from(self.time.minute.number());
|
||||
i64_to_saturated_i32(result)
|
||||
}
|
||||
|
||||
/// Convert minute count since 00:00:00 on Jan 1st, 1970 to ISO Date.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use icu::calendar::DateTime;
|
||||
///
|
||||
/// // After Unix Epoch
|
||||
/// let today = DateTime::try_new_iso_datetime(2020, 2, 29, 0, 0, 0).unwrap();
|
||||
///
|
||||
/// assert_eq!(today.minutes_since_local_unix_epoch(), 26382240);
|
||||
/// assert_eq!(
|
||||
/// DateTime::from_minutes_since_local_unix_epoch(26382240),
|
||||
/// today
|
||||
/// );
|
||||
///
|
||||
/// // Unix Epoch
|
||||
/// let today = DateTime::try_new_iso_datetime(1970, 1, 1, 0, 0, 0).unwrap();
|
||||
///
|
||||
/// assert_eq!(today.minutes_since_local_unix_epoch(), 0);
|
||||
/// assert_eq!(DateTime::from_minutes_since_local_unix_epoch(0), today);
|
||||
///
|
||||
/// // Before Unix Epoch
|
||||
/// let today = DateTime::try_new_iso_datetime(1967, 4, 6, 20, 40, 0).unwrap();
|
||||
///
|
||||
/// assert_eq!(today.minutes_since_local_unix_epoch(), -1440200);
|
||||
/// assert_eq!(
|
||||
/// DateTime::from_minutes_since_local_unix_epoch(-1440200),
|
||||
/// today
|
||||
/// );
|
||||
/// ```
|
||||
pub fn from_minutes_since_local_unix_epoch(minute: i32) -> DateTime<Iso> {
|
||||
let (time, extra_days) = types::Time::from_minute_with_remainder_days(minute);
|
||||
let unix_epoch = Date::unix_epoch();
|
||||
let unix_epoch_days = Iso::fixed_from_iso(unix_epoch.inner);
|
||||
let date = Iso::iso_from_fixed(unix_epoch_days + extra_days as i64);
|
||||
DateTime { date, time }
|
||||
}
|
||||
}
|
||||
|
||||
impl Iso {
|
||||
/// Construct a new ISO Calendar
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
/// Count the number of days in a given month/year combo
|
||||
fn days_in_month(year: i32, month: u8) -> u8 {
|
||||
match month {
|
||||
4 | 6 | 9 | 11 => 30,
|
||||
2 if Self::is_leap_year(year) => 29,
|
||||
2 => 28,
|
||||
_ => 31,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn days_in_year_direct(year: i32) -> u16 {
|
||||
if Self::is_leap_year(year) {
|
||||
366
|
||||
} else {
|
||||
365
|
||||
}
|
||||
}
|
||||
|
||||
// Fixed is day count representation of calendars starting from Jan 1st of year 1.
|
||||
// The fixed calculations algorithms are from the Calendrical Calculations book.
|
||||
pub(crate) fn fixed_from_iso(date: IsoDateInner) -> RataDie {
|
||||
calendrical_calculations::iso::fixed_from_iso(date.0.year, date.0.month, date.0.day)
|
||||
}
|
||||
|
||||
pub(crate) fn iso_from_year_day(year: i32, year_day: u16) -> Date<Iso> {
|
||||
let mut month = 1;
|
||||
let mut day = year_day as i32;
|
||||
while month <= 12 {
|
||||
let month_days = Self::days_in_month(year, month) as i32;
|
||||
if day <= month_days {
|
||||
break;
|
||||
} else {
|
||||
debug_assert!(month < 12); // don't try going to month 13
|
||||
day -= month_days;
|
||||
month += 1;
|
||||
}
|
||||
}
|
||||
let day = day as u8; // day <= month_days < u8::MAX
|
||||
|
||||
#[allow(clippy::unwrap_used)] // month in 1..=12, day <= month_days
|
||||
Date::try_new_iso_date(year, month, day).unwrap()
|
||||
}
|
||||
pub(crate) fn iso_from_fixed(date: RataDie) -> Date<Iso> {
|
||||
let (year, month, day) = match calendrical_calculations::iso::iso_from_fixed(date) {
|
||||
Err(I32CastError::BelowMin) => {
|
||||
return Date::from_raw(IsoDateInner(ArithmeticDate::min_date()), Iso)
|
||||
}
|
||||
Err(I32CastError::AboveMax) => {
|
||||
return Date::from_raw(IsoDateInner(ArithmeticDate::max_date()), Iso)
|
||||
}
|
||||
Ok(ymd) => ymd,
|
||||
};
|
||||
#[allow(clippy::unwrap_used)] // valid day and month
|
||||
Date::try_new_iso_date(year, month, day).unwrap()
|
||||
}
|
||||
|
||||
pub(crate) fn day_of_year(date: IsoDateInner) -> u16 {
|
||||
// Cumulatively how much are dates in each month
|
||||
// offset from "30 days in each month" (in non leap years)
|
||||
let month_offset = [0, 1, -1, 0, 0, 1, 1, 2, 3, 3, 4, 4];
|
||||
#[allow(clippy::indexing_slicing)] // date.0.month in 1..=12
|
||||
let mut offset = month_offset[date.0.month as usize - 1];
|
||||
if Self::is_leap_year(date.0.year) && date.0.month > 2 {
|
||||
// Months after February in a leap year are offset by one less
|
||||
offset += 1;
|
||||
}
|
||||
let prev_month_days = (30 * (date.0.month as i32 - 1) + offset) as u16;
|
||||
|
||||
prev_month_days + date.0.day as u16
|
||||
}
|
||||
|
||||
/// Wrap the year in the appropriate era code
|
||||
fn year_as_iso(year: i32) -> types::FormattableYear {
|
||||
types::FormattableYear {
|
||||
era: types::Era(tinystr!(16, "default")),
|
||||
number: year,
|
||||
cyclic: None,
|
||||
related_iso: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IsoDateInner {
|
||||
pub(crate) fn jan_1(year: i32) -> Self {
|
||||
Self(ArithmeticDate::new_unchecked(year, 1, 1))
|
||||
}
|
||||
pub(crate) fn dec_31(year: i32) -> Self {
|
||||
Self(ArithmeticDate::new_unchecked(year, 12, 1))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'_ IsoDateInner> for crate::provider::EraStartDate {
|
||||
fn from(other: &'_ IsoDateInner) -> Self {
|
||||
Self {
|
||||
year: other.0.year,
|
||||
month: other.0.month,
|
||||
day: other.0.day,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::types::IsoWeekday;
|
||||
|
||||
#[test]
|
||||
fn iso_overflow() {
|
||||
#[derive(Debug)]
|
||||
struct TestCase {
|
||||
year: i32,
|
||||
month: u8,
|
||||
day: u8,
|
||||
fixed: RataDie,
|
||||
saturating: bool,
|
||||
}
|
||||
// Calculates the max possible year representable using i32::MAX as the fixed date
|
||||
let max_year = Iso::iso_from_fixed(RataDie::new(i32::MAX as i64))
|
||||
.year()
|
||||
.number;
|
||||
|
||||
// Calculates the minimum possible year representable using i32::MIN as the fixed date
|
||||
// *Cannot be tested yet due to hard coded date not being available yet (see line 436)
|
||||
let min_year = -5879610;
|
||||
|
||||
let cases = [
|
||||
TestCase {
|
||||
// Earliest date that can be represented before causing a minimum overflow
|
||||
year: min_year,
|
||||
month: 6,
|
||||
day: 22,
|
||||
fixed: RataDie::new(i32::MIN as i64),
|
||||
saturating: false,
|
||||
},
|
||||
TestCase {
|
||||
year: min_year,
|
||||
month: 6,
|
||||
day: 23,
|
||||
fixed: RataDie::new(i32::MIN as i64 + 1),
|
||||
saturating: false,
|
||||
},
|
||||
TestCase {
|
||||
year: min_year,
|
||||
month: 6,
|
||||
day: 21,
|
||||
fixed: RataDie::new(i32::MIN as i64 - 1),
|
||||
saturating: false,
|
||||
},
|
||||
TestCase {
|
||||
year: min_year,
|
||||
month: 12,
|
||||
day: 31,
|
||||
fixed: RataDie::new(-2147483456),
|
||||
saturating: false,
|
||||
},
|
||||
TestCase {
|
||||
year: min_year + 1,
|
||||
month: 1,
|
||||
day: 1,
|
||||
fixed: RataDie::new(-2147483455),
|
||||
saturating: false,
|
||||
},
|
||||
TestCase {
|
||||
year: max_year,
|
||||
month: 6,
|
||||
day: 11,
|
||||
fixed: RataDie::new(i32::MAX as i64 - 30),
|
||||
saturating: false,
|
||||
},
|
||||
TestCase {
|
||||
year: max_year,
|
||||
month: 7,
|
||||
day: 9,
|
||||
fixed: RataDie::new(i32::MAX as i64 - 2),
|
||||
saturating: false,
|
||||
},
|
||||
TestCase {
|
||||
year: max_year,
|
||||
month: 7,
|
||||
day: 10,
|
||||
fixed: RataDie::new(i32::MAX as i64 - 1),
|
||||
saturating: false,
|
||||
},
|
||||
TestCase {
|
||||
// Latest date that can be represented before causing a maximum overflow
|
||||
year: max_year,
|
||||
month: 7,
|
||||
day: 11,
|
||||
fixed: RataDie::new(i32::MAX as i64),
|
||||
saturating: false,
|
||||
},
|
||||
TestCase {
|
||||
year: max_year,
|
||||
month: 7,
|
||||
day: 12,
|
||||
fixed: RataDie::new(i32::MAX as i64 + 1),
|
||||
saturating: false,
|
||||
},
|
||||
TestCase {
|
||||
year: i32::MIN,
|
||||
month: 1,
|
||||
day: 2,
|
||||
fixed: RataDie::new(-784352296669),
|
||||
saturating: false,
|
||||
},
|
||||
TestCase {
|
||||
year: i32::MIN,
|
||||
month: 1,
|
||||
day: 1,
|
||||
fixed: RataDie::new(-784352296670),
|
||||
saturating: false,
|
||||
},
|
||||
TestCase {
|
||||
year: i32::MIN,
|
||||
month: 1,
|
||||
day: 1,
|
||||
fixed: RataDie::new(-784352296671),
|
||||
saturating: true,
|
||||
},
|
||||
TestCase {
|
||||
year: i32::MAX,
|
||||
month: 12,
|
||||
day: 30,
|
||||
fixed: RataDie::new(784352295938),
|
||||
saturating: false,
|
||||
},
|
||||
TestCase {
|
||||
year: i32::MAX,
|
||||
month: 12,
|
||||
day: 31,
|
||||
fixed: RataDie::new(784352295939),
|
||||
saturating: false,
|
||||
},
|
||||
TestCase {
|
||||
year: i32::MAX,
|
||||
month: 12,
|
||||
day: 31,
|
||||
fixed: RataDie::new(784352295940),
|
||||
saturating: true,
|
||||
},
|
||||
];
|
||||
|
||||
for case in cases {
|
||||
let date = Date::try_new_iso_date(case.year, case.month, case.day).unwrap();
|
||||
if !case.saturating {
|
||||
assert_eq!(Iso::fixed_from_iso(date.inner), case.fixed, "{case:?}");
|
||||
}
|
||||
assert_eq!(Iso::iso_from_fixed(case.fixed), date, "{case:?}");
|
||||
}
|
||||
}
|
||||
|
||||
// Calculates the minimum possible year representable using a large negative fixed date
|
||||
#[test]
|
||||
fn min_year() {
|
||||
assert_eq!(
|
||||
Iso::iso_from_fixed(RataDie::big_negative()).year().number,
|
||||
i32::MIN
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_day_of_week() {
|
||||
// June 23, 2021 is a Wednesday
|
||||
assert_eq!(
|
||||
Date::try_new_iso_date(2021, 6, 23).unwrap().day_of_week(),
|
||||
IsoWeekday::Wednesday,
|
||||
);
|
||||
// Feb 2, 1983 was a Wednesday
|
||||
assert_eq!(
|
||||
Date::try_new_iso_date(1983, 2, 2).unwrap().day_of_week(),
|
||||
IsoWeekday::Wednesday,
|
||||
);
|
||||
// Jan 21, 2021 was a Tuesday
|
||||
assert_eq!(
|
||||
Date::try_new_iso_date(2020, 1, 21).unwrap().day_of_week(),
|
||||
IsoWeekday::Tuesday,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_day_of_year() {
|
||||
// June 23, 2021 was day 174
|
||||
assert_eq!(
|
||||
Date::try_new_iso_date(2021, 6, 23)
|
||||
.unwrap()
|
||||
.day_of_year_info()
|
||||
.day_of_year,
|
||||
174,
|
||||
);
|
||||
// June 23, 2020 was day 175
|
||||
assert_eq!(
|
||||
Date::try_new_iso_date(2020, 6, 23)
|
||||
.unwrap()
|
||||
.day_of_year_info()
|
||||
.day_of_year,
|
||||
175,
|
||||
);
|
||||
// Feb 2, 1983 was a Wednesday
|
||||
assert_eq!(
|
||||
Date::try_new_iso_date(1983, 2, 2)
|
||||
.unwrap()
|
||||
.day_of_year_info()
|
||||
.day_of_year,
|
||||
33,
|
||||
);
|
||||
}
|
||||
|
||||
fn simple_subtract(a: &Date<Iso>, b: &Date<Iso>) -> DateDuration<Iso> {
|
||||
let a = a.inner();
|
||||
let b = b.inner();
|
||||
DateDuration::new(
|
||||
a.0.year - b.0.year,
|
||||
a.0.month as i32 - b.0.month as i32,
|
||||
0,
|
||||
a.0.day as i32 - b.0.day as i32,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_offset() {
|
||||
let today = Date::try_new_iso_date(2021, 6, 23).unwrap();
|
||||
let today_plus_5000 = Date::try_new_iso_date(2035, 3, 2).unwrap();
|
||||
let offset = today.added(DateDuration::new(0, 0, 0, 5000));
|
||||
assert_eq!(offset, today_plus_5000);
|
||||
let offset = today.added(simple_subtract(&today_plus_5000, &today));
|
||||
assert_eq!(offset, today_plus_5000);
|
||||
|
||||
let today = Date::try_new_iso_date(2021, 6, 23).unwrap();
|
||||
let today_minus_5000 = Date::try_new_iso_date(2007, 10, 15).unwrap();
|
||||
let offset = today.added(DateDuration::new(0, 0, 0, -5000));
|
||||
assert_eq!(offset, today_minus_5000);
|
||||
let offset = today.added(simple_subtract(&today_minus_5000, &today));
|
||||
assert_eq!(offset, today_minus_5000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_offset_at_month_boundary() {
|
||||
let today = Date::try_new_iso_date(2020, 2, 28).unwrap();
|
||||
let today_plus_2 = Date::try_new_iso_date(2020, 3, 1).unwrap();
|
||||
let offset = today.added(DateDuration::new(0, 0, 0, 2));
|
||||
assert_eq!(offset, today_plus_2);
|
||||
|
||||
let today = Date::try_new_iso_date(2020, 2, 28).unwrap();
|
||||
let today_plus_3 = Date::try_new_iso_date(2020, 3, 2).unwrap();
|
||||
let offset = today.added(DateDuration::new(0, 0, 0, 3));
|
||||
assert_eq!(offset, today_plus_3);
|
||||
|
||||
let today = Date::try_new_iso_date(2020, 2, 28).unwrap();
|
||||
let today_plus_1 = Date::try_new_iso_date(2020, 2, 29).unwrap();
|
||||
let offset = today.added(DateDuration::new(0, 0, 0, 1));
|
||||
assert_eq!(offset, today_plus_1);
|
||||
|
||||
let today = Date::try_new_iso_date(2019, 2, 28).unwrap();
|
||||
let today_plus_2 = Date::try_new_iso_date(2019, 3, 2).unwrap();
|
||||
let offset = today.added(DateDuration::new(0, 0, 0, 2));
|
||||
assert_eq!(offset, today_plus_2);
|
||||
|
||||
let today = Date::try_new_iso_date(2019, 2, 28).unwrap();
|
||||
let today_plus_1 = Date::try_new_iso_date(2019, 3, 1).unwrap();
|
||||
let offset = today.added(DateDuration::new(0, 0, 0, 1));
|
||||
assert_eq!(offset, today_plus_1);
|
||||
|
||||
let today = Date::try_new_iso_date(2020, 3, 1).unwrap();
|
||||
let today_minus_1 = Date::try_new_iso_date(2020, 2, 29).unwrap();
|
||||
let offset = today.added(DateDuration::new(0, 0, 0, -1));
|
||||
assert_eq!(offset, today_minus_1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_offset_handles_negative_month_offset() {
|
||||
let today = Date::try_new_iso_date(2020, 3, 1).unwrap();
|
||||
let today_minus_2_months = Date::try_new_iso_date(2020, 1, 1).unwrap();
|
||||
let offset = today.added(DateDuration::new(0, -2, 0, 0));
|
||||
assert_eq!(offset, today_minus_2_months);
|
||||
|
||||
let today = Date::try_new_iso_date(2020, 3, 1).unwrap();
|
||||
let today_minus_4_months = Date::try_new_iso_date(2019, 11, 1).unwrap();
|
||||
let offset = today.added(DateDuration::new(0, -4, 0, 0));
|
||||
assert_eq!(offset, today_minus_4_months);
|
||||
|
||||
let today = Date::try_new_iso_date(2020, 3, 1).unwrap();
|
||||
let today_minus_24_months = Date::try_new_iso_date(2018, 3, 1).unwrap();
|
||||
let offset = today.added(DateDuration::new(0, -24, 0, 0));
|
||||
assert_eq!(offset, today_minus_24_months);
|
||||
|
||||
let today = Date::try_new_iso_date(2020, 3, 1).unwrap();
|
||||
let today_minus_27_months = Date::try_new_iso_date(2017, 12, 1).unwrap();
|
||||
let offset = today.added(DateDuration::new(0, -27, 0, 0));
|
||||
assert_eq!(offset, today_minus_27_months);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_offset_handles_out_of_bound_month_offset() {
|
||||
let today = Date::try_new_iso_date(2021, 1, 31).unwrap();
|
||||
// since 2021/02/31 isn't a valid date, `offset_date` auto-adjusts by adding 3 days to 2021/02/28
|
||||
let today_plus_1_month = Date::try_new_iso_date(2021, 3, 3).unwrap();
|
||||
let offset = today.added(DateDuration::new(0, 1, 0, 0));
|
||||
assert_eq!(offset, today_plus_1_month);
|
||||
|
||||
let today = Date::try_new_iso_date(2021, 1, 31).unwrap();
|
||||
// since 2021/02/31 isn't a valid date, `offset_date` auto-adjusts by adding 3 days to 2021/02/28
|
||||
let today_plus_1_month_1_day = Date::try_new_iso_date(2021, 3, 4).unwrap();
|
||||
let offset = today.added(DateDuration::new(0, 1, 0, 1));
|
||||
assert_eq!(offset, today_plus_1_month_1_day);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iso_to_from_fixed() {
|
||||
// Reminder: ISO year 0 is Gregorian year 1 BCE.
|
||||
// Year 0 is a leap year due to the 400-year rule.
|
||||
fn check(fixed: i64, year: i32, month: u8, day: u8) {
|
||||
let fixed = RataDie::new(fixed);
|
||||
|
||||
assert_eq!(
|
||||
Iso::iso_from_fixed(fixed),
|
||||
Date::try_new_iso_date(year, month, day).unwrap(),
|
||||
"fixed: {fixed:?}"
|
||||
);
|
||||
}
|
||||
check(-1828, -5, 12, 30);
|
||||
check(-1827, -5, 12, 31); // leap year
|
||||
check(-1826, -4, 1, 1);
|
||||
check(-1462, -4, 12, 30);
|
||||
check(-1461, -4, 12, 31);
|
||||
check(-1460, -3, 1, 1);
|
||||
check(-1459, -3, 1, 2);
|
||||
check(-732, -2, 12, 30);
|
||||
check(-731, -2, 12, 31);
|
||||
check(-730, -1, 1, 1);
|
||||
check(-367, -1, 12, 30);
|
||||
check(-366, -1, 12, 31);
|
||||
check(-365, 0, 1, 1); // leap year
|
||||
check(-364, 0, 1, 2);
|
||||
check(-1, 0, 12, 30);
|
||||
check(0, 0, 12, 31);
|
||||
check(1, 1, 1, 1);
|
||||
check(2, 1, 1, 2);
|
||||
check(364, 1, 12, 30);
|
||||
check(365, 1, 12, 31);
|
||||
check(366, 2, 1, 1);
|
||||
check(1459, 4, 12, 29);
|
||||
check(1460, 4, 12, 30);
|
||||
check(1461, 4, 12, 31); // leap year
|
||||
check(1462, 5, 1, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_minutes_since_local_unix_epoch() {
|
||||
fn check(minutes: i32, year: i32, month: u8, day: u8, hour: u8, minute: u8) {
|
||||
let today = DateTime::try_new_iso_datetime(year, month, day, hour, minute, 0).unwrap();
|
||||
assert_eq!(today.minutes_since_local_unix_epoch(), minutes);
|
||||
assert_eq!(
|
||||
DateTime::from_minutes_since_local_unix_epoch(minutes),
|
||||
today
|
||||
);
|
||||
}
|
||||
|
||||
check(-1441, 1969, 12, 30, 23, 59);
|
||||
check(-1440, 1969, 12, 31, 0, 0);
|
||||
check(-1439, 1969, 12, 31, 0, 1);
|
||||
check(-2879, 1969, 12, 30, 0, 1);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,988 @@
|
|||
// This file is part of ICU4X. For terms of use, please see the file
|
||||
// called LICENSE at the top level of the ICU4X source tree
|
||||
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
|
||||
|
||||
//! This module contains types and implementations for the Japanese calendar.
|
||||
//!
|
||||
//! ```rust
|
||||
//! use icu::calendar::japanese::Japanese;
|
||||
//! use icu::calendar::{types::Era, Date, DateTime};
|
||||
//! use tinystr::tinystr;
|
||||
//!
|
||||
//! let japanese_calendar = Japanese::new();
|
||||
//!
|
||||
//! // `Date` type
|
||||
//! let date_iso = Date::try_new_iso_date(1970, 1, 2)
|
||||
//! .expect("Failed to initialize ISO Date instance.");
|
||||
//! let date_japanese = Date::new_from_iso(date_iso, japanese_calendar.clone());
|
||||
//!
|
||||
//! // `DateTime` type
|
||||
//! let datetime_iso = DateTime::try_new_iso_datetime(1970, 1, 2, 13, 1, 0)
|
||||
//! .expect("Failed to initialize ISO DateTime instance.");
|
||||
//! let datetime_japanese =
|
||||
//! DateTime::new_from_iso(datetime_iso, japanese_calendar.clone());
|
||||
//!
|
||||
//! // `Date` checks
|
||||
//! assert_eq!(date_japanese.year().number, 45);
|
||||
//! assert_eq!(date_japanese.month().ordinal, 1);
|
||||
//! assert_eq!(date_japanese.day_of_month().0, 2);
|
||||
//! assert_eq!(date_japanese.year().era, Era(tinystr!(16, "showa")));
|
||||
//!
|
||||
//! // `DateTime` type
|
||||
//! assert_eq!(datetime_japanese.date.year().number, 45);
|
||||
//! assert_eq!(datetime_japanese.date.month().ordinal, 1);
|
||||
//! assert_eq!(datetime_japanese.date.day_of_month().0, 2);
|
||||
//! assert_eq!(
|
||||
//! datetime_japanese.date.year().era,
|
||||
//! Era(tinystr!(16, "showa"))
|
||||
//! );
|
||||
//! assert_eq!(datetime_japanese.time.hour.number(), 13);
|
||||
//! assert_eq!(datetime_japanese.time.minute.number(), 1);
|
||||
//! assert_eq!(datetime_japanese.time.second.number(), 0);
|
||||
//! ```
|
||||
|
||||
use crate::any_calendar::AnyCalendarKind;
|
||||
use crate::iso::{Iso, IsoDateInner};
|
||||
use crate::provider::{EraStartDate, JapaneseErasV1Marker, JapaneseExtendedErasV1Marker};
|
||||
use crate::{
|
||||
types, AsCalendar, Calendar, CalendarError, Date, DateDuration, DateDurationUnit, DateTime, Ref,
|
||||
};
|
||||
use icu_provider::prelude::*;
|
||||
use tinystr::{tinystr, TinyStr16};
|
||||
|
||||
/// The [Japanese Calendar] (with modern eras only)
|
||||
///
|
||||
/// The [Japanese calendar] is a solar calendar used in Japan, with twelve months.
|
||||
/// The months and days are identical to that of the Gregorian calendar, however the years are counted
|
||||
/// differently using the Japanese era system.
|
||||
///
|
||||
/// This calendar only contains eras after Meiji, for all historical eras, check out [`JapaneseExtended`].
|
||||
///
|
||||
/// This type can be used with [`Date`] or [`DateTime`] to represent dates in this calendar.
|
||||
///
|
||||
/// [Japanese calendar]: https://en.wikipedia.org/wiki/Japanese_calendar
|
||||
///
|
||||
/// # Era codes
|
||||
///
|
||||
/// This calendar currently supports seven era codes. It supports the five post-Meiji eras
|
||||
/// (`"meiji"`, `"taisho"`, `"showa"`, `"heisei"`, `"reiwa"`), as well as using the Gregorian
|
||||
/// `"bce"` and `"ce"` for dates before the Meiji era.
|
||||
///
|
||||
/// Future eras will also be added to this type when they are decided.
|
||||
///
|
||||
/// These eras are loaded from data, requiring a data provider capable of providing [`JapaneseErasV1Marker`]
|
||||
/// data (`calendar/japanese@1`).
|
||||
///
|
||||
/// # Month codes
|
||||
///
|
||||
/// This calendar supports 12 solar month codes (`"M01" - "M12"`)
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Japanese {
|
||||
eras: DataPayload<JapaneseErasV1Marker>,
|
||||
}
|
||||
|
||||
/// The [Japanese Calendar] (with historical eras)
|
||||
///
|
||||
/// The [Japanese calendar] is a solar calendar used in Japan, with twelve months.
|
||||
/// The months and days are identical to that of the Gregorian calendar, however the years are counted
|
||||
/// differently using the Japanese era system.
|
||||
///
|
||||
/// This type can be used with [`Date`] or [`DateTime`] to represent dates in this calendar.
|
||||
///
|
||||
/// [Japanese calendar]: https://en.wikipedia.org/wiki/Japanese_calendar
|
||||
///
|
||||
/// # Era codes
|
||||
///
|
||||
/// This calendar supports a large number of era codes. It supports the five post-Meiji eras
|
||||
/// (`"meiji"`, `"taisho"`, `"showa"`, `"heisei"`, `"reiwa"`). Pre-Meiji eras are represented
|
||||
/// with their names converted to lowercase ascii and followed by their start year. E.g. the "Ten'ō"
|
||||
/// era (781 - 782 CE) has the code `"teno-781"`. The Gregorian `"bce"` and `"ce"` eras
|
||||
/// are used for dates before the first known era era.
|
||||
///
|
||||
///
|
||||
/// These eras are loaded from data, requiring a data provider capable of providing [`JapaneseExtendedErasV1Marker`]
|
||||
/// data (`calendar/japanext@1`).
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct JapaneseExtended(Japanese);
|
||||
|
||||
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
|
||||
/// The inner date type used for representing [`Date`]s of [`Japanese`]. See [`Date`] and [`Japanese`] for more details.
|
||||
pub struct JapaneseDateInner {
|
||||
inner: IsoDateInner,
|
||||
adjusted_year: i32,
|
||||
era: TinyStr16,
|
||||
}
|
||||
|
||||
impl Japanese {
|
||||
/// Creates a new [`Japanese`] using only modern eras (post-meiji) from compiled data.
|
||||
///
|
||||
/// ✨ *Enabled with the `compiled_data` Cargo feature.*
|
||||
///
|
||||
/// [📚 Help choosing a constructor](icu_provider::constructors)
|
||||
#[cfg(feature = "compiled_data")]
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
eras: DataPayload::from_static_ref(
|
||||
crate::provider::Baked::SINGLETON_CALENDAR_JAPANESE_V1,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
icu_provider::gen_any_buffer_data_constructors!(locale: skip, options: skip, error: CalendarError,
|
||||
#[cfg(skip)]
|
||||
functions: [
|
||||
new,
|
||||
try_new_with_any_provider,
|
||||
try_new_with_buffer_provider,
|
||||
try_new_unstable,
|
||||
Self,
|
||||
]);
|
||||
|
||||
#[doc = icu_provider::gen_any_buffer_unstable_docs!(UNSTABLE, Self::new)]
|
||||
pub fn try_new_unstable<D: DataProvider<JapaneseErasV1Marker> + ?Sized>(
|
||||
provider: &D,
|
||||
) -> Result<Self, CalendarError> {
|
||||
Ok(Self {
|
||||
eras: provider.load(Default::default())?.take_payload()?,
|
||||
})
|
||||
}
|
||||
|
||||
fn japanese_date_from_codes(
|
||||
&self,
|
||||
era: types::Era,
|
||||
year: i32,
|
||||
month_code: types::MonthCode,
|
||||
day: u8,
|
||||
debug_name: &'static str,
|
||||
) -> Result<JapaneseDateInner, CalendarError> {
|
||||
let month = month_code.parsed();
|
||||
let month = if let Some((month, false)) = month {
|
||||
month
|
||||
} else {
|
||||
return Err(CalendarError::UnknownMonthCode(month_code.0, debug_name));
|
||||
};
|
||||
|
||||
if month > 12 {
|
||||
return Err(CalendarError::UnknownMonthCode(month_code.0, debug_name));
|
||||
}
|
||||
|
||||
self.new_japanese_date_inner(era, year, month, day)
|
||||
}
|
||||
|
||||
pub(crate) const DEBUG_NAME: &'static str = "Japanese";
|
||||
}
|
||||
|
||||
impl JapaneseExtended {
|
||||
/// Creates a new [`Japanese`] from using all eras (including pre-meiji) from compiled data.
|
||||
///
|
||||
/// ✨ *Enabled with the `compiled_data` Cargo feature.*
|
||||
///
|
||||
/// [📚 Help choosing a constructor](icu_provider::constructors)
|
||||
#[cfg(feature = "compiled_data")]
|
||||
pub const fn new() -> Self {
|
||||
Self(Japanese {
|
||||
eras: DataPayload::from_static_ref(
|
||||
crate::provider::Baked::SINGLETON_CALENDAR_JAPANEXT_V1,
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
icu_provider::gen_any_buffer_data_constructors!(locale: skip, options: skip, error: CalendarError,
|
||||
#[cfg(skip)]
|
||||
functions: [
|
||||
new,
|
||||
try_new_with_any_provider,
|
||||
try_new_with_buffer_provider,
|
||||
try_new_unstable,
|
||||
Self,
|
||||
]);
|
||||
|
||||
#[doc = icu_provider::gen_any_buffer_unstable_docs!(UNSTABLE, Self::new)]
|
||||
pub fn try_new_unstable<D: DataProvider<JapaneseExtendedErasV1Marker> + ?Sized>(
|
||||
provider: &D,
|
||||
) -> Result<Self, CalendarError> {
|
||||
Ok(Self(Japanese {
|
||||
eras: provider.load(Default::default())?.take_payload()?.cast(),
|
||||
}))
|
||||
}
|
||||
|
||||
pub(crate) const DEBUG_NAME: &'static str = "Japanese (historical era data)";
|
||||
}
|
||||
|
||||
impl Calendar for Japanese {
|
||||
type DateInner = JapaneseDateInner;
|
||||
|
||||
fn date_from_codes(
|
||||
&self,
|
||||
era: types::Era,
|
||||
year: i32,
|
||||
month_code: types::MonthCode,
|
||||
day: u8,
|
||||
) -> Result<Self::DateInner, CalendarError> {
|
||||
self.japanese_date_from_codes(era, year, month_code, day, self.debug_name())
|
||||
}
|
||||
|
||||
fn date_from_iso(&self, iso: Date<Iso>) -> JapaneseDateInner {
|
||||
let (adjusted_year, era) = self.adjusted_year_for(iso.inner());
|
||||
JapaneseDateInner {
|
||||
inner: *iso.inner(),
|
||||
adjusted_year,
|
||||
era,
|
||||
}
|
||||
}
|
||||
|
||||
fn date_to_iso(&self, date: &Self::DateInner) -> Date<Iso> {
|
||||
Date::from_raw(date.inner, Iso)
|
||||
}
|
||||
|
||||
fn months_in_year(&self, date: &Self::DateInner) -> u8 {
|
||||
Iso.months_in_year(&date.inner)
|
||||
}
|
||||
|
||||
fn days_in_year(&self, date: &Self::DateInner) -> u16 {
|
||||
Iso.days_in_year(&date.inner)
|
||||
}
|
||||
|
||||
fn days_in_month(&self, date: &Self::DateInner) -> u8 {
|
||||
Iso.days_in_month(&date.inner)
|
||||
}
|
||||
|
||||
fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration<Self>) {
|
||||
Iso.offset_date(&mut date.inner, offset.cast_unit());
|
||||
let (adjusted_year, era) = self.adjusted_year_for(&date.inner);
|
||||
date.adjusted_year = adjusted_year;
|
||||
date.era = era
|
||||
}
|
||||
|
||||
fn until(
|
||||
&self,
|
||||
date1: &Self::DateInner,
|
||||
date2: &Self::DateInner,
|
||||
_calendar2: &Self,
|
||||
largest_unit: DateDurationUnit,
|
||||
smallest_unit: DateDurationUnit,
|
||||
) -> DateDuration<Self> {
|
||||
Iso.until(
|
||||
&date1.inner,
|
||||
&date2.inner,
|
||||
&Iso,
|
||||
largest_unit,
|
||||
smallest_unit,
|
||||
)
|
||||
.cast_unit()
|
||||
}
|
||||
|
||||
/// The calendar-specific year represented by `date`
|
||||
fn year(&self, date: &Self::DateInner) -> types::FormattableYear {
|
||||
types::FormattableYear {
|
||||
era: types::Era(date.era),
|
||||
number: date.adjusted_year,
|
||||
cyclic: None,
|
||||
related_iso: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_in_leap_year(&self, date: &Self::DateInner) -> bool {
|
||||
Iso.is_in_leap_year(&date.inner)
|
||||
}
|
||||
|
||||
/// The calendar-specific month represented by `date`
|
||||
fn month(&self, date: &Self::DateInner) -> types::FormattableMonth {
|
||||
Iso.month(&date.inner)
|
||||
}
|
||||
|
||||
/// The calendar-specific day-of-month represented by `date`
|
||||
fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth {
|
||||
Iso.day_of_month(&date.inner)
|
||||
}
|
||||
|
||||
/// Information of the day of the year
|
||||
fn day_of_year_info(&self, date: &Self::DateInner) -> types::DayOfYearInfo {
|
||||
let prev_dec_31 = IsoDateInner::dec_31(date.inner.0.year - 1);
|
||||
let next_jan_1 = IsoDateInner::jan_1(date.inner.0.year + 1);
|
||||
|
||||
let prev_dec_31 = self.date_from_iso(Date::from_raw(prev_dec_31, Iso));
|
||||
let next_jan_1 = self.date_from_iso(Date::from_raw(next_jan_1, Iso));
|
||||
types::DayOfYearInfo {
|
||||
day_of_year: Iso::days_in_year_direct(date.inner.0.year),
|
||||
days_in_year: Iso::days_in_year_direct(date.inner.0.year),
|
||||
prev_year: self.year(&prev_dec_31),
|
||||
days_in_prev_year: Iso::days_in_year_direct(prev_dec_31.inner.0.year),
|
||||
next_year: self.year(&next_jan_1),
|
||||
}
|
||||
}
|
||||
|
||||
fn debug_name(&self) -> &'static str {
|
||||
Self::DEBUG_NAME
|
||||
}
|
||||
|
||||
fn any_calendar_kind(&self) -> Option<AnyCalendarKind> {
|
||||
Some(AnyCalendarKind::Japanese)
|
||||
}
|
||||
}
|
||||
|
||||
impl Calendar for JapaneseExtended {
|
||||
type DateInner = JapaneseDateInner;
|
||||
|
||||
fn date_from_codes(
|
||||
&self,
|
||||
era: types::Era,
|
||||
year: i32,
|
||||
month_code: types::MonthCode,
|
||||
day: u8,
|
||||
) -> Result<Self::DateInner, CalendarError> {
|
||||
self.0
|
||||
.japanese_date_from_codes(era, year, month_code, day, self.debug_name())
|
||||
}
|
||||
|
||||
fn date_from_iso(&self, iso: Date<Iso>) -> JapaneseDateInner {
|
||||
Japanese::date_from_iso(&self.0, iso)
|
||||
}
|
||||
|
||||
fn date_to_iso(&self, date: &Self::DateInner) -> Date<Iso> {
|
||||
Japanese::date_to_iso(&self.0, date)
|
||||
}
|
||||
|
||||
fn months_in_year(&self, date: &Self::DateInner) -> u8 {
|
||||
Japanese::months_in_year(&self.0, date)
|
||||
}
|
||||
|
||||
fn days_in_year(&self, date: &Self::DateInner) -> u16 {
|
||||
Japanese::days_in_year(&self.0, date)
|
||||
}
|
||||
|
||||
fn days_in_month(&self, date: &Self::DateInner) -> u8 {
|
||||
Japanese::days_in_month(&self.0, date)
|
||||
}
|
||||
|
||||
fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration<Self>) {
|
||||
Japanese::offset_date(&self.0, date, offset.cast_unit())
|
||||
}
|
||||
|
||||
fn until(
|
||||
&self,
|
||||
date1: &Self::DateInner,
|
||||
date2: &Self::DateInner,
|
||||
calendar2: &Self,
|
||||
largest_unit: DateDurationUnit,
|
||||
smallest_unit: DateDurationUnit,
|
||||
) -> DateDuration<Self> {
|
||||
Japanese::until(
|
||||
&self.0,
|
||||
date1,
|
||||
date2,
|
||||
&calendar2.0,
|
||||
largest_unit,
|
||||
smallest_unit,
|
||||
)
|
||||
.cast_unit()
|
||||
}
|
||||
|
||||
/// The calendar-specific year represented by `date`
|
||||
fn year(&self, date: &Self::DateInner) -> types::FormattableYear {
|
||||
Japanese::year(&self.0, date)
|
||||
}
|
||||
|
||||
fn is_in_leap_year(&self, date: &Self::DateInner) -> bool {
|
||||
Japanese::is_in_leap_year(&self.0, date)
|
||||
}
|
||||
|
||||
/// The calendar-specific month represented by `date`
|
||||
fn month(&self, date: &Self::DateInner) -> types::FormattableMonth {
|
||||
Japanese::month(&self.0, date)
|
||||
}
|
||||
|
||||
/// The calendar-specific day-of-month represented by `date`
|
||||
fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth {
|
||||
Japanese::day_of_month(&self.0, date)
|
||||
}
|
||||
|
||||
/// Information of the day of the year
|
||||
fn day_of_year_info(&self, date: &Self::DateInner) -> types::DayOfYearInfo {
|
||||
Japanese::day_of_year_info(&self.0, date)
|
||||
}
|
||||
|
||||
fn debug_name(&self) -> &'static str {
|
||||
Self::DEBUG_NAME
|
||||
}
|
||||
|
||||
fn any_calendar_kind(&self) -> Option<AnyCalendarKind> {
|
||||
Some(AnyCalendarKind::JapaneseExtended)
|
||||
}
|
||||
}
|
||||
|
||||
impl Date<Japanese> {
|
||||
/// Construct a new Japanese Date.
|
||||
///
|
||||
/// Years are specified in the era provided, and must be in range for Japanese
|
||||
/// eras (e.g. dates past April 30 Heisei 31 must be in Reiwa; "Jun 5 Heisei 31" and "Jan 1 Heisei 32"
|
||||
/// will not be adjusted to being in Reiwa 1 and 2 respectively)
|
||||
///
|
||||
/// However, dates may always be specified in "bce" or "ce" and they will be adjusted as necessary.
|
||||
///
|
||||
/// ```rust
|
||||
/// use icu::calendar::japanese::Japanese;
|
||||
/// use icu::calendar::{types, Date, Ref};
|
||||
/// use std::convert::TryFrom;
|
||||
/// use tinystr::tinystr;
|
||||
///
|
||||
/// let japanese_calendar = Japanese::new();
|
||||
/// // for easy sharing
|
||||
/// let japanese_calendar = Ref(&japanese_calendar);
|
||||
///
|
||||
/// let era = types::Era(tinystr!(16, "heisei"));
|
||||
///
|
||||
/// let date = Date::try_new_japanese_date(era, 14, 1, 2, japanese_calendar)
|
||||
/// .expect("Constructing a date should succeed");
|
||||
///
|
||||
/// assert_eq!(date.year().era, era);
|
||||
/// assert_eq!(date.year().number, 14);
|
||||
/// assert_eq!(date.month().ordinal, 1);
|
||||
/// assert_eq!(date.day_of_month().0, 2);
|
||||
///
|
||||
/// // This function will error for eras that are out of bounds:
|
||||
/// // (Heisei was 32 years long, Heisei 33 is in Reiwa)
|
||||
/// let oob_date =
|
||||
/// Date::try_new_japanese_date(era, 33, 1, 2, japanese_calendar);
|
||||
/// assert!(oob_date.is_err());
|
||||
///
|
||||
/// // and for unknown eras
|
||||
/// let fake_era = types::Era(tinystr!(16, "neko")); // 🐱
|
||||
/// let fake_date =
|
||||
/// Date::try_new_japanese_date(fake_era, 10, 1, 2, japanese_calendar);
|
||||
/// assert!(fake_date.is_err());
|
||||
/// ```
|
||||
pub fn try_new_japanese_date<A: AsCalendar<Calendar = Japanese>>(
|
||||
era: types::Era,
|
||||
year: i32,
|
||||
month: u8,
|
||||
day: u8,
|
||||
japanese_calendar: A,
|
||||
) -> Result<Date<A>, CalendarError> {
|
||||
let inner = japanese_calendar
|
||||
.as_calendar()
|
||||
.new_japanese_date_inner(era, year, month, day)?;
|
||||
Ok(Date::from_raw(inner, japanese_calendar))
|
||||
}
|
||||
}
|
||||
|
||||
impl Date<JapaneseExtended> {
|
||||
/// Construct a new Japanese Date with all eras.
|
||||
///
|
||||
/// Years are specified in the era provided, and must be in range for Japanese
|
||||
/// eras (e.g. dates past April 30 Heisei 31 must be in Reiwa; "Jun 5 Heisei 31" and "Jan 1 Heisei 32"
|
||||
/// will not be adjusted to being in Reiwa 1 and 2 respectively)
|
||||
///
|
||||
/// However, dates may always be specified in "bce" or "ce" and they will be adjusted as necessary.
|
||||
///
|
||||
/// ```rust
|
||||
/// use icu::calendar::japanese::JapaneseExtended;
|
||||
/// use icu::calendar::{types, Date, Ref};
|
||||
/// use std::convert::TryFrom;
|
||||
/// use tinystr::tinystr;
|
||||
///
|
||||
/// let japanext_calendar = JapaneseExtended::new();
|
||||
/// // for easy sharing
|
||||
/// let japanext_calendar = Ref(&japanext_calendar);
|
||||
///
|
||||
/// let era = types::Era(tinystr!(16, "kansei-1789"));
|
||||
///
|
||||
/// let date =
|
||||
/// Date::try_new_japanese_extended_date(era, 7, 1, 2, japanext_calendar)
|
||||
/// .expect("Constructing a date should succeed");
|
||||
///
|
||||
/// assert_eq!(date.year().era, era);
|
||||
/// assert_eq!(date.year().number, 7);
|
||||
/// assert_eq!(date.month().ordinal, 1);
|
||||
/// assert_eq!(date.day_of_month().0, 2);
|
||||
/// ```
|
||||
pub fn try_new_japanese_extended_date<A: AsCalendar<Calendar = JapaneseExtended>>(
|
||||
era: types::Era,
|
||||
year: i32,
|
||||
month: u8,
|
||||
day: u8,
|
||||
japanext_calendar: A,
|
||||
) -> Result<Date<A>, CalendarError> {
|
||||
let inner = japanext_calendar
|
||||
.as_calendar()
|
||||
.0
|
||||
.new_japanese_date_inner(era, year, month, day)?;
|
||||
Ok(Date::from_raw(inner, japanext_calendar))
|
||||
}
|
||||
|
||||
/// For testing era fallback in icu_datetime
|
||||
#[doc(hidden)]
|
||||
pub fn into_japanese_date(self) -> Date<Japanese> {
|
||||
Date::from_raw(self.inner, self.calendar.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl DateTime<Japanese> {
|
||||
/// Construct a new Japanese datetime from integers.
|
||||
///
|
||||
/// Years are specified in the era provided.
|
||||
///
|
||||
/// ```rust
|
||||
/// use icu::calendar::japanese::Japanese;
|
||||
/// use icu::calendar::{types, DateTime};
|
||||
/// use std::convert::TryFrom;
|
||||
/// use tinystr::tinystr;
|
||||
///
|
||||
/// let japanese_calendar = Japanese::new();
|
||||
///
|
||||
/// let era = types::Era(tinystr!(16, "heisei"));
|
||||
///
|
||||
/// let datetime = DateTime::try_new_japanese_datetime(
|
||||
/// era,
|
||||
/// 14,
|
||||
/// 1,
|
||||
/// 2,
|
||||
/// 13,
|
||||
/// 1,
|
||||
/// 0,
|
||||
/// japanese_calendar,
|
||||
/// )
|
||||
/// .expect("Constructing a date should succeed");
|
||||
///
|
||||
/// assert_eq!(datetime.date.year().era, era);
|
||||
/// assert_eq!(datetime.date.year().number, 14);
|
||||
/// assert_eq!(datetime.date.month().ordinal, 1);
|
||||
/// assert_eq!(datetime.date.day_of_month().0, 2);
|
||||
/// assert_eq!(datetime.time.hour.number(), 13);
|
||||
/// assert_eq!(datetime.time.minute.number(), 1);
|
||||
/// assert_eq!(datetime.time.second.number(), 0);
|
||||
/// ```
|
||||
#[allow(clippy::too_many_arguments)] // it's more convenient to have this many arguments
|
||||
// if people wish to construct this by parts they can use
|
||||
// Date::try_new_japanese_date() + DateTime::new(date, time)
|
||||
pub fn try_new_japanese_datetime<A: AsCalendar<Calendar = Japanese>>(
|
||||
era: types::Era,
|
||||
year: i32,
|
||||
month: u8,
|
||||
day: u8,
|
||||
hour: u8,
|
||||
minute: u8,
|
||||
second: u8,
|
||||
japanese_calendar: A,
|
||||
) -> Result<DateTime<A>, CalendarError> {
|
||||
Ok(DateTime {
|
||||
date: Date::try_new_japanese_date(era, year, month, day, japanese_calendar)?,
|
||||
time: types::Time::try_new(hour, minute, second, 0)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl DateTime<JapaneseExtended> {
|
||||
/// Construct a new Japanese datetime from integers with all eras.
|
||||
///
|
||||
/// Years are specified in the era provided.
|
||||
///
|
||||
/// ```rust
|
||||
/// use icu::calendar::japanese::JapaneseExtended;
|
||||
/// use icu::calendar::{types, DateTime};
|
||||
/// use std::convert::TryFrom;
|
||||
/// use tinystr::tinystr;
|
||||
///
|
||||
/// let japanext_calendar = JapaneseExtended::new();
|
||||
///
|
||||
/// let era = types::Era(tinystr!(16, "kansei-1789"));
|
||||
///
|
||||
/// let datetime = DateTime::try_new_japanese_extended_datetime(
|
||||
/// era,
|
||||
/// 7,
|
||||
/// 1,
|
||||
/// 2,
|
||||
/// 13,
|
||||
/// 1,
|
||||
/// 0,
|
||||
/// japanext_calendar,
|
||||
/// )
|
||||
/// .expect("Constructing a date should succeed");
|
||||
///
|
||||
/// assert_eq!(datetime.date.year().era, era);
|
||||
/// assert_eq!(datetime.date.year().number, 7);
|
||||
/// assert_eq!(datetime.date.month().ordinal, 1);
|
||||
/// assert_eq!(datetime.date.day_of_month().0, 2);
|
||||
/// assert_eq!(datetime.time.hour.number(), 13);
|
||||
/// assert_eq!(datetime.time.minute.number(), 1);
|
||||
/// assert_eq!(datetime.time.second.number(), 0);
|
||||
/// ```
|
||||
#[allow(clippy::too_many_arguments)] // it's more convenient to have this many arguments
|
||||
// if people wish to construct this by parts they can use
|
||||
// Date::try_new_japanese_date() + DateTime::new(date, time)
|
||||
pub fn try_new_japanese_extended_datetime<A: AsCalendar<Calendar = JapaneseExtended>>(
|
||||
era: types::Era,
|
||||
year: i32,
|
||||
month: u8,
|
||||
day: u8,
|
||||
hour: u8,
|
||||
minute: u8,
|
||||
second: u8,
|
||||
japanext_calendar: A,
|
||||
) -> Result<DateTime<A>, CalendarError> {
|
||||
Ok(DateTime {
|
||||
date: Date::try_new_japanese_extended_date(era, year, month, day, japanext_calendar)?,
|
||||
time: types::Time::try_new(hour, minute, second, 0)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const MEIJI_START: EraStartDate = EraStartDate {
|
||||
year: 1868,
|
||||
month: 9,
|
||||
day: 8,
|
||||
};
|
||||
const TAISHO_START: EraStartDate = EraStartDate {
|
||||
year: 1912,
|
||||
month: 7,
|
||||
day: 30,
|
||||
};
|
||||
const SHOWA_START: EraStartDate = EraStartDate {
|
||||
year: 1926,
|
||||
month: 12,
|
||||
day: 25,
|
||||
};
|
||||
const HEISEI_START: EraStartDate = EraStartDate {
|
||||
year: 1989,
|
||||
month: 1,
|
||||
day: 8,
|
||||
};
|
||||
const REIWA_START: EraStartDate = EraStartDate {
|
||||
year: 2019,
|
||||
month: 5,
|
||||
day: 1,
|
||||
};
|
||||
|
||||
const FALLBACK_ERA: (EraStartDate, TinyStr16) = (REIWA_START, tinystr!(16, "reiwa"));
|
||||
|
||||
impl Japanese {
|
||||
/// Given an ISO date, give year and era for that date in the Japanese calendar
|
||||
///
|
||||
/// This will also use Gregorian eras for eras that are before the earliest era
|
||||
fn adjusted_year_for(&self, date: &IsoDateInner) -> (i32, TinyStr16) {
|
||||
let date: EraStartDate = date.into();
|
||||
let (start, era) = self.japanese_era_for(date);
|
||||
// The year in which an era starts is Year 1, and it may be short
|
||||
// The only time this function will experience dates that are *before*
|
||||
// the era start date are for the first era (Currently, taika-645
|
||||
// for japanext, meiji for japanese),
|
||||
// In such a case, we instead fall back to Gregorian era codes
|
||||
if date < start {
|
||||
if date.year < 0 {
|
||||
(1 - date.year, tinystr!(16, "bce"))
|
||||
} else {
|
||||
(date.year, tinystr!(16, "ce"))
|
||||
}
|
||||
} else {
|
||||
(date.year - start.year + 1, era)
|
||||
}
|
||||
}
|
||||
|
||||
/// Given an date, obtain the era data (not counting spliced gregorian eras)
|
||||
fn japanese_era_for(&self, date: EraStartDate) -> (EraStartDate, TinyStr16) {
|
||||
let era_data = self.eras.get();
|
||||
// We optimize for the five "modern" post-Meiji eras, which are stored in a smaller
|
||||
// array and also hardcoded. The hardcoded version is not used if data indicates the
|
||||
// presence of newer eras.
|
||||
if date >= MEIJI_START
|
||||
&& era_data.dates_to_eras.last().map(|x| x.1) == Some(tinystr!(16, "reiwa"))
|
||||
{
|
||||
// Fast path in case eras have not changed since this code was written
|
||||
return if date >= REIWA_START {
|
||||
(REIWA_START, tinystr!(16, "reiwa"))
|
||||
} else if date >= HEISEI_START {
|
||||
(HEISEI_START, tinystr!(16, "heisei"))
|
||||
} else if date >= SHOWA_START {
|
||||
(SHOWA_START, tinystr!(16, "showa"))
|
||||
} else if date >= TAISHO_START {
|
||||
(TAISHO_START, tinystr!(16, "taisho"))
|
||||
} else {
|
||||
(MEIJI_START, tinystr!(16, "meiji"))
|
||||
};
|
||||
}
|
||||
let data = &era_data.dates_to_eras;
|
||||
match data.binary_search_by(|(d, _)| d.cmp(&date)) {
|
||||
Ok(index) => data.get(index),
|
||||
Err(index) if index == 0 => data.get(index),
|
||||
Err(index) => data.get(index - 1).or_else(|| data.iter().next_back()),
|
||||
}
|
||||
.unwrap_or(FALLBACK_ERA)
|
||||
}
|
||||
|
||||
/// Returns the range of dates for a given Japanese era code,
|
||||
/// not handling "bce" or "ce"
|
||||
///
|
||||
/// Returns (era_start, era_end)
|
||||
fn japanese_era_range_for(
|
||||
&self,
|
||||
era: TinyStr16,
|
||||
) -> Result<(EraStartDate, Option<EraStartDate>), CalendarError> {
|
||||
// Avoid linear search by trying well known eras
|
||||
if era == tinystr!(16, "reiwa") {
|
||||
// Check if we're the last
|
||||
if let Some(last) = self.eras.get().dates_to_eras.last() {
|
||||
if last.1 == era {
|
||||
return Ok((REIWA_START, None));
|
||||
}
|
||||
}
|
||||
} else if era == tinystr!(16, "heisei") {
|
||||
return Ok((HEISEI_START, Some(REIWA_START)));
|
||||
} else if era == tinystr!(16, "showa") {
|
||||
return Ok((SHOWA_START, Some(HEISEI_START)));
|
||||
} else if era == tinystr!(16, "taisho") {
|
||||
return Ok((TAISHO_START, Some(SHOWA_START)));
|
||||
} else if era == tinystr!(16, "meiji") {
|
||||
return Ok((MEIJI_START, Some(TAISHO_START)));
|
||||
}
|
||||
|
||||
let era_data = self.eras.get();
|
||||
let data = &era_data.dates_to_eras;
|
||||
// Try to avoid linear search by binary searching for the year suffix
|
||||
if let Some(year) = era.split('-').nth(1) {
|
||||
if let Ok(ref int) = year.parse::<i32>() {
|
||||
if let Ok(index) = data.binary_search_by(|(d, _)| d.year.cmp(int)) {
|
||||
#[allow(clippy::expect_used)] // see expect message
|
||||
let (era_start, code) = data
|
||||
.get(index)
|
||||
.expect("Indexing from successful binary search must succeed");
|
||||
// There is a slight chance we hit the case where there are two eras in the same year
|
||||
// There are a couple of rare cases of this, but it's not worth writing a range-based binary search
|
||||
// to catch them since this is an optimization
|
||||
if code == era {
|
||||
return Ok((era_start, data.get(index + 1).map(|e| e.0)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Avoidance didn't work. Let's find the era manually, searching back from the present
|
||||
if let Some((index, (start, _))) = data.iter().enumerate().rev().find(|d| d.1 .1 == era) {
|
||||
return Ok((start, data.get(index + 1).map(|e| e.0)));
|
||||
}
|
||||
|
||||
Err(CalendarError::UnknownEra(era, self.debug_name()))
|
||||
}
|
||||
|
||||
fn new_japanese_date_inner(
|
||||
&self,
|
||||
era: types::Era,
|
||||
year: i32,
|
||||
month: u8,
|
||||
day: u8,
|
||||
) -> Result<JapaneseDateInner, CalendarError> {
|
||||
let cal = Ref(self);
|
||||
if era.0 == tinystr!(16, "bce") {
|
||||
if year <= 0 {
|
||||
return Err(CalendarError::OutOfRange);
|
||||
}
|
||||
return Ok(Date::try_new_iso_date(1 - year, month, day)?
|
||||
.to_calendar(cal)
|
||||
.inner);
|
||||
} else if era.0 == tinystr!(16, "ce") {
|
||||
if year <= 0 {
|
||||
return Err(CalendarError::OutOfRange);
|
||||
}
|
||||
return Ok(Date::try_new_iso_date(year, month, day)?
|
||||
.to_calendar(cal)
|
||||
.inner);
|
||||
}
|
||||
|
||||
let (era_start, next_era_start) = self.japanese_era_range_for(era.0)?;
|
||||
|
||||
let date_in_iso = EraStartDate {
|
||||
year: era_start.year + year - 1,
|
||||
month,
|
||||
day,
|
||||
};
|
||||
|
||||
if date_in_iso < era_start {
|
||||
return Err(CalendarError::OutOfRange);
|
||||
} else if let Some(next_era_start) = next_era_start {
|
||||
if date_in_iso >= next_era_start {
|
||||
return Err(CalendarError::OutOfRange);
|
||||
}
|
||||
}
|
||||
|
||||
let iso = Date::try_new_iso_date(date_in_iso.year, date_in_iso.month, date_in_iso.day)?;
|
||||
Ok(JapaneseDateInner {
|
||||
inner: iso.inner,
|
||||
adjusted_year: year,
|
||||
era: era.0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::Ref;
|
||||
|
||||
fn single_test_roundtrip(calendar: Ref<Japanese>, era: &str, year: i32, month: u8, day: u8) {
|
||||
let era = types::Era(era.parse().expect("era must parse"));
|
||||
|
||||
let date =
|
||||
Date::try_new_japanese_date(era, year, month, day, calendar).unwrap_or_else(|e| {
|
||||
panic!("Failed to construct date with {era:?}, {year}, {month}, {day}: {e}")
|
||||
});
|
||||
let iso = date.to_iso();
|
||||
let reconstructed = Date::new_from_iso(iso, calendar);
|
||||
assert_eq!(
|
||||
date, reconstructed,
|
||||
"Failed to roundtrip with {era:?}, {year}, {month}, {day}"
|
||||
)
|
||||
}
|
||||
|
||||
fn single_test_roundtrip_ext(
|
||||
calendar: Ref<JapaneseExtended>,
|
||||
era: &str,
|
||||
year: i32,
|
||||
month: u8,
|
||||
day: u8,
|
||||
) {
|
||||
let era = types::Era(era.parse().expect("era must parse"));
|
||||
|
||||
let date = Date::try_new_japanese_extended_date(era, year, month, day, calendar)
|
||||
.unwrap_or_else(|e| {
|
||||
panic!("Failed to construct date with {era:?}, {year}, {month}, {day}: {e}")
|
||||
});
|
||||
let iso = date.to_iso();
|
||||
let reconstructed = Date::new_from_iso(iso, calendar);
|
||||
assert_eq!(
|
||||
date, reconstructed,
|
||||
"Failed to roundtrip with {era:?}, {year}, {month}, {day}"
|
||||
)
|
||||
}
|
||||
|
||||
// test that the Gregorian eras roundtrip to Japanese ones
|
||||
fn single_test_gregorian_roundtrip_ext(
|
||||
calendar: Ref<JapaneseExtended>,
|
||||
era: &str,
|
||||
year: i32,
|
||||
month: u8,
|
||||
day: u8,
|
||||
era2: &str,
|
||||
year2: i32,
|
||||
) {
|
||||
let era = types::Era(era.parse().expect("era must parse"));
|
||||
let era2 = types::Era(era2.parse().expect("era must parse"));
|
||||
|
||||
let expected = Date::try_new_japanese_extended_date(era2, year2, month, day, calendar)
|
||||
.unwrap_or_else(|e| {
|
||||
panic!(
|
||||
"Failed to construct expectation date with {era2:?}, {year2}, {month}, {day}: {e}"
|
||||
)
|
||||
});
|
||||
|
||||
let date = Date::try_new_japanese_extended_date(era, year, month, day, calendar)
|
||||
.unwrap_or_else(|e| {
|
||||
panic!("Failed to construct date with {era:?}, {year}, {month}, {day}: {e}")
|
||||
});
|
||||
let iso = date.to_iso();
|
||||
let reconstructed = Date::new_from_iso(iso, calendar);
|
||||
assert_eq!(
|
||||
expected, reconstructed,
|
||||
"Failed to roundtrip with {era:?}, {year}, {month}, {day} == {era2:?}, {year}"
|
||||
)
|
||||
}
|
||||
|
||||
fn single_test_error(
|
||||
calendar: Ref<Japanese>,
|
||||
era: &str,
|
||||
year: i32,
|
||||
month: u8,
|
||||
day: u8,
|
||||
error: CalendarError,
|
||||
) {
|
||||
let era = types::Era(era.parse().expect("era must parse"));
|
||||
|
||||
let date = Date::try_new_japanese_date(era, year, month, day, calendar);
|
||||
assert_eq!(
|
||||
date,
|
||||
Err(error),
|
||||
"Construction with {era:?}, {year}, {month}, {day} did not return {error:?}"
|
||||
)
|
||||
}
|
||||
|
||||
fn single_test_error_ext(
|
||||
calendar: Ref<JapaneseExtended>,
|
||||
era: &str,
|
||||
year: i32,
|
||||
month: u8,
|
||||
day: u8,
|
||||
error: CalendarError,
|
||||
) {
|
||||
let era = types::Era(era.parse().expect("era must parse"));
|
||||
|
||||
let date = Date::try_new_japanese_extended_date(era, year, month, day, calendar);
|
||||
assert_eq!(
|
||||
date,
|
||||
Err(error),
|
||||
"Construction with {era:?}, {year}, {month}, {day} did not return {error:?}"
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_japanese() {
|
||||
let calendar = Japanese::new();
|
||||
let calendar_ext = JapaneseExtended::new();
|
||||
let calendar = Ref(&calendar);
|
||||
let calendar_ext = Ref(&calendar_ext);
|
||||
|
||||
single_test_roundtrip(calendar, "heisei", 12, 3, 1);
|
||||
single_test_roundtrip(calendar, "taisho", 3, 3, 1);
|
||||
// Heisei did not start until later in the year
|
||||
single_test_error(calendar, "heisei", 1, 1, 1, CalendarError::OutOfRange);
|
||||
|
||||
single_test_roundtrip_ext(calendar_ext, "heisei", 12, 3, 1);
|
||||
single_test_roundtrip_ext(calendar_ext, "taisho", 3, 3, 1);
|
||||
single_test_error_ext(calendar_ext, "heisei", 1, 1, 1, CalendarError::OutOfRange);
|
||||
|
||||
single_test_roundtrip_ext(calendar_ext, "hakuho-672", 4, 3, 1);
|
||||
single_test_error(
|
||||
calendar,
|
||||
"hakuho-672",
|
||||
4,
|
||||
3,
|
||||
1,
|
||||
CalendarError::UnknownEra("hakuho-672".parse().unwrap(), "Japanese"),
|
||||
);
|
||||
|
||||
// handle bce/ce
|
||||
single_test_roundtrip(calendar, "bce", 100, 3, 1);
|
||||
single_test_roundtrip(calendar, "bce", 1, 3, 1);
|
||||
single_test_roundtrip(calendar, "ce", 1, 3, 1);
|
||||
single_test_roundtrip(calendar, "ce", 100, 3, 1);
|
||||
single_test_roundtrip_ext(calendar_ext, "ce", 100, 3, 1);
|
||||
single_test_roundtrip(calendar, "ce", 1000, 3, 1);
|
||||
single_test_error(calendar, "ce", 0, 3, 1, CalendarError::OutOfRange);
|
||||
single_test_error(calendar, "bce", -1, 3, 1, CalendarError::OutOfRange);
|
||||
|
||||
// handle the cases where bce/ce get adjusted to different eras
|
||||
// single_test_gregorian_roundtrip(calendar, "ce", 2021, 3, 1, "reiwa", 3);
|
||||
single_test_gregorian_roundtrip_ext(calendar_ext, "ce", 1000, 3, 1, "choho-999", 2);
|
||||
single_test_gregorian_roundtrip_ext(calendar_ext, "ce", 749, 5, 10, "tenpyokampo-749", 1);
|
||||
single_test_gregorian_roundtrip_ext(calendar_ext, "bce", 10, 3, 1, "bce", 10);
|
||||
|
||||
// There were multiple eras in this year
|
||||
// This one is from Apr 14 to July 2
|
||||
single_test_roundtrip_ext(calendar_ext, "tenpyokampo-749", 1, 4, 20);
|
||||
single_test_roundtrip_ext(calendar_ext, "tenpyokampo-749", 1, 4, 14);
|
||||
single_test_roundtrip_ext(calendar_ext, "tenpyokampo-749", 1, 7, 1);
|
||||
single_test_error_ext(
|
||||
calendar_ext,
|
||||
"tenpyokampo-749",
|
||||
1,
|
||||
7,
|
||||
5,
|
||||
CalendarError::OutOfRange,
|
||||
);
|
||||
single_test_error_ext(
|
||||
calendar_ext,
|
||||
"tenpyokampo-749",
|
||||
1,
|
||||
4,
|
||||
13,
|
||||
CalendarError::OutOfRange,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,556 @@
|
|||
// This file is part of ICU4X. For terms of use, please see the file
|
||||
// called LICENSE at the top level of the ICU4X source tree
|
||||
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
|
||||
|
||||
//! This module contains types and implementations for the Julian calendar.
|
||||
//!
|
||||
//! ```rust
|
||||
//! use icu::calendar::{julian::Julian, Date, DateTime};
|
||||
//!
|
||||
//! // `Date` type
|
||||
//! let date_iso = Date::try_new_iso_date(1970, 1, 2)
|
||||
//! .expect("Failed to initialize ISO Date instance.");
|
||||
//! let date_julian = Date::new_from_iso(date_iso, Julian);
|
||||
//!
|
||||
//! // `DateTime` type
|
||||
//! let datetime_iso = DateTime::try_new_iso_datetime(1970, 1, 2, 13, 1, 0)
|
||||
//! .expect("Failed to initialize ISO DateTime instance.");
|
||||
//! let datetime_julian = DateTime::new_from_iso(datetime_iso, Julian);
|
||||
//!
|
||||
//! // `Date` checks
|
||||
//! assert_eq!(date_julian.year().number, 1969);
|
||||
//! assert_eq!(date_julian.month().ordinal, 12);
|
||||
//! assert_eq!(date_julian.day_of_month().0, 20);
|
||||
//!
|
||||
//! // `DateTime` type
|
||||
//! assert_eq!(datetime_julian.date.year().number, 1969);
|
||||
//! assert_eq!(datetime_julian.date.month().ordinal, 12);
|
||||
//! assert_eq!(datetime_julian.date.day_of_month().0, 20);
|
||||
//! assert_eq!(datetime_julian.time.hour.number(), 13);
|
||||
//! assert_eq!(datetime_julian.time.minute.number(), 1);
|
||||
//! assert_eq!(datetime_julian.time.second.number(), 0);
|
||||
//! ```
|
||||
|
||||
use crate::any_calendar::AnyCalendarKind;
|
||||
use crate::calendar_arithmetic::{ArithmeticDate, CalendarArithmetic};
|
||||
use crate::gregorian::year_as_gregorian;
|
||||
use crate::iso::Iso;
|
||||
use crate::{types, Calendar, CalendarError, Date, DateDuration, DateDurationUnit, DateTime};
|
||||
use calendrical_calculations::helpers::I32CastError;
|
||||
use calendrical_calculations::rata_die::RataDie;
|
||||
use tinystr::tinystr;
|
||||
|
||||
/// The [Julian Calendar]
|
||||
///
|
||||
/// The [Julian calendar] is a solar calendar that was used commonly historically, with twelve months.
|
||||
///
|
||||
/// This type can be used with [`Date`] or [`DateTime`] to represent dates in this calendar.
|
||||
///
|
||||
/// [Julian calendar]: https://en.wikipedia.org/wiki/Julian_calendar
|
||||
///
|
||||
/// # Era codes
|
||||
///
|
||||
/// This calendar supports two era codes: `"bce"`, and `"ce"`, corresponding to the BCE/BC and CE/AD eras
|
||||
///
|
||||
/// # Month codes
|
||||
///
|
||||
/// This calendar supports 12 solar month codes (`"M01" - "M12"`)
|
||||
#[derive(Copy, Clone, Debug, Hash, Default, Eq, PartialEq, PartialOrd, Ord)]
|
||||
#[allow(clippy::exhaustive_structs)] // this type is stable
|
||||
pub struct Julian;
|
||||
|
||||
/// The inner date type used for representing [`Date`]s of [`Julian`]. See [`Date`] and [`Julian`] for more details.
|
||||
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
|
||||
// The inner date type used for representing Date<Julian>
|
||||
pub struct JulianDateInner(pub(crate) ArithmeticDate<Julian>);
|
||||
|
||||
impl CalendarArithmetic for Julian {
|
||||
fn month_days(year: i32, month: u8) -> u8 {
|
||||
match month {
|
||||
4 | 6 | 9 | 11 => 30,
|
||||
2 if Self::is_leap_year(year) => 29,
|
||||
2 => 28,
|
||||
1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn months_for_every_year(_: i32) -> u8 {
|
||||
12
|
||||
}
|
||||
|
||||
fn is_leap_year(year: i32) -> bool {
|
||||
calendrical_calculations::julian::is_leap_year(year)
|
||||
}
|
||||
|
||||
fn last_month_day_in_year(_year: i32) -> (u8, u8) {
|
||||
(12, 31)
|
||||
}
|
||||
|
||||
fn days_in_provided_year(year: i32) -> u16 {
|
||||
if Self::is_leap_year(year) {
|
||||
366
|
||||
} else {
|
||||
365
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Calendar for Julian {
|
||||
type DateInner = JulianDateInner;
|
||||
fn date_from_codes(
|
||||
&self,
|
||||
era: types::Era,
|
||||
year: i32,
|
||||
month_code: types::MonthCode,
|
||||
day: u8,
|
||||
) -> Result<Self::DateInner, CalendarError> {
|
||||
let year = if era.0 == tinystr!(16, "ce") {
|
||||
if year <= 0 {
|
||||
return Err(CalendarError::OutOfRange);
|
||||
}
|
||||
year
|
||||
} else if era.0 == tinystr!(16, "bce") {
|
||||
if year <= 0 {
|
||||
return Err(CalendarError::OutOfRange);
|
||||
}
|
||||
1 - year
|
||||
} else {
|
||||
return Err(CalendarError::UnknownEra(era.0, self.debug_name()));
|
||||
};
|
||||
|
||||
ArithmeticDate::new_from_codes(self, year, month_code, day).map(JulianDateInner)
|
||||
}
|
||||
fn date_from_iso(&self, iso: Date<Iso>) -> JulianDateInner {
|
||||
let fixed_iso = Iso::fixed_from_iso(*iso.inner());
|
||||
Self::julian_from_fixed(fixed_iso)
|
||||
}
|
||||
|
||||
fn date_to_iso(&self, date: &Self::DateInner) -> Date<Iso> {
|
||||
let fixed_julian = Julian::fixed_from_julian(date.0);
|
||||
Iso::iso_from_fixed(fixed_julian)
|
||||
}
|
||||
|
||||
fn months_in_year(&self, date: &Self::DateInner) -> u8 {
|
||||
date.0.months_in_year()
|
||||
}
|
||||
|
||||
fn days_in_year(&self, date: &Self::DateInner) -> u16 {
|
||||
date.0.days_in_year()
|
||||
}
|
||||
|
||||
fn days_in_month(&self, date: &Self::DateInner) -> u8 {
|
||||
date.0.days_in_month()
|
||||
}
|
||||
|
||||
fn day_of_week(&self, date: &Self::DateInner) -> types::IsoWeekday {
|
||||
Iso.day_of_week(Julian.date_to_iso(date).inner())
|
||||
}
|
||||
|
||||
fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration<Self>) {
|
||||
date.0.offset_date(offset);
|
||||
}
|
||||
|
||||
#[allow(clippy::field_reassign_with_default)]
|
||||
fn until(
|
||||
&self,
|
||||
date1: &Self::DateInner,
|
||||
date2: &Self::DateInner,
|
||||
_calendar2: &Self,
|
||||
_largest_unit: DateDurationUnit,
|
||||
_smallest_unit: DateDurationUnit,
|
||||
) -> DateDuration<Self> {
|
||||
date1.0.until(date2.0, _largest_unit, _smallest_unit)
|
||||
}
|
||||
|
||||
/// The calendar-specific year represented by `date`
|
||||
/// Julian has the same era scheme as Gregorian
|
||||
fn year(&self, date: &Self::DateInner) -> types::FormattableYear {
|
||||
year_as_gregorian(date.0.year)
|
||||
}
|
||||
|
||||
fn is_in_leap_year(&self, date: &Self::DateInner) -> bool {
|
||||
Self::is_leap_year(date.0.year)
|
||||
}
|
||||
|
||||
/// The calendar-specific month represented by `date`
|
||||
fn month(&self, date: &Self::DateInner) -> types::FormattableMonth {
|
||||
date.0.month()
|
||||
}
|
||||
|
||||
/// The calendar-specific day-of-month represented by `date`
|
||||
fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth {
|
||||
date.0.day_of_month()
|
||||
}
|
||||
|
||||
fn day_of_year_info(&self, date: &Self::DateInner) -> types::DayOfYearInfo {
|
||||
let prev_year = date.0.year - 1;
|
||||
let next_year = date.0.year + 1;
|
||||
types::DayOfYearInfo {
|
||||
day_of_year: date.0.day_of_year(),
|
||||
days_in_year: date.0.days_in_year(),
|
||||
prev_year: crate::gregorian::year_as_gregorian(prev_year),
|
||||
days_in_prev_year: Julian::days_in_year_direct(prev_year),
|
||||
next_year: crate::gregorian::year_as_gregorian(next_year),
|
||||
}
|
||||
}
|
||||
|
||||
fn debug_name(&self) -> &'static str {
|
||||
"Julian"
|
||||
}
|
||||
|
||||
fn any_calendar_kind(&self) -> Option<AnyCalendarKind> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Julian {
|
||||
/// Construct a new Julian Calendar
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
// "Fixed" is a day count representation of calendars staring from Jan 1st of year 1 of the Georgian Calendar.
|
||||
pub(crate) const fn fixed_from_julian(date: ArithmeticDate<Julian>) -> RataDie {
|
||||
calendrical_calculations::julian::fixed_from_julian(date.year, date.month, date.day)
|
||||
}
|
||||
|
||||
/// Convenience function so we can call days_in_year without
|
||||
/// needing to construct a full ArithmeticDate
|
||||
fn days_in_year_direct(year: i32) -> u16 {
|
||||
if Julian::is_leap_year(year) {
|
||||
366
|
||||
} else {
|
||||
365
|
||||
}
|
||||
}
|
||||
|
||||
fn julian_from_fixed(date: RataDie) -> JulianDateInner {
|
||||
let (year, month, day) = match calendrical_calculations::julian::julian_from_fixed(date) {
|
||||
Err(I32CastError::BelowMin) => return JulianDateInner(ArithmeticDate::min_date()),
|
||||
Err(I32CastError::AboveMax) => return JulianDateInner(ArithmeticDate::max_date()),
|
||||
Ok(ymd) => ymd,
|
||||
};
|
||||
JulianDateInner(ArithmeticDate::new_unchecked(year, month, day))
|
||||
}
|
||||
}
|
||||
|
||||
impl Date<Julian> {
|
||||
/// Construct new Julian Date.
|
||||
///
|
||||
/// Years are arithmetic, meaning there is a year 0. Zero and negative years are in BC, with year 0 = 1 BC
|
||||
///
|
||||
/// ```rust
|
||||
/// use icu::calendar::Date;
|
||||
///
|
||||
/// let date_julian = Date::try_new_julian_date(1969, 12, 20)
|
||||
/// .expect("Failed to initialize Julian Date instance.");
|
||||
///
|
||||
/// assert_eq!(date_julian.year().number, 1969);
|
||||
/// assert_eq!(date_julian.month().ordinal, 12);
|
||||
/// assert_eq!(date_julian.day_of_month().0, 20);
|
||||
/// ```
|
||||
pub fn try_new_julian_date(
|
||||
year: i32,
|
||||
month: u8,
|
||||
day: u8,
|
||||
) -> Result<Date<Julian>, CalendarError> {
|
||||
ArithmeticDate::new_from_ordinals(year, month, day)
|
||||
.map(JulianDateInner)
|
||||
.map(|inner| Date::from_raw(inner, Julian))
|
||||
}
|
||||
}
|
||||
|
||||
impl DateTime<Julian> {
|
||||
/// Construct a new Julian datetime from integers.
|
||||
///
|
||||
/// Years are arithmetic, meaning there is a year 0. Zero and negative years are in BC, with year 0 = 1 BC
|
||||
///
|
||||
/// ```rust
|
||||
/// use icu::calendar::DateTime;
|
||||
///
|
||||
/// let datetime_julian =
|
||||
/// DateTime::try_new_julian_datetime(1969, 12, 20, 13, 1, 0)
|
||||
/// .expect("Failed to initialize Julian DateTime instance.");
|
||||
///
|
||||
/// assert_eq!(datetime_julian.date.year().number, 1969);
|
||||
/// assert_eq!(datetime_julian.date.month().ordinal, 12);
|
||||
/// assert_eq!(datetime_julian.date.day_of_month().0, 20);
|
||||
/// assert_eq!(datetime_julian.time.hour.number(), 13);
|
||||
/// assert_eq!(datetime_julian.time.minute.number(), 1);
|
||||
/// assert_eq!(datetime_julian.time.second.number(), 0);
|
||||
/// ```
|
||||
pub fn try_new_julian_datetime(
|
||||
year: i32,
|
||||
month: u8,
|
||||
day: u8,
|
||||
hour: u8,
|
||||
minute: u8,
|
||||
second: u8,
|
||||
) -> Result<DateTime<Julian>, CalendarError> {
|
||||
Ok(DateTime {
|
||||
date: Date::try_new_julian_date(year, month, day)?,
|
||||
time: types::Time::try_new(hour, minute, second, 0)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use types::Era;
|
||||
|
||||
#[test]
|
||||
fn test_day_iso_to_julian() {
|
||||
// March 1st 200 is same on both calendars
|
||||
let iso_date = Date::try_new_iso_date(200, 3, 1).unwrap();
|
||||
let julian_date = Julian.date_from_iso(iso_date);
|
||||
assert_eq!(julian_date.0.year, 200);
|
||||
assert_eq!(julian_date.0.month, 3);
|
||||
assert_eq!(julian_date.0.day, 1);
|
||||
|
||||
// Feb 28th, 200 (iso) = Feb 29th, 200 (julian)
|
||||
let iso_date = Date::try_new_iso_date(200, 2, 28).unwrap();
|
||||
let julian_date = Julian.date_from_iso(iso_date);
|
||||
assert_eq!(julian_date.0.year, 200);
|
||||
assert_eq!(julian_date.0.month, 2);
|
||||
assert_eq!(julian_date.0.day, 29);
|
||||
|
||||
// March 1st 400 (iso) = Feb 29th, 400 (julian)
|
||||
let iso_date = Date::try_new_iso_date(400, 3, 1).unwrap();
|
||||
let julian_date = Julian.date_from_iso(iso_date);
|
||||
assert_eq!(julian_date.0.year, 400);
|
||||
assert_eq!(julian_date.0.month, 2);
|
||||
assert_eq!(julian_date.0.day, 29);
|
||||
|
||||
// Jan 1st, 2022 (iso) = Dec 19, 2021 (julian)
|
||||
let iso_date = Date::try_new_iso_date(2022, 1, 1).unwrap();
|
||||
let julian_date = Julian.date_from_iso(iso_date);
|
||||
assert_eq!(julian_date.0.year, 2021);
|
||||
assert_eq!(julian_date.0.month, 12);
|
||||
assert_eq!(julian_date.0.day, 19);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_day_julian_to_iso() {
|
||||
// March 1st 200 is same on both calendars
|
||||
let julian_date = Date::try_new_julian_date(200, 3, 1).unwrap();
|
||||
let iso_date = Julian.date_to_iso(julian_date.inner());
|
||||
let iso_expected_date = Date::try_new_iso_date(200, 3, 1).unwrap();
|
||||
assert_eq!(iso_date, iso_expected_date);
|
||||
|
||||
// Feb 28th, 200 (iso) = Feb 29th, 200 (julian)
|
||||
let julian_date = Date::try_new_julian_date(200, 2, 29).unwrap();
|
||||
let iso_date = Julian.date_to_iso(julian_date.inner());
|
||||
let iso_expected_date = Date::try_new_iso_date(200, 2, 28).unwrap();
|
||||
assert_eq!(iso_date, iso_expected_date);
|
||||
|
||||
// March 1st 400 (iso) = Feb 29th, 400 (julian)
|
||||
let julian_date = Date::try_new_julian_date(400, 2, 29).unwrap();
|
||||
let iso_date = Julian.date_to_iso(julian_date.inner());
|
||||
let iso_expected_date = Date::try_new_iso_date(400, 3, 1).unwrap();
|
||||
assert_eq!(iso_date, iso_expected_date);
|
||||
|
||||
// Jan 1st, 2022 (iso) = Dec 19, 2021 (julian)
|
||||
let julian_date = Date::try_new_julian_date(2021, 12, 19).unwrap();
|
||||
let iso_date = Julian.date_to_iso(julian_date.inner());
|
||||
let iso_expected_date = Date::try_new_iso_date(2022, 1, 1).unwrap();
|
||||
assert_eq!(iso_date, iso_expected_date);
|
||||
|
||||
// March 1st, 2022 (iso) = Feb 16, 2022 (julian)
|
||||
let julian_date = Date::try_new_julian_date(2022, 2, 16).unwrap();
|
||||
let iso_date = Julian.date_to_iso(julian_date.inner());
|
||||
let iso_expected_date = Date::try_new_iso_date(2022, 3, 1).unwrap();
|
||||
assert_eq!(iso_date, iso_expected_date);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_roundtrip_negative() {
|
||||
// https://github.com/unicode-org/icu4x/issues/2254
|
||||
let iso_date = Date::try_new_iso_date(-1000, 3, 3).unwrap();
|
||||
let julian = iso_date.to_calendar(Julian::new());
|
||||
let recovered_iso = julian.to_iso();
|
||||
assert_eq!(iso_date, recovered_iso);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_julian_near_era_change() {
|
||||
// Tests that the Julian calendar gives the correct expected
|
||||
// day, month, and year for positive years (CE)
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TestCase {
|
||||
fixed_date: i64,
|
||||
iso_year: i32,
|
||||
iso_month: u8,
|
||||
iso_day: u8,
|
||||
expected_year: i32,
|
||||
expected_era: Era,
|
||||
expected_month: u32,
|
||||
expected_day: u32,
|
||||
}
|
||||
|
||||
let cases = [
|
||||
TestCase {
|
||||
fixed_date: 1,
|
||||
iso_year: 1,
|
||||
iso_month: 1,
|
||||
iso_day: 1,
|
||||
expected_year: 1,
|
||||
expected_era: Era(tinystr!(16, "ce")),
|
||||
expected_month: 1,
|
||||
expected_day: 3,
|
||||
},
|
||||
TestCase {
|
||||
fixed_date: 0,
|
||||
iso_year: 0,
|
||||
iso_month: 12,
|
||||
iso_day: 31,
|
||||
expected_year: 1,
|
||||
expected_era: Era(tinystr!(16, "ce")),
|
||||
expected_month: 1,
|
||||
expected_day: 2,
|
||||
},
|
||||
TestCase {
|
||||
fixed_date: -1,
|
||||
iso_year: 0,
|
||||
iso_month: 12,
|
||||
iso_day: 30,
|
||||
expected_year: 1,
|
||||
expected_era: Era(tinystr!(16, "ce")),
|
||||
expected_month: 1,
|
||||
expected_day: 1,
|
||||
},
|
||||
TestCase {
|
||||
fixed_date: -2,
|
||||
iso_year: 0,
|
||||
iso_month: 12,
|
||||
iso_day: 29,
|
||||
expected_year: 1,
|
||||
expected_era: Era(tinystr!(16, "bce")),
|
||||
expected_month: 12,
|
||||
expected_day: 31,
|
||||
},
|
||||
TestCase {
|
||||
fixed_date: -3,
|
||||
iso_year: 0,
|
||||
iso_month: 12,
|
||||
iso_day: 28,
|
||||
expected_year: 1,
|
||||
expected_era: Era(tinystr!(16, "bce")),
|
||||
expected_month: 12,
|
||||
expected_day: 30,
|
||||
},
|
||||
TestCase {
|
||||
fixed_date: -367,
|
||||
iso_year: -1,
|
||||
iso_month: 12,
|
||||
iso_day: 30,
|
||||
expected_year: 1,
|
||||
expected_era: Era(tinystr!(16, "bce")),
|
||||
expected_month: 1,
|
||||
expected_day: 1,
|
||||
},
|
||||
TestCase {
|
||||
fixed_date: -368,
|
||||
iso_year: -1,
|
||||
iso_month: 12,
|
||||
iso_day: 29,
|
||||
expected_year: 2,
|
||||
expected_era: Era(tinystr!(16, "bce")),
|
||||
expected_month: 12,
|
||||
expected_day: 31,
|
||||
},
|
||||
TestCase {
|
||||
fixed_date: -1462,
|
||||
iso_year: -4,
|
||||
iso_month: 12,
|
||||
iso_day: 30,
|
||||
expected_year: 4,
|
||||
expected_era: Era(tinystr!(16, "bce")),
|
||||
expected_month: 1,
|
||||
expected_day: 1,
|
||||
},
|
||||
TestCase {
|
||||
fixed_date: -1463,
|
||||
iso_year: -4,
|
||||
iso_month: 12,
|
||||
iso_day: 29,
|
||||
expected_year: 5,
|
||||
expected_era: Era(tinystr!(16, "bce")),
|
||||
expected_month: 12,
|
||||
expected_day: 31,
|
||||
},
|
||||
];
|
||||
|
||||
for case in cases {
|
||||
let iso_from_fixed: Date<Iso> = Iso::iso_from_fixed(RataDie::new(case.fixed_date));
|
||||
let julian_from_fixed: Date<Julian> = Date::new_from_iso(iso_from_fixed, Julian);
|
||||
assert_eq!(julian_from_fixed.year().number, case.expected_year,
|
||||
"Failed year check from fixed: {case:?}\nISO: {iso_from_fixed:?}\nJulian: {julian_from_fixed:?}");
|
||||
assert_eq!(julian_from_fixed.year().era, case.expected_era,
|
||||
"Failed era check from fixed: {case:?}\nISO: {iso_from_fixed:?}\nJulian: {julian_from_fixed:?}");
|
||||
assert_eq!(julian_from_fixed.month().ordinal, case.expected_month,
|
||||
"Failed month check from fixed: {case:?}\nISO: {iso_from_fixed:?}\nJulian: {julian_from_fixed:?}");
|
||||
assert_eq!(julian_from_fixed.day_of_month().0, case.expected_day,
|
||||
"Failed day check from fixed: {case:?}\nISO: {iso_from_fixed:?}\nJulian: {julian_from_fixed:?}");
|
||||
|
||||
let iso_date_man: Date<Iso> =
|
||||
Date::try_new_iso_date(case.iso_year, case.iso_month, case.iso_day)
|
||||
.expect("Failed to initialize ISO date for {case:?}");
|
||||
let julian_date_man: Date<Julian> = Date::new_from_iso(iso_date_man, Julian);
|
||||
assert_eq!(iso_from_fixed, iso_date_man,
|
||||
"ISO from fixed not equal to ISO generated from manually-input ymd\nCase: {case:?}\nFixed: {iso_from_fixed:?}\nMan: {iso_date_man:?}");
|
||||
assert_eq!(julian_from_fixed, julian_date_man,
|
||||
"Julian from fixed not equal to Julian generated from manually-input ymd\nCase: {case:?}\nFixed: {julian_from_fixed:?}\nMan: {julian_date_man:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_julian_fixed_date_conversion() {
|
||||
// Tests that converting from fixed date to Julian then
|
||||
// back to fixed date yields the same fixed date
|
||||
for i in -10000..=10000 {
|
||||
let fixed = RataDie::new(i);
|
||||
let julian = Julian::julian_from_fixed(fixed);
|
||||
let new_fixed = Julian::fixed_from_julian(julian.0);
|
||||
assert_eq!(fixed, new_fixed);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_julian_directionality() {
|
||||
// Tests that for a large range of fixed dates, if a fixed date
|
||||
// is less than another, the corresponding YMD should also be less
|
||||
// than the other, without exception.
|
||||
for i in -100..=100 {
|
||||
for j in -100..=100 {
|
||||
let julian_i = Julian::julian_from_fixed(RataDie::new(i)).0;
|
||||
let julian_j = Julian::julian_from_fixed(RataDie::new(j)).0;
|
||||
|
||||
assert_eq!(
|
||||
i.cmp(&j),
|
||||
julian_i.cmp(&julian_j),
|
||||
"Julian directionality inconsistent with directionality for i: {i}, j: {j}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hebrew_epoch() {
|
||||
assert_eq!(
|
||||
calendrical_calculations::julian::fixed_from_julian_book_version(-3761, 10, 7),
|
||||
RataDie::new(-1373427)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_julian_leap_years() {
|
||||
assert!(Julian::is_leap_year(4));
|
||||
assert!(Julian::is_leap_year(0));
|
||||
assert!(Julian::is_leap_year(-4));
|
||||
|
||||
Date::try_new_julian_date(2020, 2, 29).unwrap();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,165 @@
|
|||
// This file is part of ICU4X. For terms of use, please see the file
|
||||
// called LICENSE at the top level of the ICU4X source tree
|
||||
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
|
||||
|
||||
//! Types for dealing with dates, times, and custom calendars.
|
||||
//!
|
||||
//! This module is published as its own crate ([`icu_calendar`](https://docs.rs/icu_calendar/latest/icu_calendar/))
|
||||
//! and as part of the [`icu`](https://docs.rs/icu/latest/icu/) crate. See the latter for more details on the ICU4X project.
|
||||
//! The [`types`] module has a lot of common types for dealing with dates and times.
|
||||
//!
|
||||
//! [`Calendar`] is a trait that allows one to define custom calendars, and [`Date`]
|
||||
//! can represent dates for arbitrary calendars.
|
||||
//!
|
||||
//! The [`iso`] and [`gregorian`] modules contain implementations for the ISO and
|
||||
//! Gregorian calendars respectively. Further calendars can be found in modules like
|
||||
//! [`japanese`], [`julian`], [`coptic`], [`indian`], [`buddhist`], and [`ethiopian`].
|
||||
//!
|
||||
//! Most interaction with this crate will be done via the [`Date`] and [`DateTime`] types.
|
||||
//!
|
||||
//! Some of the algorithms implemented here are based on
|
||||
//! Dershowitz, Nachum, and Edward M. Reingold. _Calendrical calculations_. Cambridge University Press, 2008.
|
||||
//! with associated Lisp code found at <https://github.com/EdReingold/calendar-code2>.
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! Examples of date manipulation using `Date` object. `Date` objects are useful
|
||||
//! for working with dates, encompassing information about the day, month, year,
|
||||
//! as well as the calendar type.
|
||||
//!
|
||||
//! ```rust
|
||||
//! use icu::calendar::{types::IsoWeekday, Date};
|
||||
//!
|
||||
//! // Creating ISO date: 1992-09-02.
|
||||
//! let mut date_iso = Date::try_new_iso_date(1992, 9, 2)
|
||||
//! .expect("Failed to initialize ISO Date instance.");
|
||||
//!
|
||||
//! assert_eq!(date_iso.day_of_week(), IsoWeekday::Wednesday);
|
||||
//! assert_eq!(date_iso.year().number, 1992);
|
||||
//! assert_eq!(date_iso.month().ordinal, 9);
|
||||
//! assert_eq!(date_iso.day_of_month().0, 2);
|
||||
//!
|
||||
//! // Answering questions about days in month and year.
|
||||
//! assert_eq!(date_iso.days_in_year(), 366);
|
||||
//! assert_eq!(date_iso.days_in_month(), 30);
|
||||
//! ```
|
||||
//!
|
||||
//! Example of converting an ISO date across Indian and Buddhist calendars.
|
||||
//!
|
||||
//! ```rust
|
||||
//! use icu::calendar::{buddhist::Buddhist, indian::Indian, Date};
|
||||
//!
|
||||
//! // Creating ISO date: 1992-09-02.
|
||||
//! let mut date_iso = Date::try_new_iso_date(1992, 9, 2)
|
||||
//! .expect("Failed to initialize ISO Date instance.");
|
||||
//!
|
||||
//! assert_eq!(date_iso.year().number, 1992);
|
||||
//! assert_eq!(date_iso.month().ordinal, 9);
|
||||
//! assert_eq!(date_iso.day_of_month().0, 2);
|
||||
//!
|
||||
//! // Conversion into Indian calendar: 1914-08-02.
|
||||
//! let date_indian = date_iso.to_calendar(Indian);
|
||||
//! assert_eq!(date_indian.year().number, 1914);
|
||||
//! assert_eq!(date_indian.month().ordinal, 6);
|
||||
//! assert_eq!(date_indian.day_of_month().0, 11);
|
||||
//!
|
||||
//! // Conversion into Buddhist calendar: 2535-09-02.
|
||||
//! let date_buddhist = date_iso.to_calendar(Buddhist);
|
||||
//! assert_eq!(date_buddhist.year().number, 2535);
|
||||
//! assert_eq!(date_buddhist.month().ordinal, 9);
|
||||
//! assert_eq!(date_buddhist.day_of_month().0, 2);
|
||||
//! ```
|
||||
//!
|
||||
//! Example using `DateTime` object. Similar to `Date` objects, `DateTime` objects
|
||||
//! contain an accessible `Date` object containing information about the day, month,
|
||||
//! year, and calendar type. Additionally, `DateTime` objects contain an accessible
|
||||
//! `Time` object, including granularity of hour, minute, second, and nanosecond.
|
||||
//!
|
||||
//! ```rust
|
||||
//! use icu::calendar::{types::IsoWeekday, types::Time, DateTime};
|
||||
//!
|
||||
//! // Creating ISO date: 1992-09-02 8:59
|
||||
//! let mut datetime_iso = DateTime::try_new_iso_datetime(1992, 9, 2, 8, 59, 0)
|
||||
//! .expect("Failed to initialize ISO DateTime instance.");
|
||||
//!
|
||||
//! assert_eq!(datetime_iso.date.day_of_week(), IsoWeekday::Wednesday);
|
||||
//! assert_eq!(datetime_iso.date.year().number, 1992);
|
||||
//! assert_eq!(datetime_iso.date.month().ordinal, 9);
|
||||
//! assert_eq!(datetime_iso.date.day_of_month().0, 2);
|
||||
//! assert_eq!(datetime_iso.time.hour.number(), 8);
|
||||
//! assert_eq!(datetime_iso.time.minute.number(), 59);
|
||||
//! assert_eq!(datetime_iso.time.second.number(), 0);
|
||||
//! assert_eq!(datetime_iso.time.nanosecond.number(), 0);
|
||||
//! ```
|
||||
//! [`ICU4X`]: ../icu/index.html
|
||||
|
||||
// https://github.com/unicode-org/icu4x/blob/main/docs/process/boilerplate.md#library-annotations
|
||||
#![cfg_attr(not(any(test, feature = "std")), no_std)]
|
||||
#![cfg_attr(
|
||||
not(test),
|
||||
deny(
|
||||
clippy::indexing_slicing,
|
||||
clippy::unwrap_used,
|
||||
clippy::expect_used,
|
||||
clippy::panic,
|
||||
clippy::exhaustive_structs,
|
||||
clippy::exhaustive_enums,
|
||||
missing_debug_implementations,
|
||||
)
|
||||
)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
// Make sure inherent docs go first
|
||||
mod date;
|
||||
mod datetime;
|
||||
|
||||
pub mod any_calendar;
|
||||
pub mod buddhist;
|
||||
mod calendar;
|
||||
mod calendar_arithmetic;
|
||||
pub mod chinese;
|
||||
mod chinese_based;
|
||||
mod chinese_data;
|
||||
pub mod coptic;
|
||||
pub mod dangi;
|
||||
mod duration;
|
||||
mod error;
|
||||
pub mod ethiopian;
|
||||
pub mod gregorian;
|
||||
pub mod hebrew;
|
||||
pub mod indian;
|
||||
pub mod islamic;
|
||||
pub mod iso;
|
||||
pub mod japanese;
|
||||
pub mod julian;
|
||||
pub mod persian;
|
||||
pub mod provider;
|
||||
pub mod roc;
|
||||
pub mod types;
|
||||
mod week_of;
|
||||
|
||||
pub mod week {
|
||||
//! Functions for week-of-month and week-of-year arithmetic.
|
||||
use crate::week_of;
|
||||
pub use week_of::RelativeUnit;
|
||||
pub use week_of::WeekCalculator;
|
||||
pub use week_of::WeekOf;
|
||||
}
|
||||
|
||||
#[doc(no_inline)]
|
||||
pub use any_calendar::{AnyCalendar, AnyCalendarKind};
|
||||
pub use calendar::Calendar;
|
||||
pub use date::{AsCalendar, Date, Ref};
|
||||
pub use datetime::DateTime;
|
||||
#[doc(hidden)]
|
||||
pub use duration::{DateDuration, DateDurationUnit};
|
||||
pub use error::CalendarError;
|
||||
#[doc(no_inline)]
|
||||
pub use gregorian::Gregorian;
|
||||
#[doc(no_inline)]
|
||||
pub use iso::Iso;
|
||||
|
||||
#[doc(no_inline)]
|
||||
pub use CalendarError as Error;
|
|
@ -0,0 +1,598 @@
|
|||
// This file is part of ICU4X. For terms of use, please see the file
|
||||
// called LICENSE at the top level of the ICU4X source tree
|
||||
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
|
||||
|
||||
//! This module contains types and implementations for the Persian calendar.
|
||||
//!
|
||||
//! ```rust
|
||||
//! use icu::calendar::{Date, DateTime};
|
||||
//!
|
||||
//! // `Date` type
|
||||
//! let persian_date = Date::try_new_persian_date(1348, 10, 11)
|
||||
//! .expect("Failed to initialize Persian Date instance.");
|
||||
//!
|
||||
//! // `DateTime` type
|
||||
//! let persian_datetime =
|
||||
//! DateTime::try_new_persian_datetime(1348, 10, 11, 13, 1, 0)
|
||||
//! .expect("Failed to initialize Persian DateTime instance.");
|
||||
//!
|
||||
//! // `Date` checks
|
||||
//! assert_eq!(persian_date.year().number, 1348);
|
||||
//! assert_eq!(persian_date.month().ordinal, 10);
|
||||
//! assert_eq!(persian_date.day_of_month().0, 11);
|
||||
//!
|
||||
//! // `DateTime` checks
|
||||
//! assert_eq!(persian_datetime.date.year().number, 1348);
|
||||
//! assert_eq!(persian_datetime.date.month().ordinal, 10);
|
||||
//! assert_eq!(persian_datetime.date.day_of_month().0, 11);
|
||||
//! assert_eq!(persian_datetime.time.hour.number(), 13);
|
||||
//! assert_eq!(persian_datetime.time.minute.number(), 1);
|
||||
//! assert_eq!(persian_datetime.time.second.number(), 0);
|
||||
//! ```
|
||||
|
||||
use crate::any_calendar::AnyCalendarKind;
|
||||
use crate::calendar_arithmetic::{ArithmeticDate, CalendarArithmetic};
|
||||
use crate::iso::Iso;
|
||||
use crate::{types, Calendar, CalendarError, Date, DateDuration, DateDurationUnit, DateTime};
|
||||
use ::tinystr::tinystr;
|
||||
use calendrical_calculations::helpers::I32CastError;
|
||||
use calendrical_calculations::rata_die::RataDie;
|
||||
|
||||
/// The Persian Calendar
|
||||
///
|
||||
/// The [Persian Calendar] is a solar calendar used officially by the countries of Iran and Afghanistan and many Persian-speaking regions.
|
||||
/// It has 12 months and other similarities to the Gregorian Calendar
|
||||
///
|
||||
/// This type can be used with [`Date`] or [`DateTime`] to represent dates in this calendar.
|
||||
///
|
||||
/// [Persian Calendar]: https://en.wikipedia.org/wiki/Solar_Hijri_calendar
|
||||
///
|
||||
/// # Era codes
|
||||
///
|
||||
/// This calendar supports only one era code, which starts from the year of the Hijra, designated as "ah".
|
||||
///
|
||||
/// # Month codes
|
||||
///
|
||||
/// This calendar supports 12 solar month codes (`"M01" - "M12"`)
|
||||
#[derive(Copy, Clone, Debug, Default, Hash, Eq, PartialEq, PartialOrd, Ord)]
|
||||
#[allow(clippy::exhaustive_structs)]
|
||||
pub struct Persian;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
|
||||
|
||||
/// The inner date type used for representing [`Date`]s of [`Persian`]. See [`Date`] and [`Persian`] for more details.
|
||||
pub struct PersianDateInner(ArithmeticDate<Persian>);
|
||||
|
||||
impl CalendarArithmetic for Persian {
|
||||
fn month_days(year: i32, month: u8) -> u8 {
|
||||
match month {
|
||||
1..=6 => 31,
|
||||
7..=11 => 30,
|
||||
12 if Self::is_leap_year(year) => 30,
|
||||
12 => 29,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn months_for_every_year(_: i32) -> u8 {
|
||||
12
|
||||
}
|
||||
// Lisp code reference: https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L4789
|
||||
fn is_leap_year(p_year: i32) -> bool {
|
||||
let mut p_year = p_year as i64;
|
||||
if 0 < p_year {
|
||||
p_year -= 474;
|
||||
} else {
|
||||
p_year -= 473;
|
||||
};
|
||||
let year = p_year.rem_euclid(2820) + 474;
|
||||
|
||||
((year + 38) * 31).rem_euclid(128) < 31
|
||||
}
|
||||
|
||||
fn days_in_provided_year(year: i32) -> u16 {
|
||||
if Self::is_leap_year(year) {
|
||||
366
|
||||
} else {
|
||||
365
|
||||
}
|
||||
}
|
||||
|
||||
fn last_month_day_in_year(year: i32) -> (u8, u8) {
|
||||
if Self::is_leap_year(year) {
|
||||
(12, 30)
|
||||
} else {
|
||||
(12, 29)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Calendar for Persian {
|
||||
type DateInner = PersianDateInner;
|
||||
fn date_from_codes(
|
||||
&self,
|
||||
era: types::Era,
|
||||
year: i32,
|
||||
month_code: types::MonthCode,
|
||||
day: u8,
|
||||
) -> Result<Self::DateInner, CalendarError> {
|
||||
let year = if era.0 == tinystr!(16, "ah") || era.0 == tinystr!(16, "persian") {
|
||||
year
|
||||
} else {
|
||||
return Err(CalendarError::UnknownEra(era.0, self.debug_name()));
|
||||
};
|
||||
|
||||
ArithmeticDate::new_from_codes(self, year, month_code, day).map(PersianDateInner)
|
||||
}
|
||||
|
||||
fn date_from_iso(&self, iso: Date<Iso>) -> PersianDateInner {
|
||||
let fixed_iso = Iso::fixed_from_iso(*iso.inner());
|
||||
Self::arithmetic_persian_from_fixed(fixed_iso)
|
||||
}
|
||||
|
||||
fn date_to_iso(&self, date: &Self::DateInner) -> Date<Iso> {
|
||||
let fixed_persian = Persian::fixed_from_arithmetic_persian(*date);
|
||||
Iso::iso_from_fixed(fixed_persian)
|
||||
}
|
||||
|
||||
fn months_in_year(&self, date: &Self::DateInner) -> u8 {
|
||||
date.0.months_in_year()
|
||||
}
|
||||
|
||||
fn days_in_year(&self, date: &Self::DateInner) -> u16 {
|
||||
date.0.days_in_year()
|
||||
}
|
||||
|
||||
fn days_in_month(&self, date: &Self::DateInner) -> u8 {
|
||||
date.0.days_in_month()
|
||||
}
|
||||
|
||||
fn day_of_week(&self, date: &Self::DateInner) -> types::IsoWeekday {
|
||||
Iso.day_of_week(self.date_to_iso(date).inner())
|
||||
}
|
||||
|
||||
fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration<Self>) {
|
||||
date.0.offset_date(offset)
|
||||
}
|
||||
|
||||
#[allow(clippy::field_reassign_with_default)]
|
||||
fn until(
|
||||
&self,
|
||||
date1: &Self::DateInner,
|
||||
date2: &Self::DateInner,
|
||||
_calendar2: &Self,
|
||||
_largest_unit: DateDurationUnit,
|
||||
_smallest_unit: DateDurationUnit,
|
||||
) -> DateDuration<Self> {
|
||||
date1.0.until(date2.0, _largest_unit, _smallest_unit)
|
||||
}
|
||||
|
||||
fn year(&self, date: &Self::DateInner) -> types::FormattableYear {
|
||||
Self::year_as_persian(date.0.year)
|
||||
}
|
||||
|
||||
fn is_in_leap_year(&self, date: &Self::DateInner) -> bool {
|
||||
Self::is_leap_year(date.0.year)
|
||||
}
|
||||
|
||||
fn month(&self, date: &Self::DateInner) -> types::FormattableMonth {
|
||||
date.0.month()
|
||||
}
|
||||
|
||||
fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth {
|
||||
date.0.day_of_month()
|
||||
}
|
||||
|
||||
fn day_of_year_info(&self, date: &Self::DateInner) -> types::DayOfYearInfo {
|
||||
let prev_year = date.0.year.saturating_sub(1);
|
||||
let next_year = date.0.year.saturating_add(1);
|
||||
types::DayOfYearInfo {
|
||||
day_of_year: date.0.day_of_year(),
|
||||
days_in_year: date.0.days_in_year(),
|
||||
prev_year: Persian::year_as_persian(prev_year),
|
||||
days_in_prev_year: Persian::days_in_provided_year(prev_year),
|
||||
next_year: Persian::year_as_persian(next_year),
|
||||
}
|
||||
}
|
||||
|
||||
fn debug_name(&self) -> &'static str {
|
||||
"Persian"
|
||||
}
|
||||
// Missing any_calendar persian tests, the rest is completed
|
||||
fn any_calendar_kind(&self) -> Option<AnyCalendarKind> {
|
||||
Some(AnyCalendarKind::Persian)
|
||||
}
|
||||
}
|
||||
|
||||
impl Persian {
|
||||
/// Constructs a new Persian Calendar
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn fixed_from_arithmetic_persian(p_date: PersianDateInner) -> RataDie {
|
||||
calendrical_calculations::persian::fixed_from_arithmetic_persian(
|
||||
p_date.0.year,
|
||||
p_date.0.month,
|
||||
p_date.0.day,
|
||||
)
|
||||
}
|
||||
fn arithmetic_persian_from_fixed(date: RataDie) -> PersianDateInner {
|
||||
let (year, month, day) =
|
||||
match calendrical_calculations::persian::arithmetic_persian_from_fixed(date) {
|
||||
Err(I32CastError::BelowMin) => return PersianDateInner(ArithmeticDate::min_date()),
|
||||
Err(I32CastError::AboveMax) => return PersianDateInner(ArithmeticDate::max_date()),
|
||||
Ok(ymd) => ymd,
|
||||
};
|
||||
|
||||
PersianDateInner(ArithmeticDate::new_unchecked(year, month, day))
|
||||
}
|
||||
|
||||
fn year_as_persian(year: i32) -> types::FormattableYear {
|
||||
types::FormattableYear {
|
||||
era: types::Era(tinystr!(16, "ah")),
|
||||
number: year,
|
||||
cyclic: None,
|
||||
related_iso: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Date<Persian> {
|
||||
/// Construct new Persian Date.
|
||||
///
|
||||
/// Has no negative years, only era is the AH/AP.
|
||||
///
|
||||
/// ```rust
|
||||
/// use icu::calendar::Date;
|
||||
///
|
||||
/// let date_persian = Date::try_new_persian_date(1392, 4, 25)
|
||||
/// .expect("Failed to initialize Persian Date instance.");
|
||||
///
|
||||
/// assert_eq!(date_persian.year().number, 1392);
|
||||
/// assert_eq!(date_persian.month().ordinal, 4);
|
||||
/// assert_eq!(date_persian.day_of_month().0, 25);
|
||||
/// ```
|
||||
pub fn try_new_persian_date(
|
||||
year: i32,
|
||||
month: u8,
|
||||
day: u8,
|
||||
) -> Result<Date<Persian>, CalendarError> {
|
||||
ArithmeticDate::new_from_ordinals(year, month, day)
|
||||
.map(PersianDateInner)
|
||||
.map(|inner| Date::from_raw(inner, Persian))
|
||||
}
|
||||
}
|
||||
|
||||
impl DateTime<Persian> {
|
||||
/// Construct a new Persian datetime from integers.
|
||||
///
|
||||
/// ```rust
|
||||
/// use icu::calendar::DateTime;
|
||||
///
|
||||
/// let datetime_persian =
|
||||
/// DateTime::try_new_persian_datetime(474, 10, 11, 13, 1, 0)
|
||||
/// .expect("Failed to initialize Persian DateTime instance.");
|
||||
///
|
||||
/// assert_eq!(datetime_persian.date.year().number, 474);
|
||||
/// assert_eq!(datetime_persian.date.month().ordinal, 10);
|
||||
/// assert_eq!(datetime_persian.date.day_of_month().0, 11);
|
||||
/// assert_eq!(datetime_persian.time.hour.number(), 13);
|
||||
/// assert_eq!(datetime_persian.time.minute.number(), 1);
|
||||
/// assert_eq!(datetime_persian.time.second.number(), 0);
|
||||
/// ```
|
||||
pub fn try_new_persian_datetime(
|
||||
year: i32,
|
||||
month: u8,
|
||||
day: u8,
|
||||
hour: u8,
|
||||
minute: u8,
|
||||
second: u8,
|
||||
) -> Result<DateTime<Persian>, CalendarError> {
|
||||
Ok(DateTime {
|
||||
date: Date::try_new_persian_date(year, month, day)?,
|
||||
time: types::Time::try_new(hour, minute, second, 0)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[derive(Debug)]
|
||||
struct DateCase {
|
||||
year: i32,
|
||||
month: u8,
|
||||
day: u8,
|
||||
}
|
||||
|
||||
static TEST_FIXED_DATE: [i64; 33] = [
|
||||
-214193, -61387, 25469, 49217, 171307, 210155, 253427, 369740, 400085, 434355, 452605,
|
||||
470160, 473837, 507850, 524156, 544676, 567118, 569477, 601716, 613424, 626596, 645554,
|
||||
664224, 671401, 694799, 704424, 708842, 709409, 709580, 727274, 728714, 744313, 764652,
|
||||
];
|
||||
|
||||
static CASES: [DateCase; 33] = [
|
||||
DateCase {
|
||||
year: -1208,
|
||||
month: 5,
|
||||
day: 1,
|
||||
},
|
||||
DateCase {
|
||||
year: -790,
|
||||
month: 9,
|
||||
day: 14,
|
||||
},
|
||||
DateCase {
|
||||
year: -552,
|
||||
month: 7,
|
||||
day: 2,
|
||||
},
|
||||
DateCase {
|
||||
year: -487,
|
||||
month: 7,
|
||||
day: 9,
|
||||
},
|
||||
DateCase {
|
||||
year: -153,
|
||||
month: 10,
|
||||
day: 18,
|
||||
},
|
||||
DateCase {
|
||||
year: -46,
|
||||
month: 2,
|
||||
day: 30,
|
||||
},
|
||||
DateCase {
|
||||
year: 73,
|
||||
month: 8,
|
||||
day: 19,
|
||||
},
|
||||
DateCase {
|
||||
year: 392,
|
||||
month: 2,
|
||||
day: 5,
|
||||
},
|
||||
DateCase {
|
||||
year: 475,
|
||||
month: 3,
|
||||
day: 3,
|
||||
},
|
||||
DateCase {
|
||||
year: 569,
|
||||
month: 1,
|
||||
day: 3,
|
||||
},
|
||||
DateCase {
|
||||
year: 618,
|
||||
month: 12,
|
||||
day: 20,
|
||||
},
|
||||
DateCase {
|
||||
year: 667,
|
||||
month: 1,
|
||||
day: 14,
|
||||
},
|
||||
DateCase {
|
||||
year: 677,
|
||||
month: 2,
|
||||
day: 8,
|
||||
},
|
||||
DateCase {
|
||||
year: 770,
|
||||
month: 3,
|
||||
day: 22,
|
||||
},
|
||||
DateCase {
|
||||
year: 814,
|
||||
month: 11,
|
||||
day: 13,
|
||||
},
|
||||
DateCase {
|
||||
year: 871,
|
||||
month: 1,
|
||||
day: 21,
|
||||
},
|
||||
DateCase {
|
||||
year: 932,
|
||||
month: 6,
|
||||
day: 28,
|
||||
},
|
||||
DateCase {
|
||||
year: 938,
|
||||
month: 12,
|
||||
day: 14,
|
||||
},
|
||||
DateCase {
|
||||
year: 1027,
|
||||
month: 3,
|
||||
day: 21,
|
||||
},
|
||||
DateCase {
|
||||
year: 1059,
|
||||
month: 4,
|
||||
day: 10,
|
||||
},
|
||||
DateCase {
|
||||
year: 1095,
|
||||
month: 5,
|
||||
day: 2,
|
||||
},
|
||||
DateCase {
|
||||
year: 1147,
|
||||
month: 3,
|
||||
day: 30,
|
||||
},
|
||||
DateCase {
|
||||
year: 1198,
|
||||
month: 5,
|
||||
day: 10,
|
||||
},
|
||||
DateCase {
|
||||
year: 1218,
|
||||
month: 1,
|
||||
day: 7,
|
||||
},
|
||||
DateCase {
|
||||
year: 1282,
|
||||
month: 1,
|
||||
day: 29,
|
||||
},
|
||||
DateCase {
|
||||
year: 1308,
|
||||
month: 6,
|
||||
day: 3,
|
||||
},
|
||||
DateCase {
|
||||
year: 1320,
|
||||
month: 7,
|
||||
day: 7,
|
||||
},
|
||||
DateCase {
|
||||
year: 1322,
|
||||
month: 1,
|
||||
day: 29,
|
||||
},
|
||||
DateCase {
|
||||
year: 1322,
|
||||
month: 7,
|
||||
day: 14,
|
||||
},
|
||||
DateCase {
|
||||
year: 1370,
|
||||
month: 12,
|
||||
day: 27,
|
||||
},
|
||||
DateCase {
|
||||
year: 1374,
|
||||
month: 12,
|
||||
day: 6,
|
||||
},
|
||||
DateCase {
|
||||
year: 1417,
|
||||
month: 8,
|
||||
day: 19,
|
||||
},
|
||||
DateCase {
|
||||
year: 1473,
|
||||
month: 4,
|
||||
day: 28,
|
||||
},
|
||||
];
|
||||
|
||||
fn days_in_provided_year_core(year: i32) -> u16 {
|
||||
let fixed_year =
|
||||
calendrical_calculations::persian::fixed_from_arithmetic_persian(year, 1, 1)
|
||||
.to_i64_date();
|
||||
let next_fixed_year =
|
||||
calendrical_calculations::persian::fixed_from_arithmetic_persian(year + 1, 1, 1)
|
||||
.to_i64_date();
|
||||
|
||||
(next_fixed_year - fixed_year) as u16
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_persian_leap_year() {
|
||||
let mut leap_years: [i32; 33] = [0; 33];
|
||||
// These values were computed from the "Calendrical Calculations" reference code output
|
||||
let expected_values = [
|
||||
false, true, false, false, false, false, false, true, false, true, false, false, true,
|
||||
false, false, true, false, false, false, false, false, false, false, true, false,
|
||||
false, false, false, false, true, false, false, false,
|
||||
];
|
||||
|
||||
let mut leap_year_results: Vec<bool> = Vec::new();
|
||||
let canonical_leap_year_cycle_start = 474;
|
||||
let canonical_leap_year_cycle_end = 3293;
|
||||
for year in canonical_leap_year_cycle_start..=canonical_leap_year_cycle_end {
|
||||
let r = Persian::is_leap_year(year);
|
||||
if r {
|
||||
leap_year_results.push(r);
|
||||
}
|
||||
}
|
||||
// 683 is the amount of leap years in the 2820 Persian year cycle
|
||||
assert_eq!(leap_year_results.len(), 683);
|
||||
|
||||
for (index, case) in CASES.iter().enumerate() {
|
||||
leap_years[index] = case.year;
|
||||
}
|
||||
for (year, bool) in leap_years.iter().zip(expected_values.iter()) {
|
||||
assert_eq!(Persian::is_leap_year(*year), *bool);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn days_in_provided_year_test() {
|
||||
for case in CASES.iter() {
|
||||
assert_eq!(
|
||||
days_in_provided_year_core(case.year),
|
||||
Persian::days_in_provided_year(case.year)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fixed_from_persian() {
|
||||
for (case, f_date) in CASES.iter().zip(TEST_FIXED_DATE.iter()) {
|
||||
let date = Date::try_new_persian_date(case.year, case.month, case.day).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
Persian::fixed_from_arithmetic_persian(*date.inner()).to_i64_date(),
|
||||
*f_date,
|
||||
"{case:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn test_persian_from_fixed() {
|
||||
for (case, f_date) in CASES.iter().zip(TEST_FIXED_DATE.iter()) {
|
||||
let date = Date::try_new_persian_date(case.year, case.month, case.day).unwrap();
|
||||
assert_eq!(
|
||||
Persian::arithmetic_persian_from_fixed(RataDie::new(*f_date)),
|
||||
date.inner,
|
||||
"{case:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_day_of_year_info() {
|
||||
#[derive(Debug)]
|
||||
struct TestCase {
|
||||
input: i32,
|
||||
expected_prev: i32,
|
||||
expected_next: i32,
|
||||
}
|
||||
|
||||
let test_cases = [
|
||||
TestCase {
|
||||
input: 0,
|
||||
expected_prev: -1,
|
||||
expected_next: 1,
|
||||
},
|
||||
TestCase {
|
||||
input: i32::MAX,
|
||||
expected_prev: i32::MAX - 1,
|
||||
expected_next: i32::MAX, // can't go above i32::MAX
|
||||
},
|
||||
TestCase {
|
||||
input: i32::MIN + 1,
|
||||
expected_prev: i32::MIN,
|
||||
expected_next: i32::MIN + 2,
|
||||
},
|
||||
TestCase {
|
||||
input: i32::MIN,
|
||||
expected_prev: i32::MIN, // can't go below i32::MIN
|
||||
expected_next: i32::MIN + 1,
|
||||
},
|
||||
];
|
||||
|
||||
for case in test_cases {
|
||||
let date = Date::try_new_persian_date(case.input, 1, 1).unwrap();
|
||||
let info = Persian::day_of_year_info(&Persian, date.inner());
|
||||
|
||||
assert_eq!(info.prev_year.number, case.expected_prev, "{:?}", case);
|
||||
assert_eq!(info.next_year.number, case.expected_next, "{:?}", case);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
// This file is part of ICU4X. For terms of use, please see the file
|
||||
// called LICENSE at the top level of the ICU4X source tree
|
||||
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
|
||||
|
||||
//! 🚧 \[Unstable\] Data provider struct definitions for this ICU4X component.
|
||||
//!
|
||||
//! <div class="stab unstable">
|
||||
//! 🚧 This code is considered unstable; it may change at any time, in breaking or non-breaking ways,
|
||||
//! including in SemVer minor releases. While the serde representation of data structs is guaranteed
|
||||
//! to be stable, their Rust representation might not be. Use with caution.
|
||||
//! </div>
|
||||
//!
|
||||
//! Read more about data providers: [`icu_provider`]
|
||||
|
||||
// Provider structs must be stable
|
||||
#![allow(clippy::exhaustive_structs, clippy::exhaustive_enums)]
|
||||
|
||||
use crate::types::IsoWeekday;
|
||||
use core::str::FromStr;
|
||||
use icu_provider::prelude::*;
|
||||
use tinystr::TinyStr16;
|
||||
use zerovec::ZeroVec;
|
||||
|
||||
#[cfg(feature = "compiled_data")]
|
||||
#[derive(Debug)]
|
||||
/// Baked data
|
||||
///
|
||||
/// <div class="stab unstable">
|
||||
/// 🚧 This code is considered unstable; it may change at any time, in breaking or non-breaking ways,
|
||||
/// including in SemVer minor releases. In particular, the `DataProvider` implementations are only
|
||||
/// guaranteed to match with this version's `*_unstable` providers. Use with caution.
|
||||
/// </div>
|
||||
pub struct Baked;
|
||||
|
||||
#[cfg(feature = "compiled_data")]
|
||||
const _: () = {
|
||||
pub mod icu {
|
||||
pub use crate as calendar;
|
||||
pub use icu_locid_transform as locid_transform;
|
||||
}
|
||||
icu_calendar_data::make_provider!(Baked);
|
||||
icu_calendar_data::impl_calendar_japanese_v1!(Baked);
|
||||
icu_calendar_data::impl_calendar_japanext_v1!(Baked);
|
||||
icu_calendar_data::impl_datetime_week_data_v1!(Baked);
|
||||
};
|
||||
|
||||
#[cfg(feature = "datagen")]
|
||||
/// The latest minimum set of keys required by this component.
|
||||
pub const KEYS: &[DataKey] = &[
|
||||
JapaneseErasV1Marker::KEY,
|
||||
JapaneseExtendedErasV1Marker::KEY,
|
||||
WeekDataV1Marker::KEY,
|
||||
];
|
||||
|
||||
/// The date at which an era started
|
||||
///
|
||||
/// The order of fields in this struct is important!
|
||||
///
|
||||
/// <div class="stab unstable">
|
||||
/// 🚧 This code is considered unstable; it may change at any time, in breaking or non-breaking ways,
|
||||
/// including in SemVer minor releases. While the serde representation of data structs is guaranteed
|
||||
/// to be stable, their Rust representation might not be. Use with caution.
|
||||
/// </div>
|
||||
#[zerovec::make_ule(EraStartDateULE)]
|
||||
#[derive(
|
||||
Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Debug, yoke::Yokeable, zerofrom::ZeroFrom,
|
||||
)]
|
||||
#[cfg_attr(
|
||||
feature = "datagen",
|
||||
derive(serde::Serialize, databake::Bake),
|
||||
databake(path = icu_calendar::provider),
|
||||
)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
|
||||
pub struct EraStartDate {
|
||||
/// The year the era started in
|
||||
pub year: i32,
|
||||
/// The month the era started in
|
||||
pub month: u8,
|
||||
/// The day the era started in
|
||||
pub day: u8,
|
||||
}
|
||||
|
||||
/// A data structure containing the necessary era data for constructing a
|
||||
/// [`Japanese`](crate::japanese::Japanese) calendar object
|
||||
///
|
||||
/// <div class="stab unstable">
|
||||
/// 🚧 This code is considered unstable; it may change at any time, in breaking or non-breaking ways,
|
||||
/// including in SemVer minor releases. While the serde representation of data structs is guaranteed
|
||||
/// to be stable, their Rust representation might not be. Use with caution.
|
||||
/// </div>
|
||||
#[icu_provider::data_struct(
|
||||
marker(JapaneseErasV1Marker, "calendar/japanese@1", singleton),
|
||||
marker(JapaneseExtendedErasV1Marker, "calendar/japanext@1", singleton)
|
||||
)]
|
||||
#[derive(Debug, PartialEq, Clone, Default)]
|
||||
#[cfg_attr(
|
||||
feature = "datagen",
|
||||
derive(serde::Serialize, databake::Bake),
|
||||
databake(path = icu_calendar::provider),
|
||||
)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
|
||||
pub struct JapaneseErasV1<'data> {
|
||||
/// A map from era start dates to their era codes
|
||||
#[cfg_attr(feature = "serde", serde(borrow))]
|
||||
pub dates_to_eras: ZeroVec<'data, (EraStartDate, TinyStr16)>,
|
||||
}
|
||||
|
||||
impl FromStr for EraStartDate {
|
||||
type Err = ();
|
||||
fn from_str(mut s: &str) -> Result<Self, ()> {
|
||||
let sign = if let Some(suffix) = s.strip_prefix('-') {
|
||||
s = suffix;
|
||||
-1
|
||||
} else {
|
||||
1
|
||||
};
|
||||
|
||||
let mut split = s.split('-');
|
||||
let year = split.next().ok_or(())?.parse::<i32>().map_err(|_| ())? * sign;
|
||||
let month = split.next().ok_or(())?.parse().map_err(|_| ())?;
|
||||
let day = split.next().ok_or(())?.parse().map_err(|_| ())?;
|
||||
|
||||
Ok(EraStartDate { year, month, day })
|
||||
}
|
||||
}
|
||||
|
||||
/// An ICU4X mapping to a subset of CLDR weekData.
|
||||
/// See CLDR-JSON's weekData.json for more context.
|
||||
///
|
||||
/// <div class="stab unstable">
|
||||
/// 🚧 This code is considered unstable; it may change at any time, in breaking or non-breaking ways,
|
||||
/// including in SemVer minor releases. While the serde representation of data structs is guaranteed
|
||||
/// to be stable, their Rust representation might not be. Use with caution.
|
||||
/// </div>
|
||||
#[icu_provider::data_struct(marker(
|
||||
WeekDataV1Marker,
|
||||
"datetime/week_data@1",
|
||||
fallback_by = "region"
|
||||
))]
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[cfg_attr(
|
||||
feature = "datagen",
|
||||
derive(serde::Serialize, databake::Bake),
|
||||
databake(path = icu_calendar::provider),
|
||||
)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
|
||||
#[allow(clippy::exhaustive_structs)] // used in data provider
|
||||
pub struct WeekDataV1 {
|
||||
/// The first day of a week.
|
||||
pub first_weekday: IsoWeekday,
|
||||
/// For a given week, the minimum number of that week's days present in a given month or year for the week to be considered part of that month or year.
|
||||
pub min_week_days: u8,
|
||||
}
|
|
@ -0,0 +1,498 @@
|
|||
// This file is part of ICU4X. For terms of use, please see the file
|
||||
// called LICENSE at the top level of the ICU4X source tree
|
||||
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
|
||||
|
||||
//! This module contains types and implementations for the Republic of China calendar.
|
||||
//!
|
||||
//! ```rust
|
||||
//! use icu::calendar::{roc::Roc, Date, DateTime};
|
||||
//!
|
||||
//! // `Date` type
|
||||
//! let date_iso = Date::try_new_iso_date(1970, 1, 2)
|
||||
//! .expect("Failed to initialize ISO Date instance.");
|
||||
//! let date_roc = Date::new_from_iso(date_iso, Roc);
|
||||
//!
|
||||
//! // `DateTime` type
|
||||
//! let datetime_iso = DateTime::try_new_iso_datetime(1970, 1, 2, 13, 1, 0)
|
||||
//! .expect("Failed to initialize ISO DateTime instance.");
|
||||
//! let datetime_roc = DateTime::new_from_iso(datetime_iso, Roc);
|
||||
//!
|
||||
//! // `Date` checks
|
||||
//! assert_eq!(date_roc.year().number, 59);
|
||||
//! assert_eq!(date_roc.month().ordinal, 1);
|
||||
//! assert_eq!(date_roc.day_of_month().0, 2);
|
||||
//!
|
||||
//! // `DateTime` checks
|
||||
//! assert_eq!(datetime_roc.date.year().number, 59);
|
||||
//! assert_eq!(datetime_roc.date.month().ordinal, 1);
|
||||
//! assert_eq!(datetime_roc.date.day_of_month().0, 2);
|
||||
//! assert_eq!(datetime_roc.time.hour.number(), 13);
|
||||
//! assert_eq!(datetime_roc.time.minute.number(), 1);
|
||||
//! assert_eq!(datetime_roc.time.second.number(), 0);
|
||||
//! ```
|
||||
|
||||
use crate::{
|
||||
calendar_arithmetic::ArithmeticDate, iso::IsoDateInner, types, AnyCalendarKind, Calendar,
|
||||
CalendarError, Date, DateTime, Iso,
|
||||
};
|
||||
use calendrical_calculations::helpers::i64_to_saturated_i32;
|
||||
use tinystr::tinystr;
|
||||
|
||||
/// Year of the beginning of the Taiwanese (ROC/Minguo) calendar.
|
||||
/// 1912 ISO = ROC 1
|
||||
const ROC_ERA_OFFSET: i32 = 1911;
|
||||
|
||||
/// The Republic of China (ROC) Calendar
|
||||
///
|
||||
/// The [Republic of China calendar] is a solar calendar used in Taiwan and Penghu, as well as by overseas diaspora from
|
||||
/// those locations. Months and days are identical to the [`Gregorian`] calendar, while years are counted
|
||||
/// with 1912, the year of the establishment of the Republic of China, as year 1 of the ROC/Minguo/民国/民國 era.
|
||||
///
|
||||
/// [Republic of China calendar]: https://en.wikipedia.org/wiki/Republic_of_China_calendar
|
||||
///
|
||||
/// The Republic of China calendar should not be confused with the Chinese traditional lunar calendar
|
||||
/// (see [`Chinese`]).
|
||||
///
|
||||
/// # Era codes
|
||||
///
|
||||
/// This calendar supports two era codes: `"roc"`, corresponding to years in the 民國 (minguo) era (CE year 1912 and
|
||||
/// after), and `"roc-inverse"`, corresponding to years before the 民國 (minguo) era (CE year 1911 and before).
|
||||
///
|
||||
///
|
||||
/// # Month codes
|
||||
///
|
||||
/// This calendar supports 12 solar month codes (`"M01" - "M12"`)
|
||||
///
|
||||
/// [`Chinese`]: crate::chinese::Chinese
|
||||
/// [`Gregorian`]: crate::Gregorian
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
#[allow(clippy::exhaustive_structs)] // this type is stable
|
||||
pub struct Roc;
|
||||
|
||||
/// The inner date type used for representing [`Date`]s of [`Roc`]. See [`Date`] and [`Roc`] for more info.
|
||||
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub struct RocDateInner(IsoDateInner);
|
||||
|
||||
impl Calendar for Roc {
|
||||
type DateInner = RocDateInner;
|
||||
|
||||
fn date_from_codes(
|
||||
&self,
|
||||
era: crate::types::Era,
|
||||
year: i32,
|
||||
month_code: crate::types::MonthCode,
|
||||
day: u8,
|
||||
) -> Result<Self::DateInner, crate::Error> {
|
||||
let year = if era.0 == tinystr!(16, "roc") {
|
||||
if year <= 0 {
|
||||
return Err(CalendarError::OutOfRange);
|
||||
}
|
||||
year + ROC_ERA_OFFSET
|
||||
} else if era.0 == tinystr!(16, "roc-inverse") {
|
||||
if year <= 0 {
|
||||
return Err(CalendarError::OutOfRange);
|
||||
}
|
||||
1 - year + ROC_ERA_OFFSET
|
||||
} else {
|
||||
return Err(CalendarError::UnknownEra(era.0, self.debug_name()));
|
||||
};
|
||||
|
||||
ArithmeticDate::new_from_codes(self, year, month_code, day)
|
||||
.map(IsoDateInner)
|
||||
.map(RocDateInner)
|
||||
}
|
||||
|
||||
fn date_from_iso(&self, iso: crate::Date<crate::Iso>) -> Self::DateInner {
|
||||
RocDateInner(*iso.inner())
|
||||
}
|
||||
|
||||
fn date_to_iso(&self, date: &Self::DateInner) -> crate::Date<crate::Iso> {
|
||||
Date::from_raw(date.0, Iso)
|
||||
}
|
||||
|
||||
fn months_in_year(&self, date: &Self::DateInner) -> u8 {
|
||||
Iso.months_in_year(&date.0)
|
||||
}
|
||||
|
||||
fn days_in_year(&self, date: &Self::DateInner) -> u16 {
|
||||
Iso.days_in_year(&date.0)
|
||||
}
|
||||
|
||||
fn days_in_month(&self, date: &Self::DateInner) -> u8 {
|
||||
Iso.days_in_month(&date.0)
|
||||
}
|
||||
|
||||
fn offset_date(&self, date: &mut Self::DateInner, offset: crate::DateDuration<Self>) {
|
||||
Iso.offset_date(&mut date.0, offset.cast_unit())
|
||||
}
|
||||
|
||||
fn until(
|
||||
&self,
|
||||
date1: &Self::DateInner,
|
||||
date2: &Self::DateInner,
|
||||
_calendar2: &Self,
|
||||
largest_unit: crate::DateDurationUnit,
|
||||
smallest_unit: crate::DateDurationUnit,
|
||||
) -> crate::DateDuration<Self> {
|
||||
Iso.until(&date1.0, &date2.0, &Iso, largest_unit, smallest_unit)
|
||||
.cast_unit()
|
||||
}
|
||||
|
||||
fn debug_name(&self) -> &'static str {
|
||||
"ROC"
|
||||
}
|
||||
|
||||
fn year(&self, date: &Self::DateInner) -> crate::types::FormattableYear {
|
||||
year_as_roc(date.0 .0.year as i64)
|
||||
}
|
||||
|
||||
fn is_in_leap_year(&self, date: &Self::DateInner) -> bool {
|
||||
Iso.is_in_leap_year(&date.0)
|
||||
}
|
||||
|
||||
fn month(&self, date: &Self::DateInner) -> crate::types::FormattableMonth {
|
||||
Iso.month(&date.0)
|
||||
}
|
||||
|
||||
fn day_of_month(&self, date: &Self::DateInner) -> crate::types::DayOfMonth {
|
||||
Iso.day_of_month(&date.0)
|
||||
}
|
||||
|
||||
fn day_of_year_info(&self, date: &Self::DateInner) -> crate::types::DayOfYearInfo {
|
||||
let prev_year = date.0 .0.year.saturating_sub(1);
|
||||
let next_year = date.0 .0.year.saturating_add(1);
|
||||
types::DayOfYearInfo {
|
||||
day_of_year: Iso::day_of_year(date.0),
|
||||
days_in_year: Iso::days_in_year_direct(date.0 .0.year),
|
||||
prev_year: year_as_roc(prev_year as i64),
|
||||
days_in_prev_year: Iso::days_in_year_direct(prev_year),
|
||||
next_year: year_as_roc(next_year as i64),
|
||||
}
|
||||
}
|
||||
|
||||
/// The [`AnyCalendarKind`] corresponding to this calendar
|
||||
fn any_calendar_kind(&self) -> Option<AnyCalendarKind> {
|
||||
Some(AnyCalendarKind::Roc)
|
||||
}
|
||||
}
|
||||
|
||||
impl Date<Roc> {
|
||||
/// Construct a new Republic of China calendar Date.
|
||||
///
|
||||
/// Years are specified in the "roc" era. This function accepts an extended year in that era, so dates
|
||||
/// before Minguo are negative and year 0 is 1 Before Minguo. To specify dates using explicit era
|
||||
/// codes, use [`Roc::date_from_codes()`].
|
||||
///
|
||||
/// ```rust
|
||||
/// use icu::calendar::Date;
|
||||
/// use icu::calendar::gregorian::Gregorian;
|
||||
/// use tinystr::tinystr;
|
||||
///
|
||||
/// // Create a new ROC Date
|
||||
/// let date_roc = Date::try_new_roc_date(1, 2, 3)
|
||||
/// .expect("Failed to initialize ROC Date instance.");
|
||||
///
|
||||
/// assert_eq!(date_roc.year().era.0, tinystr!(16, "roc"));
|
||||
/// assert_eq!(date_roc.year().number, 1, "ROC year check failed!");
|
||||
/// assert_eq!(date_roc.month().ordinal, 2, "ROC month check failed!");
|
||||
/// assert_eq!(date_roc.day_of_month().0, 3, "ROC day of month check failed!");
|
||||
///
|
||||
/// // Convert to an equivalent Gregorian date
|
||||
/// let date_gregorian = date_roc.to_calendar(Gregorian);
|
||||
///
|
||||
/// assert_eq!(date_gregorian.year().number, 1912, "Gregorian from ROC year check failed!");
|
||||
/// assert_eq!(date_gregorian.month().ordinal, 2, "Gregorian from ROC month check failed!");
|
||||
/// assert_eq!(date_gregorian.day_of_month().0, 3, "Gregorian from ROC day of month check failed!");
|
||||
pub fn try_new_roc_date(year: i32, month: u8, day: u8) -> Result<Date<Roc>, CalendarError> {
|
||||
let iso_year = year.saturating_add(ROC_ERA_OFFSET);
|
||||
Date::try_new_iso_date(iso_year, month, day).map(|d| Date::new_from_iso(d, Roc))
|
||||
}
|
||||
}
|
||||
|
||||
impl DateTime<Roc> {
|
||||
/// Construct a new Republic of China calendar datetime from integers.
|
||||
///
|
||||
/// Years are specified in the "roc" era, Before Minguo dates are negative (year 0 is 1 Before Minguo)
|
||||
///
|
||||
/// ```rust
|
||||
/// use icu::calendar::gregorian::Gregorian;
|
||||
/// use icu::calendar::DateTime;
|
||||
/// use tinystr::tinystr;
|
||||
///
|
||||
/// // Create a new ROC DateTime
|
||||
/// let datetime_roc = DateTime::try_new_roc_datetime(1, 2, 3, 13, 1, 0)
|
||||
/// .expect("Failed to initialize ROC DateTime instance.");
|
||||
///
|
||||
/// assert_eq!(datetime_roc.date.year().era.0, tinystr!(16, "roc"));
|
||||
/// assert_eq!(datetime_roc.date.year().number, 1, "ROC year check failed!");
|
||||
/// assert_eq!(
|
||||
/// datetime_roc.date.month().ordinal,
|
||||
/// 2,
|
||||
/// "ROC month check failed!"
|
||||
/// );
|
||||
/// assert_eq!(
|
||||
/// datetime_roc.date.day_of_month().0,
|
||||
/// 3,
|
||||
/// "ROC day of month check failed!"
|
||||
/// );
|
||||
/// assert_eq!(datetime_roc.time.hour.number(), 13);
|
||||
/// assert_eq!(datetime_roc.time.minute.number(), 1);
|
||||
/// assert_eq!(datetime_roc.time.second.number(), 0);
|
||||
/// ```
|
||||
pub fn try_new_roc_datetime(
|
||||
year: i32,
|
||||
month: u8,
|
||||
day: u8,
|
||||
hour: u8,
|
||||
minute: u8,
|
||||
second: u8,
|
||||
) -> Result<DateTime<Roc>, CalendarError> {
|
||||
Ok(DateTime {
|
||||
date: Date::try_new_roc_date(year, month, day)?,
|
||||
time: types::Time::try_new(hour, minute, second, 0)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn year_as_roc(year: i64) -> types::FormattableYear {
|
||||
let year_i32 = i64_to_saturated_i32(year);
|
||||
let offset_i64 = ROC_ERA_OFFSET as i64;
|
||||
if year > offset_i64 {
|
||||
types::FormattableYear {
|
||||
era: types::Era(tinystr!(16, "roc")),
|
||||
number: year_i32.saturating_sub(ROC_ERA_OFFSET),
|
||||
cyclic: None,
|
||||
related_iso: Some(year_i32),
|
||||
}
|
||||
} else {
|
||||
types::FormattableYear {
|
||||
era: types::Era(tinystr!(16, "roc-inverse")),
|
||||
number: (ROC_ERA_OFFSET + 1).saturating_sub(year_i32),
|
||||
cyclic: None,
|
||||
related_iso: Some(year_i32),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
use super::*;
|
||||
use crate::types::Era;
|
||||
use calendrical_calculations::rata_die::RataDie;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TestCase {
|
||||
fixed_date: RataDie,
|
||||
iso_year: i32,
|
||||
iso_month: u8,
|
||||
iso_day: u8,
|
||||
expected_year: i32,
|
||||
expected_era: Era,
|
||||
expected_month: u32,
|
||||
expected_day: u32,
|
||||
}
|
||||
|
||||
fn check_test_case(case: TestCase) {
|
||||
let iso_from_fixed = Iso::iso_from_fixed(case.fixed_date);
|
||||
let roc_from_fixed = Date::new_from_iso(iso_from_fixed, Roc);
|
||||
assert_eq!(roc_from_fixed.year().number, case.expected_year,
|
||||
"Failed year check from fixed: {case:?}\nISO: {iso_from_fixed:?}\nROC: {roc_from_fixed:?}");
|
||||
assert_eq!(roc_from_fixed.year().era, case.expected_era,
|
||||
"Failed era check from fixed: {case:?}\nISO: {iso_from_fixed:?}\nROC: {roc_from_fixed:?}");
|
||||
assert_eq!(roc_from_fixed.month().ordinal, case.expected_month,
|
||||
"Failed month check from fixed: {case:?}\nISO: {iso_from_fixed:?}\nROC: {roc_from_fixed:?}");
|
||||
assert_eq!(roc_from_fixed.day_of_month().0, case.expected_day,
|
||||
"Failed day_of_month check from fixed: {case:?}\nISO: {iso_from_fixed:?}\nROC: {roc_from_fixed:?}");
|
||||
|
||||
let iso_from_case = Date::try_new_iso_date(case.iso_year, case.iso_month, case.iso_day)
|
||||
.expect("Failed to initialize ISO date for {case:?}");
|
||||
let roc_from_case = Date::new_from_iso(iso_from_case, Roc);
|
||||
assert_eq!(iso_from_fixed, iso_from_case,
|
||||
"ISO from fixed not equal to ISO generated from manually-input ymd\nCase: {case:?}\nFixed: {iso_from_fixed:?}\nManual: {iso_from_case:?}");
|
||||
assert_eq!(roc_from_fixed, roc_from_case,
|
||||
"ROC date from fixed not equal to ROC generated from manually-input ymd\nCase: {case:?}\nFixed: {roc_from_fixed:?}\nManual: {roc_from_case:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_roc_current_era() {
|
||||
// Tests that the ROC calendar gives the correct expected day, month, and year for years >= 1912
|
||||
// (years in the ROC/minguo era)
|
||||
//
|
||||
// Jan 1. 1912 CE = RD 697978
|
||||
|
||||
let cases = [
|
||||
TestCase {
|
||||
fixed_date: RataDie::new(697978),
|
||||
iso_year: 1912,
|
||||
iso_month: 1,
|
||||
iso_day: 1,
|
||||
expected_year: 1,
|
||||
expected_era: Era(tinystr!(16, "roc")),
|
||||
expected_month: 1,
|
||||
expected_day: 1,
|
||||
},
|
||||
TestCase {
|
||||
fixed_date: RataDie::new(698037),
|
||||
iso_year: 1912,
|
||||
iso_month: 2,
|
||||
iso_day: 29,
|
||||
expected_year: 1,
|
||||
expected_era: Era(tinystr!(16, "roc")),
|
||||
expected_month: 2,
|
||||
expected_day: 29,
|
||||
},
|
||||
TestCase {
|
||||
fixed_date: RataDie::new(698524),
|
||||
iso_year: 1913,
|
||||
iso_month: 6,
|
||||
iso_day: 30,
|
||||
expected_year: 2,
|
||||
expected_era: Era(tinystr!(16, "roc")),
|
||||
expected_month: 6,
|
||||
expected_day: 30,
|
||||
},
|
||||
TestCase {
|
||||
fixed_date: RataDie::new(738714),
|
||||
iso_year: 2023,
|
||||
iso_month: 7,
|
||||
iso_day: 13,
|
||||
expected_year: 112,
|
||||
expected_era: Era(tinystr!(16, "roc")),
|
||||
expected_month: 7,
|
||||
expected_day: 13,
|
||||
},
|
||||
];
|
||||
|
||||
for case in cases {
|
||||
check_test_case(case);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_roc_prior_era() {
|
||||
// Tests that the ROC calendar gives the correct expected day, month, and year for years <= 1911
|
||||
// (years in the ROC/minguo era)
|
||||
//
|
||||
// Jan 1. 1912 CE = RD 697978
|
||||
let cases = [
|
||||
TestCase {
|
||||
fixed_date: RataDie::new(697977),
|
||||
iso_year: 1911,
|
||||
iso_month: 12,
|
||||
iso_day: 31,
|
||||
expected_year: 1,
|
||||
expected_era: Era(tinystr!(16, "roc-inverse")),
|
||||
expected_month: 12,
|
||||
expected_day: 31,
|
||||
},
|
||||
TestCase {
|
||||
fixed_date: RataDie::new(697613),
|
||||
iso_year: 1911,
|
||||
iso_month: 1,
|
||||
iso_day: 1,
|
||||
expected_year: 1,
|
||||
expected_era: Era(tinystr!(16, "roc-inverse")),
|
||||
expected_month: 1,
|
||||
expected_day: 1,
|
||||
},
|
||||
TestCase {
|
||||
fixed_date: RataDie::new(697612),
|
||||
iso_year: 1910,
|
||||
iso_month: 12,
|
||||
iso_day: 31,
|
||||
expected_year: 2,
|
||||
expected_era: Era(tinystr!(16, "roc-inverse")),
|
||||
expected_month: 12,
|
||||
expected_day: 31,
|
||||
},
|
||||
TestCase {
|
||||
fixed_date: RataDie::new(696576),
|
||||
iso_year: 1908,
|
||||
iso_month: 2,
|
||||
iso_day: 29,
|
||||
expected_year: 4,
|
||||
expected_era: Era(tinystr!(16, "roc-inverse")),
|
||||
expected_month: 2,
|
||||
expected_day: 29,
|
||||
},
|
||||
TestCase {
|
||||
fixed_date: RataDie::new(1),
|
||||
iso_year: 1,
|
||||
iso_month: 1,
|
||||
iso_day: 1,
|
||||
expected_year: 1911,
|
||||
expected_era: Era(tinystr!(16, "roc-inverse")),
|
||||
expected_month: 1,
|
||||
expected_day: 1,
|
||||
},
|
||||
TestCase {
|
||||
fixed_date: RataDie::new(0),
|
||||
iso_year: 0,
|
||||
iso_month: 12,
|
||||
iso_day: 31,
|
||||
expected_year: 1912,
|
||||
expected_era: Era(tinystr!(16, "roc-inverse")),
|
||||
expected_month: 12,
|
||||
expected_day: 31,
|
||||
},
|
||||
];
|
||||
|
||||
for case in cases {
|
||||
check_test_case(case);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_roc_directionality_near_epoch() {
|
||||
// Tests that for a large range of fixed dates near the beginning of the minguo era (CE 1912),
|
||||
// the comparison between those two fixed dates should be equal to the comparison between their
|
||||
// corresponding YMD.
|
||||
let rd_epoch_start = 697978;
|
||||
for i in (rd_epoch_start - 100)..=(rd_epoch_start + 100) {
|
||||
for j in (rd_epoch_start - 100)..=(rd_epoch_start + 100) {
|
||||
let iso_i = Iso::iso_from_fixed(RataDie::new(i));
|
||||
let iso_j = Iso::iso_from_fixed(RataDie::new(j));
|
||||
|
||||
let roc_i = iso_i.to_calendar(Roc);
|
||||
let roc_j = iso_j.to_calendar(Roc);
|
||||
|
||||
assert_eq!(
|
||||
i.cmp(&j),
|
||||
iso_i.cmp(&iso_j),
|
||||
"ISO directionality inconsistent with directionality for i: {i}, j: {j}"
|
||||
);
|
||||
assert_eq!(
|
||||
i.cmp(&j),
|
||||
roc_i.cmp(&roc_j),
|
||||
"ROC directionality inconsistent with directionality for i: {i}, j: {j}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_roc_directionality_near_rd_zero() {
|
||||
// Same as `test_directionality_near_epoch`, but with a focus around RD 0
|
||||
for i in -100..=100 {
|
||||
for j in -100..100 {
|
||||
let iso_i = Iso::iso_from_fixed(RataDie::new(i));
|
||||
let iso_j = Iso::iso_from_fixed(RataDie::new(j));
|
||||
|
||||
let roc_i = iso_i.to_calendar(Roc);
|
||||
let roc_j = iso_j.to_calendar(Roc);
|
||||
|
||||
assert_eq!(
|
||||
i.cmp(&j),
|
||||
iso_i.cmp(&iso_j),
|
||||
"ISO directionality inconsistent with directionality for i: {i}, j: {j}"
|
||||
);
|
||||
assert_eq!(
|
||||
i.cmp(&j),
|
||||
roc_i.cmp(&roc_j),
|
||||
"ROC directionality inconsistent with directionality for i: {i}, j: {j}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,781 @@
|
|||
// This file is part of ICU4X. For terms of use, please see the file
|
||||
// called LICENSE at the top level of the ICU4X source tree
|
||||
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
|
||||
|
||||
//! This module contains various types used by `icu_calendar` and `icu_datetime`
|
||||
|
||||
use crate::error::CalendarError;
|
||||
use core::convert::TryFrom;
|
||||
use core::convert::TryInto;
|
||||
use core::fmt;
|
||||
use core::num::NonZeroU8;
|
||||
use core::str::FromStr;
|
||||
use tinystr::TinyAsciiStr;
|
||||
use tinystr::{TinyStr16, TinyStr4};
|
||||
use zerovec::maps::ZeroMapKV;
|
||||
use zerovec::ule::AsULE;
|
||||
|
||||
/// The era of a particular date
|
||||
///
|
||||
/// Different calendars use different era codes, see their documentation
|
||||
/// for details.
|
||||
///
|
||||
/// Era codes are shared with Temporal, [see Temporal proposal][era-proposal].
|
||||
///
|
||||
/// [era-proposal]: https://tc39.es/proposal-intl-era-monthcode/
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
#[allow(clippy::exhaustive_structs)] // this is a newtype
|
||||
pub struct Era(pub TinyStr16);
|
||||
|
||||
impl From<TinyStr16> for Era {
|
||||
fn from(x: TinyStr16) -> Self {
|
||||
Self(x)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Era {
|
||||
type Err = <TinyStr16 as FromStr>::Err;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
s.parse().map(Self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Representation of a formattable year.
|
||||
///
|
||||
/// More fields may be added in the future for things like extended year
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
#[non_exhaustive]
|
||||
pub struct FormattableYear {
|
||||
/// The era containing the year.
|
||||
///
|
||||
/// This may not always be the canonical era for the calendar and could be an alias,
|
||||
/// for example all `islamic` calendars return `islamic` as the formattable era code
|
||||
/// which allows them to share data.
|
||||
pub era: Era,
|
||||
|
||||
/// The year number in the current era (usually 1-based).
|
||||
pub number: i32,
|
||||
|
||||
/// The year in the current cycle for cyclic calendars (1-indexed)
|
||||
/// can be set to `None` for non-cyclic calendars
|
||||
///
|
||||
/// For chinese and dangi it will be
|
||||
/// a number between 1 and 60, for hypothetical other calendars it may be something else.
|
||||
pub cyclic: Option<NonZeroU8>,
|
||||
|
||||
/// The related ISO year. This is normally the ISO (proleptic Gregorian) year having the greatest
|
||||
/// overlap with the calendar year. It is used in certain date formatting patterns.
|
||||
///
|
||||
/// Can be `None` if the calendar does not typically use `related_iso` (and CLDR does not contain patterns
|
||||
/// using it)
|
||||
pub related_iso: Option<i32>,
|
||||
}
|
||||
|
||||
impl FormattableYear {
|
||||
/// Construct a new Year given an era and number
|
||||
///
|
||||
/// Other fields can be set mutably after construction
|
||||
/// as needed
|
||||
pub fn new(era: Era, number: i32, cyclic: Option<NonZeroU8>) -> Self {
|
||||
Self {
|
||||
era,
|
||||
number,
|
||||
cyclic,
|
||||
related_iso: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Representation of a month in a year
|
||||
///
|
||||
/// Month codes typically look like `M01`, `M02`, etc, but can handle leap months
|
||||
/// (`M03L`) in lunar calendars. Solar calendars will have codes between `M01` and `M12`
|
||||
/// potentially with an `M13` for epagomenal months. Check the docs for a particular calendar
|
||||
/// for details on what its month codes are.
|
||||
///
|
||||
/// Month codes are shared with Temporal, [see Temporal proposal][era-proposal].
|
||||
///
|
||||
/// [era-proposal]: https://tc39.es/proposal-intl-era-monthcode/
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[allow(clippy::exhaustive_structs)] // this is a newtype
|
||||
#[cfg_attr(
|
||||
feature = "datagen",
|
||||
derive(serde::Serialize, databake::Bake),
|
||||
databake(path = icu_calendar::types),
|
||||
)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
|
||||
pub struct MonthCode(pub TinyStr4);
|
||||
|
||||
impl MonthCode {
|
||||
/// Returns an option which is `Some` containing the non-month version of a leap month
|
||||
/// if the [`MonthCode`] this method is called upon is a leap month, and `None` otherwise.
|
||||
/// This method assumes the [`MonthCode`] is valid.
|
||||
pub fn get_normal_if_leap(self) -> Option<MonthCode> {
|
||||
let bytes = self.0.all_bytes();
|
||||
if bytes[3] == b'L' {
|
||||
Some(MonthCode(TinyAsciiStr::from_bytes(&bytes[0..3]).ok()?))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
/// Get the month number and whether or not it is leap from the month code
|
||||
pub fn parsed(self) -> Option<(u8, bool)> {
|
||||
// Match statements on tinystrs are annoying so instead
|
||||
// we calculate it from the bytes directly
|
||||
|
||||
let bytes = self.0.all_bytes();
|
||||
let is_leap = bytes[3] == b'L';
|
||||
if bytes[0] != b'M' {
|
||||
return None;
|
||||
}
|
||||
if bytes[1] == b'0' {
|
||||
if bytes[2] >= b'1' && bytes[2] <= b'9' {
|
||||
return Some((bytes[2] - b'0', is_leap));
|
||||
}
|
||||
} else if bytes[1] == b'1' && bytes[2] >= b'0' && bytes[2] <= b'3' {
|
||||
return Some((10 + bytes[2] - b'0', is_leap));
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_normal_month_code_if_leap() {
|
||||
let mc1 = MonthCode(tinystr::tinystr!(4, "M01L"));
|
||||
let result1 = mc1.get_normal_if_leap();
|
||||
assert_eq!(result1, Some(MonthCode(tinystr::tinystr!(4, "M01"))));
|
||||
|
||||
let mc2 = MonthCode(tinystr::tinystr!(4, "M11L"));
|
||||
let result2 = mc2.get_normal_if_leap();
|
||||
assert_eq!(result2, Some(MonthCode(tinystr::tinystr!(4, "M11"))));
|
||||
|
||||
let mc_invalid = MonthCode(tinystr::tinystr!(4, "M10"));
|
||||
let result_invalid = mc_invalid.get_normal_if_leap();
|
||||
assert_eq!(result_invalid, None);
|
||||
}
|
||||
|
||||
impl AsULE for MonthCode {
|
||||
type ULE = TinyStr4;
|
||||
fn to_unaligned(self) -> TinyStr4 {
|
||||
self.0
|
||||
}
|
||||
fn from_unaligned(u: TinyStr4) -> Self {
|
||||
Self(u)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ZeroMapKV<'a> for MonthCode {
|
||||
type Container = zerovec::ZeroVec<'a, MonthCode>;
|
||||
type Slice = zerovec::ZeroSlice<MonthCode>;
|
||||
type GetType = <MonthCode as AsULE>::ULE;
|
||||
type OwnedType = MonthCode;
|
||||
}
|
||||
|
||||
impl fmt::Display for MonthCode {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TinyStr4> for MonthCode {
|
||||
fn from(x: TinyStr4) -> Self {
|
||||
Self(x)
|
||||
}
|
||||
}
|
||||
impl FromStr for MonthCode {
|
||||
type Err = <TinyStr4 as FromStr>::Err;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
s.parse().map(Self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Representation of a formattable month.
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
#[allow(clippy::exhaustive_structs)] // this type is stable
|
||||
pub struct FormattableMonth {
|
||||
/// The month number in this given year. For calendars with leap months, all months after
|
||||
/// the leap month will end up with an incremented number.
|
||||
///
|
||||
/// In general, prefer using the month code in generic code.
|
||||
pub ordinal: u32,
|
||||
|
||||
/// The month code, used to distinguish months during leap years.
|
||||
///
|
||||
/// This may not necessarily be the canonical month code for a month in cases where a month has different
|
||||
/// formatting in a leap year, for example Adar/Adar II in the Hebrew calendar in a leap year has
|
||||
/// the code M06, but for formatting specifically the Hebrew calendar will return M06L since it is formatted
|
||||
/// differently.
|
||||
pub code: MonthCode,
|
||||
}
|
||||
|
||||
/// A struct containing various details about the position of the day within a year. It is returned
|
||||
// by the [`day_of_year_info()`](trait.DateInput.html#tymethod.day_of_year_info) method of the
|
||||
// [`DateInput`] trait.
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
#[allow(clippy::exhaustive_structs)] // this type is stable
|
||||
pub struct DayOfYearInfo {
|
||||
/// The current day of the year, 1-based.
|
||||
pub day_of_year: u16,
|
||||
/// The number of days in a year.
|
||||
pub days_in_year: u16,
|
||||
/// The previous year.
|
||||
pub prev_year: FormattableYear,
|
||||
/// The number of days in the previous year.
|
||||
pub days_in_prev_year: u16,
|
||||
/// The next year.
|
||||
pub next_year: FormattableYear,
|
||||
}
|
||||
|
||||
/// A day number in a month. Usually 1-based.
|
||||
#[allow(clippy::exhaustive_structs)] // this is a newtype
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct DayOfMonth(pub u32);
|
||||
|
||||
/// A week number in a month. Usually 1-based.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[allow(clippy::exhaustive_structs)] // this is a newtype
|
||||
pub struct WeekOfMonth(pub u32);
|
||||
|
||||
/// A week number in a year. Usually 1-based.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[allow(clippy::exhaustive_structs)] // this is a newtype
|
||||
pub struct WeekOfYear(pub u32);
|
||||
|
||||
/// A day of week in month. 1-based.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[allow(clippy::exhaustive_structs)] // this is a newtype
|
||||
pub struct DayOfWeekInMonth(pub u32);
|
||||
|
||||
impl From<DayOfMonth> for DayOfWeekInMonth {
|
||||
fn from(day_of_month: DayOfMonth) -> Self {
|
||||
DayOfWeekInMonth(1 + ((day_of_month.0 - 1) / 7))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_day_of_week_in_month() {
|
||||
assert_eq!(DayOfWeekInMonth::from(DayOfMonth(1)).0, 1);
|
||||
assert_eq!(DayOfWeekInMonth::from(DayOfMonth(7)).0, 1);
|
||||
assert_eq!(DayOfWeekInMonth::from(DayOfMonth(8)).0, 2);
|
||||
}
|
||||
|
||||
/// This macro defines a struct for 0-based date fields: hours, minutes, seconds
|
||||
/// and fractional seconds. Each unit is bounded by a range. The traits implemented
|
||||
/// here will return a Result on whether or not the unit is in range from the given
|
||||
/// input.
|
||||
macro_rules! dt_unit {
|
||||
($name:ident, $storage:ident, $value:expr, $docs:expr) => {
|
||||
#[doc=$docs]
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)]
|
||||
pub struct $name($storage);
|
||||
|
||||
impl $name {
|
||||
/// Gets the numeric value for this component.
|
||||
pub const fn number(self) -> $storage {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Creates a new value at 0.
|
||||
pub const fn zero() -> $name {
|
||||
Self(0)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for $name {
|
||||
type Err = CalendarError;
|
||||
|
||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||
let val: $storage = input.parse()?;
|
||||
if val > $value {
|
||||
Err(CalendarError::Overflow {
|
||||
field: "$name",
|
||||
max: $value,
|
||||
})
|
||||
} else {
|
||||
Ok(Self(val))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<$storage> for $name {
|
||||
type Error = CalendarError;
|
||||
|
||||
fn try_from(input: $storage) -> Result<Self, Self::Error> {
|
||||
if input > $value {
|
||||
Err(CalendarError::Overflow {
|
||||
field: "$name",
|
||||
max: $value,
|
||||
})
|
||||
} else {
|
||||
Ok(Self(input))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<usize> for $name {
|
||||
type Error = CalendarError;
|
||||
|
||||
fn try_from(input: usize) -> Result<Self, Self::Error> {
|
||||
if input > $value {
|
||||
Err(CalendarError::Overflow {
|
||||
field: "$name",
|
||||
max: $value,
|
||||
})
|
||||
} else {
|
||||
Ok(Self(input as $storage))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<$name> for $storage {
|
||||
fn from(input: $name) -> Self {
|
||||
input.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<$name> for usize {
|
||||
fn from(input: $name) -> Self {
|
||||
input.0 as Self
|
||||
}
|
||||
}
|
||||
|
||||
impl $name {
|
||||
/// Attempts to add two values.
|
||||
/// Returns `Some` if the sum is within bounds.
|
||||
/// Returns `None` if the sum is out of bounds.
|
||||
pub fn try_add(self, other: $storage) -> Option<Self> {
|
||||
let sum = self.0.saturating_add(other);
|
||||
if sum > $value {
|
||||
None
|
||||
} else {
|
||||
Some(Self(sum))
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to subtract two values.
|
||||
/// Returns `Some` if the difference is within bounds.
|
||||
/// Returns `None` if the difference is out of bounds.
|
||||
pub fn try_sub(self, other: $storage) -> Option<Self> {
|
||||
self.0.checked_sub(other).map(Self)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
dt_unit!(
|
||||
IsoHour,
|
||||
u8,
|
||||
24,
|
||||
"An ISO-8601 hour component, for use with ISO calendars.
|
||||
|
||||
Must be within inclusive bounds `[0, 24]`. The value could be equal to 24 to
|
||||
denote the end of a day, with the writing 24:00:00. It corresponds to the same
|
||||
time as the next day at 00:00:00."
|
||||
);
|
||||
|
||||
dt_unit!(
|
||||
IsoMinute,
|
||||
u8,
|
||||
60,
|
||||
"An ISO-8601 minute component, for use with ISO calendars.
|
||||
|
||||
Must be within inclusive bounds `[0, 60]`. The value could be equal to 60 to
|
||||
denote the end of an hour, with the writing 12:60:00. This example corresponds
|
||||
to the same time as 13:00:00. This is an extension to ISO 8601."
|
||||
);
|
||||
|
||||
dt_unit!(
|
||||
IsoSecond,
|
||||
u8,
|
||||
61,
|
||||
"An ISO-8601 second component, for use with ISO calendars.
|
||||
|
||||
Must be within inclusive bounds `[0, 61]`. `60` accommodates for leap seconds.
|
||||
|
||||
The value could also be equal to 60 or 61, to indicate the end of a leap second,
|
||||
with the writing `23:59:61.000000000Z` or `23:59:60.000000000Z`. These examples,
|
||||
if used with this goal, would correspond to the same time as the next day, at
|
||||
time `00:00:00.000000000Z`. This is an extension to ISO 8601."
|
||||
);
|
||||
|
||||
dt_unit!(
|
||||
NanoSecond,
|
||||
u32,
|
||||
999_999_999,
|
||||
"A fractional second component, stored as nanoseconds.
|
||||
|
||||
Must be within inclusive bounds `[0, 999_999_999]`."
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn test_iso_hour_arithmetic() {
|
||||
const HOUR_MAX: u8 = 24;
|
||||
const HOUR_VALUE: u8 = 5;
|
||||
let hour = IsoHour(HOUR_VALUE);
|
||||
|
||||
// middle of bounds
|
||||
assert_eq!(
|
||||
hour.try_add(HOUR_VALUE - 1),
|
||||
Some(IsoHour(HOUR_VALUE + (HOUR_VALUE - 1)))
|
||||
);
|
||||
assert_eq!(
|
||||
hour.try_sub(HOUR_VALUE - 1),
|
||||
Some(IsoHour(HOUR_VALUE - (HOUR_VALUE - 1)))
|
||||
);
|
||||
|
||||
// edge of bounds
|
||||
assert_eq!(hour.try_add(HOUR_MAX - HOUR_VALUE), Some(IsoHour(HOUR_MAX)));
|
||||
assert_eq!(hour.try_sub(HOUR_VALUE), Some(IsoHour(0)));
|
||||
|
||||
// out of bounds
|
||||
assert_eq!(hour.try_add(1 + HOUR_MAX - HOUR_VALUE), None);
|
||||
assert_eq!(hour.try_sub(1 + HOUR_VALUE), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iso_minute_arithmetic() {
|
||||
const MINUTE_MAX: u8 = 60;
|
||||
const MINUTE_VALUE: u8 = 5;
|
||||
let minute = IsoMinute(MINUTE_VALUE);
|
||||
|
||||
// middle of bounds
|
||||
assert_eq!(
|
||||
minute.try_add(MINUTE_VALUE - 1),
|
||||
Some(IsoMinute(MINUTE_VALUE + (MINUTE_VALUE - 1)))
|
||||
);
|
||||
assert_eq!(
|
||||
minute.try_sub(MINUTE_VALUE - 1),
|
||||
Some(IsoMinute(MINUTE_VALUE - (MINUTE_VALUE - 1)))
|
||||
);
|
||||
|
||||
// edge of bounds
|
||||
assert_eq!(
|
||||
minute.try_add(MINUTE_MAX - MINUTE_VALUE),
|
||||
Some(IsoMinute(MINUTE_MAX))
|
||||
);
|
||||
assert_eq!(minute.try_sub(MINUTE_VALUE), Some(IsoMinute(0)));
|
||||
|
||||
// out of bounds
|
||||
assert_eq!(minute.try_add(1 + MINUTE_MAX - MINUTE_VALUE), None);
|
||||
assert_eq!(minute.try_sub(1 + MINUTE_VALUE), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iso_second_arithmetic() {
|
||||
const SECOND_MAX: u8 = 61;
|
||||
const SECOND_VALUE: u8 = 5;
|
||||
let second = IsoSecond(SECOND_VALUE);
|
||||
|
||||
// middle of bounds
|
||||
assert_eq!(
|
||||
second.try_add(SECOND_VALUE - 1),
|
||||
Some(IsoSecond(SECOND_VALUE + (SECOND_VALUE - 1)))
|
||||
);
|
||||
assert_eq!(
|
||||
second.try_sub(SECOND_VALUE - 1),
|
||||
Some(IsoSecond(SECOND_VALUE - (SECOND_VALUE - 1)))
|
||||
);
|
||||
|
||||
// edge of bounds
|
||||
assert_eq!(
|
||||
second.try_add(SECOND_MAX - SECOND_VALUE),
|
||||
Some(IsoSecond(SECOND_MAX))
|
||||
);
|
||||
assert_eq!(second.try_sub(SECOND_VALUE), Some(IsoSecond(0)));
|
||||
|
||||
// out of bounds
|
||||
assert_eq!(second.try_add(1 + SECOND_MAX - SECOND_VALUE), None);
|
||||
assert_eq!(second.try_sub(1 + SECOND_VALUE), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iso_nano_second_arithmetic() {
|
||||
const NANO_SECOND_MAX: u32 = 999_999_999;
|
||||
const NANO_SECOND_VALUE: u32 = 5;
|
||||
let nano_second = NanoSecond(NANO_SECOND_VALUE);
|
||||
|
||||
// middle of bounds
|
||||
assert_eq!(
|
||||
nano_second.try_add(NANO_SECOND_VALUE - 1),
|
||||
Some(NanoSecond(NANO_SECOND_VALUE + (NANO_SECOND_VALUE - 1)))
|
||||
);
|
||||
assert_eq!(
|
||||
nano_second.try_sub(NANO_SECOND_VALUE - 1),
|
||||
Some(NanoSecond(NANO_SECOND_VALUE - (NANO_SECOND_VALUE - 1)))
|
||||
);
|
||||
|
||||
// edge of bounds
|
||||
assert_eq!(
|
||||
nano_second.try_add(NANO_SECOND_MAX - NANO_SECOND_VALUE),
|
||||
Some(NanoSecond(NANO_SECOND_MAX))
|
||||
);
|
||||
assert_eq!(nano_second.try_sub(NANO_SECOND_VALUE), Some(NanoSecond(0)));
|
||||
|
||||
// out of bounds
|
||||
assert_eq!(
|
||||
nano_second.try_add(1 + NANO_SECOND_MAX - NANO_SECOND_VALUE),
|
||||
None
|
||||
);
|
||||
assert_eq!(nano_second.try_sub(1 + NANO_SECOND_VALUE), None);
|
||||
}
|
||||
|
||||
/// A representation of a time in hours, minutes, seconds, and nanoseconds
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
#[allow(clippy::exhaustive_structs)] // this type is stable
|
||||
pub struct Time {
|
||||
/// 0-based hour.
|
||||
pub hour: IsoHour,
|
||||
|
||||
/// 0-based minute.
|
||||
pub minute: IsoMinute,
|
||||
|
||||
/// 0-based second.
|
||||
pub second: IsoSecond,
|
||||
|
||||
/// Fractional second
|
||||
pub nanosecond: NanoSecond,
|
||||
}
|
||||
|
||||
impl Time {
|
||||
/// Construct a new [`Time`], without validating that all components are in range
|
||||
pub const fn new(
|
||||
hour: IsoHour,
|
||||
minute: IsoMinute,
|
||||
second: IsoSecond,
|
||||
nanosecond: NanoSecond,
|
||||
) -> Self {
|
||||
Self {
|
||||
hour,
|
||||
minute,
|
||||
second,
|
||||
nanosecond,
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a new [`Time`] representing midnight (00:00.000)
|
||||
pub const fn midnight() -> Self {
|
||||
Self {
|
||||
hour: IsoHour::zero(),
|
||||
minute: IsoMinute::zero(),
|
||||
second: IsoSecond::zero(),
|
||||
nanosecond: NanoSecond::zero(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a new [`Time`], whilst validating that all components are in range
|
||||
pub fn try_new(
|
||||
hour: u8,
|
||||
minute: u8,
|
||||
second: u8,
|
||||
nanosecond: u32,
|
||||
) -> Result<Self, CalendarError> {
|
||||
Ok(Self {
|
||||
hour: hour.try_into()?,
|
||||
minute: minute.try_into()?,
|
||||
second: second.try_into()?,
|
||||
nanosecond: nanosecond.try_into()?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Takes a number of minutes, which could be positive or negative, and returns the Time
|
||||
/// and the day number, which could be positive or negative.
|
||||
pub(crate) fn from_minute_with_remainder_days(minute: i32) -> (Time, i32) {
|
||||
let (extra_days, minute_in_day) = (minute.div_euclid(1440), minute.rem_euclid(1440));
|
||||
let (hours, minutes) = (minute_in_day / 60, minute_in_day % 60);
|
||||
#[allow(clippy::unwrap_used)] // values are moduloed to be in range
|
||||
(
|
||||
Self {
|
||||
hour: (hours as u8).try_into().unwrap(),
|
||||
minute: (minutes as u8).try_into().unwrap(),
|
||||
second: IsoSecond::zero(),
|
||||
nanosecond: NanoSecond::zero(),
|
||||
},
|
||||
extra_days,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_minute_with_remainder_days() {
|
||||
#[derive(Debug)]
|
||||
struct TestCase {
|
||||
minute: i32,
|
||||
expected_time: Time,
|
||||
expected_remainder: i32,
|
||||
}
|
||||
let zero_time = Time::new(
|
||||
IsoHour::zero(),
|
||||
IsoMinute::zero(),
|
||||
IsoSecond::zero(),
|
||||
NanoSecond::zero(),
|
||||
);
|
||||
let first_minute_in_day = Time::new(
|
||||
IsoHour::zero(),
|
||||
IsoMinute::try_from(1u8).unwrap(),
|
||||
IsoSecond::zero(),
|
||||
NanoSecond::zero(),
|
||||
);
|
||||
let last_minute_in_day = Time::new(
|
||||
IsoHour::try_from(23u8).unwrap(),
|
||||
IsoMinute::try_from(59u8).unwrap(),
|
||||
IsoSecond::zero(),
|
||||
NanoSecond::zero(),
|
||||
);
|
||||
let cases = [
|
||||
TestCase {
|
||||
minute: 0,
|
||||
expected_time: zero_time,
|
||||
expected_remainder: 0,
|
||||
},
|
||||
TestCase {
|
||||
minute: 30,
|
||||
expected_time: Time::new(
|
||||
IsoHour::zero(),
|
||||
IsoMinute::try_from(30u8).unwrap(),
|
||||
IsoSecond::zero(),
|
||||
NanoSecond::zero(),
|
||||
),
|
||||
expected_remainder: 0,
|
||||
},
|
||||
TestCase {
|
||||
minute: 60,
|
||||
expected_time: Time::new(
|
||||
IsoHour::try_from(1u8).unwrap(),
|
||||
IsoMinute::zero(),
|
||||
IsoSecond::zero(),
|
||||
NanoSecond::zero(),
|
||||
),
|
||||
expected_remainder: 0,
|
||||
},
|
||||
TestCase {
|
||||
minute: 90,
|
||||
expected_time: Time::new(
|
||||
IsoHour::try_from(1u8).unwrap(),
|
||||
IsoMinute::try_from(30u8).unwrap(),
|
||||
IsoSecond::zero(),
|
||||
NanoSecond::zero(),
|
||||
),
|
||||
expected_remainder: 0,
|
||||
},
|
||||
TestCase {
|
||||
minute: 1439,
|
||||
expected_time: last_minute_in_day,
|
||||
expected_remainder: 0,
|
||||
},
|
||||
TestCase {
|
||||
minute: 1440,
|
||||
expected_time: Time::new(
|
||||
IsoHour::zero(),
|
||||
IsoMinute::zero(),
|
||||
IsoSecond::zero(),
|
||||
NanoSecond::zero(),
|
||||
),
|
||||
expected_remainder: 1,
|
||||
},
|
||||
TestCase {
|
||||
minute: 1441,
|
||||
expected_time: first_minute_in_day,
|
||||
expected_remainder: 1,
|
||||
},
|
||||
TestCase {
|
||||
minute: i32::MAX,
|
||||
expected_time: Time::new(
|
||||
IsoHour::try_from(2u8).unwrap(),
|
||||
IsoMinute::try_from(7u8).unwrap(),
|
||||
IsoSecond::zero(),
|
||||
NanoSecond::zero(),
|
||||
),
|
||||
expected_remainder: 1491308,
|
||||
},
|
||||
TestCase {
|
||||
minute: -1,
|
||||
expected_time: last_minute_in_day,
|
||||
expected_remainder: -1,
|
||||
},
|
||||
TestCase {
|
||||
minute: -1439,
|
||||
expected_time: first_minute_in_day,
|
||||
expected_remainder: -1,
|
||||
},
|
||||
TestCase {
|
||||
minute: -1440,
|
||||
expected_time: zero_time,
|
||||
expected_remainder: -1,
|
||||
},
|
||||
TestCase {
|
||||
minute: -1441,
|
||||
expected_time: last_minute_in_day,
|
||||
expected_remainder: -2,
|
||||
},
|
||||
TestCase {
|
||||
minute: i32::MIN,
|
||||
expected_time: Time::new(
|
||||
IsoHour::try_from(21u8).unwrap(),
|
||||
IsoMinute::try_from(52u8).unwrap(),
|
||||
IsoSecond::zero(),
|
||||
NanoSecond::zero(),
|
||||
),
|
||||
expected_remainder: -1491309,
|
||||
},
|
||||
];
|
||||
for cas in cases {
|
||||
let (actual_time, actual_remainder) = Time::from_minute_with_remainder_days(cas.minute);
|
||||
assert_eq!(actual_time, cas.expected_time, "{cas:?}");
|
||||
assert_eq!(actual_remainder, cas.expected_remainder, "{cas:?}");
|
||||
}
|
||||
}
|
||||
|
||||
/// A weekday in a 7-day week, according to ISO-8601.
|
||||
///
|
||||
/// The discriminant values correspond to ISO-8601 weekday numbers (Monday = 1, Sunday = 7).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use icu::calendar::types::IsoWeekday;
|
||||
///
|
||||
/// assert_eq!(1, IsoWeekday::Monday as usize);
|
||||
/// assert_eq!(7, IsoWeekday::Sunday as usize);
|
||||
/// ```
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[allow(missing_docs)] // The weekday variants should be self-obvious.
|
||||
#[repr(i8)]
|
||||
#[cfg_attr(
|
||||
feature = "datagen",
|
||||
derive(serde::Serialize, databake::Bake),
|
||||
databake(path = icu_calendar::types),
|
||||
)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
|
||||
#[allow(clippy::exhaustive_enums)] // This is stable
|
||||
pub enum IsoWeekday {
|
||||
Monday = 1,
|
||||
Tuesday,
|
||||
Wednesday,
|
||||
Thursday,
|
||||
Friday,
|
||||
Saturday,
|
||||
Sunday,
|
||||
}
|
||||
|
||||
impl From<usize> for IsoWeekday {
|
||||
/// Convert from an ISO-8601 weekday number to an [`IsoWeekday`] enum. 0 is automatically converted
|
||||
/// to 7 (Sunday). If the number is out of range, it is interpreted modulo 7.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use icu::calendar::types::IsoWeekday;
|
||||
///
|
||||
/// assert_eq!(IsoWeekday::Sunday, IsoWeekday::from(0));
|
||||
/// assert_eq!(IsoWeekday::Monday, IsoWeekday::from(1));
|
||||
/// assert_eq!(IsoWeekday::Sunday, IsoWeekday::from(7));
|
||||
/// assert_eq!(IsoWeekday::Monday, IsoWeekday::from(8));
|
||||
/// ```
|
||||
fn from(input: usize) -> Self {
|
||||
let mut ordinal = (input % 7) as i8;
|
||||
if ordinal == 0 {
|
||||
ordinal = 7;
|
||||
}
|
||||
unsafe { core::mem::transmute(ordinal) }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,601 @@
|
|||
// This file is part of ICU4X. For terms of use, please see the file
|
||||
// called LICENSE at the top level of the ICU4X source tree
|
||||
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
|
||||
|
||||
use crate::{
|
||||
error::CalendarError,
|
||||
provider::WeekDataV1,
|
||||
types::{DayOfMonth, DayOfYearInfo, IsoWeekday, WeekOfMonth},
|
||||
};
|
||||
use icu_provider::prelude::*;
|
||||
|
||||
/// Minimum number of days in a month unit required for using this module
|
||||
pub const MIN_UNIT_DAYS: u16 = 14;
|
||||
|
||||
/// Calculator for week-of-month and week-of-year based on locale-specific configurations.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[non_exhaustive]
|
||||
pub struct WeekCalculator {
|
||||
/// The first day of a week.
|
||||
pub first_weekday: IsoWeekday,
|
||||
/// For a given week, the minimum number of that week's days present in a given month or year
|
||||
/// for the week to be considered part of that month or year.
|
||||
pub min_week_days: u8,
|
||||
}
|
||||
|
||||
impl From<WeekDataV1> for WeekCalculator {
|
||||
fn from(other: WeekDataV1) -> Self {
|
||||
Self {
|
||||
first_weekday: other.first_weekday,
|
||||
min_week_days: other.min_week_days,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&WeekDataV1> for WeekCalculator {
|
||||
fn from(other: &WeekDataV1) -> Self {
|
||||
Self {
|
||||
first_weekday: other.first_weekday,
|
||||
min_week_days: other.min_week_days,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WeekCalculator {
|
||||
icu_provider::gen_any_buffer_data_constructors!(
|
||||
locale: include,
|
||||
options: skip,
|
||||
error: CalendarError,
|
||||
/// Creates a new [`WeekCalculator`] from compiled locale data.
|
||||
///
|
||||
/// ✨ *Enabled with the `compiled_data` Cargo feature.*
|
||||
///
|
||||
/// [📚 Help choosing a constructor](icu_provider::constructors)
|
||||
);
|
||||
|
||||
#[doc = icu_provider::gen_any_buffer_unstable_docs!(UNSTABLE, Self::try_new)]
|
||||
pub fn try_new_unstable<P>(provider: &P, locale: &DataLocale) -> Result<Self, CalendarError>
|
||||
where
|
||||
P: DataProvider<crate::provider::WeekDataV1Marker> + ?Sized,
|
||||
{
|
||||
provider
|
||||
.load(DataRequest {
|
||||
locale,
|
||||
metadata: Default::default(),
|
||||
})
|
||||
.and_then(DataResponse::take_payload)
|
||||
.map(|payload| payload.get().into())
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Returns the week of month according to a calendar with min_week_days = 1.
|
||||
///
|
||||
/// This is different from what the UTS35 spec describes [1] but the latter is
|
||||
/// missing a month of week-of-month field so following the spec would result
|
||||
/// in inconsistencies (e.g. in the ISO calendar 2021-01-01 is the last week
|
||||
/// of December but 'MMMMW' would have it formatted as 'week 5 of January').
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use icu_calendar::types::{DayOfMonth, IsoWeekday, WeekOfMonth};
|
||||
/// use icu_calendar::week::WeekCalculator;
|
||||
///
|
||||
/// let week_calculator =
|
||||
/// WeekCalculator::try_new(&icu_locid::locale!("und-GB").into())
|
||||
/// .expect("locale should be present");
|
||||
///
|
||||
/// // Wednesday the 10th is in week 2:
|
||||
/// assert_eq!(
|
||||
/// WeekOfMonth(2),
|
||||
/// week_calculator.week_of_month(DayOfMonth(10), IsoWeekday::Wednesday)
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
/// [1]: https://www.unicode.org/reports/tr35/tr35-55/tr35-dates.html#Date_Patterns_Week_Of_Year
|
||||
pub fn week_of_month(&self, day_of_month: DayOfMonth, iso_weekday: IsoWeekday) -> WeekOfMonth {
|
||||
WeekOfMonth(simple_week_of(self.first_weekday, day_of_month.0 as u16, iso_weekday) as u32)
|
||||
}
|
||||
|
||||
/// Returns the week of year according to the weekday and [`DayOfYearInfo`].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use icu_calendar::types::{DayOfMonth, IsoWeekday};
|
||||
/// use icu_calendar::week::{RelativeUnit, WeekCalculator, WeekOf};
|
||||
/// use icu_calendar::Date;
|
||||
///
|
||||
/// let week_calculator =
|
||||
/// WeekCalculator::try_new(&icu_locid::locale!("und-GB").into())
|
||||
/// .expect("locale should be present");
|
||||
///
|
||||
/// let iso_date = Date::try_new_iso_date(2022, 8, 26).unwrap();
|
||||
///
|
||||
/// // Friday August 26 is in week 34 of year 2022:
|
||||
/// assert_eq!(
|
||||
/// WeekOf {
|
||||
/// unit: RelativeUnit::Current,
|
||||
/// week: 34
|
||||
/// },
|
||||
/// week_calculator
|
||||
/// .week_of_year(iso_date.day_of_year_info(), IsoWeekday::Friday)
|
||||
/// .unwrap()
|
||||
/// );
|
||||
/// ```
|
||||
pub fn week_of_year(
|
||||
&self,
|
||||
day_of_year_info: DayOfYearInfo,
|
||||
iso_weekday: IsoWeekday,
|
||||
) -> Result<WeekOf, CalendarError> {
|
||||
week_of(
|
||||
self,
|
||||
day_of_year_info.days_in_prev_year,
|
||||
day_of_year_info.days_in_year,
|
||||
day_of_year_info.day_of_year,
|
||||
iso_weekday,
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns the zero based index of `weekday` vs this calendar's start of week.
|
||||
fn weekday_index(&self, weekday: IsoWeekday) -> i8 {
|
||||
(7 + (weekday as i8) - (self.first_weekday as i8)) % 7
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for WeekCalculator {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
first_weekday: IsoWeekday::Monday,
|
||||
min_week_days: 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the weekday that's `num_days` after `weekday`.
|
||||
fn add_to_weekday(weekday: IsoWeekday, num_days: i32) -> IsoWeekday {
|
||||
let new_weekday = (7 + (weekday as i32) + (num_days % 7)) % 7;
|
||||
IsoWeekday::from(new_weekday as usize)
|
||||
}
|
||||
|
||||
/// Which year or month that a calendar assigns a week to relative to the year/month
|
||||
/// the week is in.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
enum RelativeWeek {
|
||||
/// A week that is assigned to the last week of the previous year/month. e.g. 2021-01-01 is week 54 of 2020 per the ISO calendar.
|
||||
LastWeekOfPreviousUnit,
|
||||
/// A week that's assigned to the current year/month. The offset is 1-based. e.g. 2021-01-11 is week 2 of 2021 per the ISO calendar so would be WeekOfCurrentUnit(2).
|
||||
WeekOfCurrentUnit(u16),
|
||||
/// A week that is assigned to the first week of the next year/month. e.g. 2019-12-31 is week 1 of 2020 per the ISO calendar.
|
||||
FirstWeekOfNextUnit,
|
||||
}
|
||||
|
||||
/// Information about a year or month.
|
||||
struct UnitInfo {
|
||||
/// The weekday of this year/month's first day.
|
||||
first_day: IsoWeekday,
|
||||
/// The number of days in this year/month.
|
||||
duration_days: u16,
|
||||
}
|
||||
|
||||
impl UnitInfo {
|
||||
/// Creates a UnitInfo for a given year or month.
|
||||
fn new(first_day: IsoWeekday, duration_days: u16) -> Result<UnitInfo, CalendarError> {
|
||||
if duration_days < MIN_UNIT_DAYS {
|
||||
return Err(CalendarError::Underflow {
|
||||
field: "Month/Year duration",
|
||||
min: MIN_UNIT_DAYS as isize,
|
||||
});
|
||||
}
|
||||
Ok(UnitInfo {
|
||||
first_day,
|
||||
duration_days,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the start of this unit's first week.
|
||||
///
|
||||
/// The returned value can be negative if this unit's first week started during the previous
|
||||
/// unit.
|
||||
fn first_week_offset(&self, calendar: &WeekCalculator) -> i8 {
|
||||
let first_day_index = calendar.weekday_index(self.first_day);
|
||||
if 7 - first_day_index >= calendar.min_week_days as i8 {
|
||||
-first_day_index
|
||||
} else {
|
||||
7 - first_day_index
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the number of weeks in this unit according to `calendar`.
|
||||
fn num_weeks(&self, calendar: &WeekCalculator) -> u16 {
|
||||
let first_week_offset = self.first_week_offset(calendar);
|
||||
let num_days_including_first_week =
|
||||
(self.duration_days as i32) - (first_week_offset as i32);
|
||||
debug_assert!(
|
||||
num_days_including_first_week >= 0,
|
||||
"Unit is shorter than a week."
|
||||
);
|
||||
((num_days_including_first_week + 7 - (calendar.min_week_days as i32)) / 7) as u16
|
||||
}
|
||||
|
||||
/// Returns the week number for the given day in this unit.
|
||||
fn relative_week(&self, calendar: &WeekCalculator, day: u16) -> RelativeWeek {
|
||||
let days_since_first_week =
|
||||
i32::from(day) - i32::from(self.first_week_offset(calendar)) - 1;
|
||||
if days_since_first_week < 0 {
|
||||
return RelativeWeek::LastWeekOfPreviousUnit;
|
||||
}
|
||||
|
||||
let week_number = (1 + days_since_first_week / 7) as u16;
|
||||
if week_number > self.num_weeks(calendar) {
|
||||
return RelativeWeek::FirstWeekOfNextUnit;
|
||||
}
|
||||
RelativeWeek::WeekOfCurrentUnit(week_number)
|
||||
}
|
||||
}
|
||||
|
||||
/// The year or month that a calendar assigns a week to relative to the year/month that it is in.
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[allow(clippy::exhaustive_enums)] // this type is stable
|
||||
pub enum RelativeUnit {
|
||||
/// A week that is assigned to previous year/month. e.g. 2021-01-01 is week 54 of 2020 per the ISO calendar.
|
||||
Previous,
|
||||
/// A week that's assigned to the current year/month. e.g. 2021-01-11 is week 2 of 2021 per the ISO calendar.
|
||||
Current,
|
||||
/// A week that is assigned to the next year/month. e.g. 2019-12-31 is week 1 of 2020 per the ISO calendar.
|
||||
Next,
|
||||
}
|
||||
|
||||
/// The week number assigned to a given week according to a calendar.
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[allow(clippy::exhaustive_structs)] // this type is stable
|
||||
pub struct WeekOf {
|
||||
/// Week of month/year. 1 based.
|
||||
pub week: u16,
|
||||
/// The month/year that this week is in, relative to the month/year of the input date.
|
||||
pub unit: RelativeUnit,
|
||||
}
|
||||
|
||||
/// Computes & returns the week of given month/year according to `calendar`.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - calendar: Calendar information used to compute the week number.
|
||||
/// - num_days_in_previous_unit: The number of days in the preceding month/year.
|
||||
/// - num_days_in_unit: The number of days in the month/year.
|
||||
/// - day: 1-based day of month/year.
|
||||
/// - week_day: The weekday of `day`..
|
||||
pub fn week_of(
|
||||
calendar: &WeekCalculator,
|
||||
num_days_in_previous_unit: u16,
|
||||
num_days_in_unit: u16,
|
||||
day: u16,
|
||||
week_day: IsoWeekday,
|
||||
) -> Result<WeekOf, CalendarError> {
|
||||
let current = UnitInfo::new(
|
||||
// The first day of this month/year is (day - 1) days from `day`.
|
||||
add_to_weekday(week_day, 1 - i32::from(day)),
|
||||
num_days_in_unit,
|
||||
)?;
|
||||
|
||||
match current.relative_week(calendar, day) {
|
||||
RelativeWeek::LastWeekOfPreviousUnit => {
|
||||
let previous = UnitInfo::new(
|
||||
add_to_weekday(current.first_day, -i32::from(num_days_in_previous_unit)),
|
||||
num_days_in_previous_unit,
|
||||
)?;
|
||||
|
||||
Ok(WeekOf {
|
||||
week: previous.num_weeks(calendar),
|
||||
unit: RelativeUnit::Previous,
|
||||
})
|
||||
}
|
||||
RelativeWeek::WeekOfCurrentUnit(w) => Ok(WeekOf {
|
||||
week: w,
|
||||
unit: RelativeUnit::Current,
|
||||
}),
|
||||
RelativeWeek::FirstWeekOfNextUnit => Ok(WeekOf {
|
||||
week: 1,
|
||||
unit: RelativeUnit::Next,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Computes & returns the week of given month or year according to a calendar with min_week_days = 1.
|
||||
///
|
||||
/// Does not know anything about the unit size (month or year), and will just assume the date falls
|
||||
/// within whatever unit that is being considered. In other words, this function returns strictly increasing
|
||||
/// values as `day` increases, unlike [`week_of()`] which is cyclic.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - first_weekday: The first day of a week.
|
||||
/// - day: 1-based day of the month or year.
|
||||
/// - week_day: The weekday of `day`.
|
||||
pub fn simple_week_of(first_weekday: IsoWeekday, day: u16, week_day: IsoWeekday) -> u16 {
|
||||
let calendar = WeekCalculator {
|
||||
first_weekday,
|
||||
min_week_days: 1,
|
||||
};
|
||||
|
||||
#[allow(clippy::unwrap_used)] // week_of should can't fail with MIN_UNIT_DAYS
|
||||
week_of(
|
||||
&calendar,
|
||||
// The duration of the previous unit does not influence the result if min_week_days = 1
|
||||
// so we only need to use a valid value.
|
||||
MIN_UNIT_DAYS,
|
||||
u16::MAX,
|
||||
day,
|
||||
week_day,
|
||||
)
|
||||
.unwrap()
|
||||
.week
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{week_of, RelativeUnit, RelativeWeek, UnitInfo, WeekCalculator, WeekOf};
|
||||
use crate::{error::CalendarError, types::IsoWeekday, Date, DateDuration};
|
||||
|
||||
static ISO_CALENDAR: WeekCalculator = WeekCalculator {
|
||||
first_weekday: IsoWeekday::Monday,
|
||||
min_week_days: 4,
|
||||
};
|
||||
|
||||
static AE_CALENDAR: WeekCalculator = WeekCalculator {
|
||||
first_weekday: IsoWeekday::Saturday,
|
||||
min_week_days: 4,
|
||||
};
|
||||
|
||||
static US_CALENDAR: WeekCalculator = WeekCalculator {
|
||||
first_weekday: IsoWeekday::Sunday,
|
||||
min_week_days: 1,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_weekday_index() {
|
||||
assert_eq!(ISO_CALENDAR.weekday_index(IsoWeekday::Monday), 0);
|
||||
assert_eq!(ISO_CALENDAR.weekday_index(IsoWeekday::Sunday), 6);
|
||||
|
||||
assert_eq!(AE_CALENDAR.weekday_index(IsoWeekday::Saturday), 0);
|
||||
assert_eq!(AE_CALENDAR.weekday_index(IsoWeekday::Friday), 6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_first_week_offset() {
|
||||
let first_week_offset =
|
||||
|calendar, day| UnitInfo::new(day, 30).unwrap().first_week_offset(calendar);
|
||||
assert_eq!(first_week_offset(&ISO_CALENDAR, IsoWeekday::Monday), 0);
|
||||
assert_eq!(first_week_offset(&ISO_CALENDAR, IsoWeekday::Tuesday), -1);
|
||||
assert_eq!(first_week_offset(&ISO_CALENDAR, IsoWeekday::Wednesday), -2);
|
||||
assert_eq!(first_week_offset(&ISO_CALENDAR, IsoWeekday::Thursday), -3);
|
||||
assert_eq!(first_week_offset(&ISO_CALENDAR, IsoWeekday::Friday), 3);
|
||||
assert_eq!(first_week_offset(&ISO_CALENDAR, IsoWeekday::Saturday), 2);
|
||||
assert_eq!(first_week_offset(&ISO_CALENDAR, IsoWeekday::Sunday), 1);
|
||||
|
||||
assert_eq!(first_week_offset(&AE_CALENDAR, IsoWeekday::Saturday), 0);
|
||||
assert_eq!(first_week_offset(&AE_CALENDAR, IsoWeekday::Sunday), -1);
|
||||
assert_eq!(first_week_offset(&AE_CALENDAR, IsoWeekday::Monday), -2);
|
||||
assert_eq!(first_week_offset(&AE_CALENDAR, IsoWeekday::Tuesday), -3);
|
||||
assert_eq!(first_week_offset(&AE_CALENDAR, IsoWeekday::Wednesday), 3);
|
||||
assert_eq!(first_week_offset(&AE_CALENDAR, IsoWeekday::Thursday), 2);
|
||||
assert_eq!(first_week_offset(&AE_CALENDAR, IsoWeekday::Friday), 1);
|
||||
|
||||
assert_eq!(first_week_offset(&US_CALENDAR, IsoWeekday::Sunday), 0);
|
||||
assert_eq!(first_week_offset(&US_CALENDAR, IsoWeekday::Monday), -1);
|
||||
assert_eq!(first_week_offset(&US_CALENDAR, IsoWeekday::Tuesday), -2);
|
||||
assert_eq!(first_week_offset(&US_CALENDAR, IsoWeekday::Wednesday), -3);
|
||||
assert_eq!(first_week_offset(&US_CALENDAR, IsoWeekday::Thursday), -4);
|
||||
assert_eq!(first_week_offset(&US_CALENDAR, IsoWeekday::Friday), -5);
|
||||
assert_eq!(first_week_offset(&US_CALENDAR, IsoWeekday::Saturday), -6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_num_weeks() -> Result<(), CalendarError> {
|
||||
// 4 days in first & last week.
|
||||
assert_eq!(
|
||||
UnitInfo::new(IsoWeekday::Thursday, 4 + 2 * 7 + 4)?.num_weeks(&ISO_CALENDAR),
|
||||
4
|
||||
);
|
||||
// 3 days in first week, 4 in last week.
|
||||
assert_eq!(
|
||||
UnitInfo::new(IsoWeekday::Friday, 3 + 2 * 7 + 4)?.num_weeks(&ISO_CALENDAR),
|
||||
3
|
||||
);
|
||||
// 3 days in first & last week.
|
||||
assert_eq!(
|
||||
UnitInfo::new(IsoWeekday::Friday, 3 + 2 * 7 + 3)?.num_weeks(&ISO_CALENDAR),
|
||||
2
|
||||
);
|
||||
|
||||
// 1 day in first & last week.
|
||||
assert_eq!(
|
||||
UnitInfo::new(IsoWeekday::Saturday, 1 + 2 * 7 + 1)?.num_weeks(&US_CALENDAR),
|
||||
4
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Uses enumeration & bucketing to assign each day of a month or year `unit` to a week.
|
||||
///
|
||||
/// This alternative implementation serves as an exhaustive safety check
|
||||
/// of relative_week() (in addition to the manual test points used
|
||||
/// for testing week_of()).
|
||||
fn classify_days_of_unit(calendar: &WeekCalculator, unit: &UnitInfo) -> Vec<RelativeWeek> {
|
||||
let mut weeks: Vec<Vec<IsoWeekday>> = Vec::new();
|
||||
for day_index in 0..unit.duration_days {
|
||||
let day = super::add_to_weekday(unit.first_day, i32::from(day_index));
|
||||
if day == calendar.first_weekday || weeks.is_empty() {
|
||||
weeks.push(Vec::new());
|
||||
}
|
||||
weeks.last_mut().unwrap().push(day);
|
||||
}
|
||||
|
||||
let mut day_week_of_units = Vec::new();
|
||||
let mut weeks_in_unit = 0;
|
||||
for (index, week) in weeks.iter().enumerate() {
|
||||
let week_of_unit = if week.len() < usize::from(calendar.min_week_days) {
|
||||
match index {
|
||||
0 => RelativeWeek::LastWeekOfPreviousUnit,
|
||||
x if x == weeks.len() - 1 => RelativeWeek::FirstWeekOfNextUnit,
|
||||
_ => panic!(),
|
||||
}
|
||||
} else {
|
||||
weeks_in_unit += 1;
|
||||
RelativeWeek::WeekOfCurrentUnit(weeks_in_unit)
|
||||
};
|
||||
|
||||
day_week_of_units.append(&mut [week_of_unit].repeat(week.len()));
|
||||
}
|
||||
day_week_of_units
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_relative_week_of_month() -> Result<(), CalendarError> {
|
||||
for min_week_days in 1..7 {
|
||||
for start_of_week in 1..7 {
|
||||
let calendar = WeekCalculator {
|
||||
first_weekday: IsoWeekday::from(start_of_week),
|
||||
min_week_days,
|
||||
};
|
||||
for unit_duration in super::MIN_UNIT_DAYS..400 {
|
||||
for start_of_unit in 1..7 {
|
||||
let unit = UnitInfo::new(IsoWeekday::from(start_of_unit), unit_duration)?;
|
||||
let expected = classify_days_of_unit(&calendar, &unit);
|
||||
for (index, expected_week_of) in expected.iter().enumerate() {
|
||||
let day = index + 1;
|
||||
assert_eq!(
|
||||
unit.relative_week(&calendar, day as u16),
|
||||
*expected_week_of,
|
||||
"For the {day}/{unit_duration} starting on IsoWeekday \
|
||||
{start_of_unit} using start_of_week {start_of_week} \
|
||||
& min_week_days {min_week_days}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn week_of_month_from_iso_date(
|
||||
calendar: &WeekCalculator,
|
||||
yyyymmdd: u32,
|
||||
) -> Result<WeekOf, CalendarError> {
|
||||
let year = (yyyymmdd / 10000) as i32;
|
||||
let month = ((yyyymmdd / 100) % 100) as u8;
|
||||
let day = (yyyymmdd % 100) as u8;
|
||||
|
||||
let date = Date::try_new_iso_date(year, month, day)?;
|
||||
let previous_month = date.added(DateDuration::new(0, -1, 0, 0));
|
||||
|
||||
week_of(
|
||||
calendar,
|
||||
u16::from(previous_month.days_in_month()),
|
||||
u16::from(date.days_in_month()),
|
||||
u16::from(day),
|
||||
date.day_of_week(),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_week_of_month_using_dates() -> Result<(), CalendarError> {
|
||||
assert_eq!(
|
||||
week_of_month_from_iso_date(&ISO_CALENDAR, 20210418)?,
|
||||
WeekOf {
|
||||
week: 3,
|
||||
unit: RelativeUnit::Current,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
week_of_month_from_iso_date(&ISO_CALENDAR, 20210419)?,
|
||||
WeekOf {
|
||||
week: 4,
|
||||
unit: RelativeUnit::Current,
|
||||
}
|
||||
);
|
||||
|
||||
// First day of year is a Thursday.
|
||||
assert_eq!(
|
||||
week_of_month_from_iso_date(&ISO_CALENDAR, 20180101)?,
|
||||
WeekOf {
|
||||
week: 1,
|
||||
unit: RelativeUnit::Current,
|
||||
}
|
||||
);
|
||||
// First day of year is a Friday.
|
||||
assert_eq!(
|
||||
week_of_month_from_iso_date(&ISO_CALENDAR, 20210101)?,
|
||||
WeekOf {
|
||||
week: 5,
|
||||
unit: RelativeUnit::Previous,
|
||||
}
|
||||
);
|
||||
|
||||
// The month ends on a Wednesday.
|
||||
assert_eq!(
|
||||
week_of_month_from_iso_date(&ISO_CALENDAR, 20200930)?,
|
||||
WeekOf {
|
||||
week: 1,
|
||||
unit: RelativeUnit::Next,
|
||||
}
|
||||
);
|
||||
// The month ends on a Thursday.
|
||||
assert_eq!(
|
||||
week_of_month_from_iso_date(&ISO_CALENDAR, 20201231)?,
|
||||
WeekOf {
|
||||
week: 5,
|
||||
unit: RelativeUnit::Current,
|
||||
}
|
||||
);
|
||||
|
||||
// US calendar always assigns the week to the current month. 2020-12-31 is a Thursday.
|
||||
assert_eq!(
|
||||
week_of_month_from_iso_date(&US_CALENDAR, 20201231)?,
|
||||
WeekOf {
|
||||
week: 5,
|
||||
unit: RelativeUnit::Current,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
week_of_month_from_iso_date(&US_CALENDAR, 20210101)?,
|
||||
WeekOf {
|
||||
week: 1,
|
||||
unit: RelativeUnit::Current,
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simple_week_of() {
|
||||
// The 1st is a Monday and the week starts on Mondays.
|
||||
assert_eq!(
|
||||
simple_week_of(IsoWeekday::Monday, 2, IsoWeekday::Tuesday),
|
||||
1
|
||||
);
|
||||
assert_eq!(simple_week_of(IsoWeekday::Monday, 7, IsoWeekday::Sunday), 1);
|
||||
assert_eq!(simple_week_of(IsoWeekday::Monday, 8, IsoWeekday::Monday), 2);
|
||||
|
||||
// The 1st is a Wednesday and the week starts on Tuesdays.
|
||||
assert_eq!(
|
||||
simple_week_of(IsoWeekday::Tuesday, 1, IsoWeekday::Wednesday),
|
||||
1
|
||||
);
|
||||
assert_eq!(
|
||||
simple_week_of(IsoWeekday::Tuesday, 6, IsoWeekday::Monday),
|
||||
1
|
||||
);
|
||||
assert_eq!(
|
||||
simple_week_of(IsoWeekday::Tuesday, 7, IsoWeekday::Tuesday),
|
||||
2
|
||||
);
|
||||
|
||||
// The 1st is a Monday and the week starts on Sundays.
|
||||
assert_eq!(
|
||||
simple_week_of(IsoWeekday::Sunday, 26, IsoWeekday::Friday),
|
||||
4
|
||||
);
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
{"files":{"Cargo.toml":"c460ba2e580e4b8ee0353018ef9060729006b442dad69e9a7c403e0581789017","LICENSE":"853f87c96f3d249f200fec6db1114427bc8bdf4afddc93c576956d78152ce978","README.md":"2f95fb0e29ecad6911e71b9aa17b5fb03b451fc3a9cf39cf813685b8adb93c97","data/macros.rs":"1b09cc81f8ce452ac2705c6cb1484d22e00599ea0071b077db9449dede1a7579","data/macros/calendar_japanese_v1.rs.data":"9e2955674aa7584c2701b1c6a68342fa51d33ad9a506063eb2f87e93ac3b990f","data/macros/calendar_japanext_v1.rs.data":"356827b4686d2c2838b12a237c365a96dc6b60b0eaa16ef0574c2f6ef3185db8","data/macros/datetime_week_data_v1.rs.data":"ea6bd34a67f8f4536600ef893daf356c5b39b22dafa2c5e52e89983076874651","src/lib.rs":"ec94d44f61b0ce73dae2d6628aa9771dacf93f309bd27000ac6eb3dca0e955de"},"package":"22aec7d032735d9acb256eeef72adcac43c3b7572f19b51576a63d664b524ca2"}
|
|
@ -0,0 +1,33 @@
|
|||
# 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.67"
|
||||
name = "icu_calendar_data"
|
||||
version = "1.4.0"
|
||||
authors = ["The ICU4X Project Developers"]
|
||||
include = [
|
||||
"data/**/*",
|
||||
"src/**/*",
|
||||
"examples/**/*",
|
||||
"benches/**/*",
|
||||
"tests/**/*",
|
||||
"Cargo.toml",
|
||||
"LICENSE",
|
||||
"README.md",
|
||||
]
|
||||
description = "Data for the icu_calendar crate"
|
||||
homepage = "https://icu4x.unicode.org"
|
||||
readme = "README.md"
|
||||
categories = ["internationalization"]
|
||||
license-file = "LICENSE"
|
||||
repository = "https://github.com/unicode-org/icu4x"
|
|
@ -0,0 +1,44 @@
|
|||
UNICODE LICENSE V3
|
||||
|
||||
COPYRIGHT AND PERMISSION NOTICE
|
||||
|
||||
Copyright © 2020-2023 Unicode, Inc.
|
||||
|
||||
NOTICE TO USER: Carefully read the following legal agreement. BY
|
||||
DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR
|
||||
SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE
|
||||
TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT
|
||||
DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of data files and any associated documentation (the "Data Files") or
|
||||
software and any associated documentation (the "Software") to deal in the
|
||||
Data Files or Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, and/or sell
|
||||
copies of the Data Files or Software, and to permit persons to whom the
|
||||
Data Files or Software are furnished to do so, provided that either (a)
|
||||
this copyright and permission notice appear with all copies of the Data
|
||||
Files or Software, or (b) this copyright and permission notice appear in
|
||||
associated Documentation.
|
||||
|
||||
THE DATA FILES AND SOFTWARE ARE 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 OF
|
||||
THIRD PARTY RIGHTS.
|
||||
|
||||
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE
|
||||
BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES,
|
||||
OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
|
||||
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
||||
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA
|
||||
FILES OR SOFTWARE.
|
||||
|
||||
Except as contained in this notice, the name of a copyright holder shall
|
||||
not be used in advertising or otherwise to promote the sale, use or other
|
||||
dealings in these Data Files or Software without prior written
|
||||
authorization of the copyright holder.
|
||||
|
||||
—
|
||||
|
||||
Portions of ICU4X may have been adapted from ICU4C and/or ICU4J.
|
||||
ICU 1.8.1 to ICU 57.1 © 1995-2016 International Business Machines Corporation and others.
|
|
@ -0,0 +1,11 @@
|
|||
# icu_calendar_data [![crates.io](https://img.shields.io/crates/v/icu_calendar_data)](https://crates.io/crates/icu_calendar_data)
|
||||
|
||||
<!-- cargo-rdme start -->
|
||||
|
||||
Data for the icu_calendar crate
|
||||
|
||||
<!-- cargo-rdme end -->
|
||||
|
||||
## More Information
|
||||
|
||||
For more information on development, authorship, contributing etc. please visit [`ICU4X home page`](https://github.com/unicode-org/icu4x).
|
|
@ -0,0 +1,41 @@
|
|||
// @generated
|
||||
/// Marks a type as a data provider. You can then use macros like
|
||||
/// `impl_core_helloworld_v1` to add implementations.
|
||||
///
|
||||
/// ```ignore
|
||||
/// struct MyProvider;
|
||||
/// const _: () = {
|
||||
/// include!("path/to/generated/macros.rs");
|
||||
/// make_provider!(MyProvider);
|
||||
/// impl_core_helloworld_v1!(MyProvider);
|
||||
/// }
|
||||
/// ```
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! __make_provider {
|
||||
($ name : ty) => {
|
||||
#[clippy::msrv = "1.67"]
|
||||
impl $name {
|
||||
#[doc(hidden)]
|
||||
#[allow(dead_code)]
|
||||
pub const MUST_USE_MAKE_PROVIDER_MACRO: () = ();
|
||||
}
|
||||
};
|
||||
}
|
||||
#[doc(inline)]
|
||||
pub use __make_provider as make_provider;
|
||||
#[macro_use]
|
||||
#[path = "macros/calendar_japanese_v1.rs.data"]
|
||||
mod calendar_japanese_v1;
|
||||
#[doc(inline)]
|
||||
pub use __impl_calendar_japanese_v1 as impl_calendar_japanese_v1;
|
||||
#[macro_use]
|
||||
#[path = "macros/calendar_japanext_v1.rs.data"]
|
||||
mod calendar_japanext_v1;
|
||||
#[doc(inline)]
|
||||
pub use __impl_calendar_japanext_v1 as impl_calendar_japanext_v1;
|
||||
#[macro_use]
|
||||
#[path = "macros/datetime_week_data_v1.rs.data"]
|
||||
mod datetime_week_data_v1;
|
||||
#[doc(inline)]
|
||||
pub use __impl_datetime_week_data_v1 as impl_datetime_week_data_v1;
|
27
third_party/rust/icu_calendar_data/data/macros/calendar_japanese_v1.rs.data
поставляемый
Normal file
27
third_party/rust/icu_calendar_data/data/macros/calendar_japanese_v1.rs.data
поставляемый
Normal file
|
@ -0,0 +1,27 @@
|
|||
// @generated
|
||||
/// Implement `DataProvider<JapaneseErasV1Marker>` on the given struct using the data
|
||||
/// hardcoded in this file. This allows the struct to be used with
|
||||
/// `icu`'s `_unstable` constructors.
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! __impl_calendar_japanese_v1 {
|
||||
($ provider : ty) => {
|
||||
#[clippy::msrv = "1.67"]
|
||||
const _: () = <$provider>::MUST_USE_MAKE_PROVIDER_MACRO;
|
||||
#[clippy::msrv = "1.67"]
|
||||
impl $provider {
|
||||
#[doc(hidden)]
|
||||
pub const SINGLETON_CALENDAR_JAPANESE_V1: &'static <icu::calendar::provider::JapaneseErasV1Marker as icu_provider::DataMarker>::Yokeable = &icu::calendar::provider::JapaneseErasV1 { dates_to_eras: unsafe { zerovec::ZeroVec::from_bytes_unchecked(b"L\x07\0\0\t\x08meiji\0\0\0\0\0\0\0\0\0\0\0x\x07\0\0\x07\x1Etaisho\0\0\0\0\0\0\0\0\0\0\x86\x07\0\0\x0C\x19showa\0\0\0\0\0\0\0\0\0\0\0\xC5\x07\0\0\x01\x08heisei\0\0\0\0\0\0\0\0\0\0\xE3\x07\0\0\x05\x01reiwa\0\0\0\0\0\0\0\0\0\0\0") } };
|
||||
}
|
||||
#[clippy::msrv = "1.67"]
|
||||
impl icu_provider::DataProvider<icu::calendar::provider::JapaneseErasV1Marker> for $provider {
|
||||
fn load(&self, req: icu_provider::DataRequest) -> Result<icu_provider::DataResponse<icu::calendar::provider::JapaneseErasV1Marker>, icu_provider::DataError> {
|
||||
if req.locale.is_empty() {
|
||||
Ok(icu_provider::DataResponse { payload: Some(icu_provider::DataPayload::from_static_ref(Self::SINGLETON_CALENDAR_JAPANESE_V1)), metadata: Default::default() })
|
||||
} else {
|
||||
Err(icu_provider::DataErrorKind::ExtraneousLocale.with_req(<icu::calendar::provider::JapaneseErasV1Marker as icu_provider::KeyedDataMarker>::KEY, req))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
27
third_party/rust/icu_calendar_data/data/macros/calendar_japanext_v1.rs.data
поставляемый
Normal file
27
third_party/rust/icu_calendar_data/data/macros/calendar_japanext_v1.rs.data
поставляемый
Normal file
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
40
third_party/rust/icu_calendar_data/data/macros/datetime_week_data_v1.rs.data
поставляемый
Normal file
40
third_party/rust/icu_calendar_data/data/macros/datetime_week_data_v1.rs.data
поставляемый
Normal file
|
@ -0,0 +1,40 @@
|
|||
// @generated
|
||||
/// Implement `DataProvider<WeekDataV1Marker>` on the given struct using the data
|
||||
/// hardcoded in this file. This allows the struct to be used with
|
||||
/// `icu`'s `_unstable` constructors.
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! __impl_datetime_week_data_v1 {
|
||||
($ provider : ty) => {
|
||||
#[clippy::msrv = "1.67"]
|
||||
const _: () = <$provider>::MUST_USE_MAKE_PROVIDER_MACRO;
|
||||
#[clippy::msrv = "1.67"]
|
||||
impl icu_provider::DataProvider<icu::calendar::provider::WeekDataV1Marker> for $provider {
|
||||
fn load(&self, req: icu_provider::DataRequest) -> Result<icu_provider::DataResponse<icu::calendar::provider::WeekDataV1Marker>, icu_provider::DataError> {
|
||||
static UND_MV: <icu::calendar::provider::WeekDataV1Marker as icu_provider::DataMarker>::Yokeable = icu::calendar::provider::WeekDataV1 { first_weekday: icu::calendar::types::IsoWeekday::Friday, min_week_days: 1u8 };
|
||||
static UND: <icu::calendar::provider::WeekDataV1Marker as icu_provider::DataMarker>::Yokeable = icu::calendar::provider::WeekDataV1 { first_weekday: icu::calendar::types::IsoWeekday::Monday, min_week_days: 1u8 };
|
||||
static UND_AD: <icu::calendar::provider::WeekDataV1Marker as icu_provider::DataMarker>::Yokeable = icu::calendar::provider::WeekDataV1 { first_weekday: icu::calendar::types::IsoWeekday::Monday, min_week_days: 4u8 };
|
||||
static UND_AE: <icu::calendar::provider::WeekDataV1Marker as icu_provider::DataMarker>::Yokeable = icu::calendar::provider::WeekDataV1 { first_weekday: icu::calendar::types::IsoWeekday::Saturday, min_week_days: 1u8 };
|
||||
static UND_AG: <icu::calendar::provider::WeekDataV1Marker as icu_provider::DataMarker>::Yokeable = icu::calendar::provider::WeekDataV1 { first_weekday: icu::calendar::types::IsoWeekday::Sunday, min_week_days: 1u8 };
|
||||
static UND_PT: <icu::calendar::provider::WeekDataV1Marker as icu_provider::DataMarker>::Yokeable = icu::calendar::provider::WeekDataV1 { first_weekday: icu::calendar::types::IsoWeekday::Sunday, min_week_days: 4u8 };
|
||||
static VALUES: [&<icu::calendar::provider::WeekDataV1Marker as icu_provider::DataMarker>::Yokeable; 115usize] = [&UND, &UND_AD, &UND_AE, &UND_AE, &UND_AG, &UND_AD, &UND_AG, &UND_AD, &UND_AD, &UND_AG, &UND_AD, &UND_AD, &UND_AE, &UND_AG, &UND_AG, &UND_AG, &UND_AG, &UND_AG, &UND_AG, &UND_AD, &UND_AG, &UND_AD, &UND_AD, &UND_AE, &UND_AD, &UND_AG, &UND_AG, &UND_AE, &UND_AD, &UND_AE, &UND_AD, &UND_AG, &UND_AD, &UND_AD, &UND_AD, &UND_AD, &UND_AD, &UND_AD, &UND_AD, &UND_AD, &UND_AD, &UND_AD, &UND_AG, &UND_AG, &UND_AG, &UND_AG, &UND_AD, &UND_AG, &UND_AD, &UND_AG, &UND_AD, &UND_AG, &UND_AE, &UND_AE, &UND_AD, &UND_AD, &UND_AD, &UND_AG, &UND_AE, &UND_AG, &UND_AG, &UND_AG, &UND_AG, &UND_AE, &UND_AG, &UND_AD, &UND_AD, &UND_AD, &UND_AE, &UND_AD, &UND_AG, &UND_AG, &UND_AG, &UND_AD, &UND_AG, &UND_MV, &UND_AG, &UND_AG, &UND_AG, &UND_AD, &UND_AD, &UND_AG, &UND_AE, &UND_AG, &UND_AG, &UND_AG, &UND_AG, &UND_AD, &UND_AG, &UND_PT, &UND_AG, &UND_AE, &UND_AD, &UND_AD, &UND_AG, &UND_AE, &UND_AD, &UND_AG, &UND_AD, &UND_AD, &UND_AD, &UND_AG, &UND_AE, &UND_AG, &UND_AG, &UND_AG, &UND_AG, &UND_AG, &UND_AD, &UND_AG, &UND_AG, &UND_AG, &UND_AG, &UND_AG, &UND_AG];
|
||||
static KEYS: [&str; 115usize] = ["und", "und-AD", "und-AE", "und-AF", "und-AG", "und-AN", "und-AS", "und-AT", "und-AX", "und-BD", "und-BE", "und-BG", "und-BH", "und-BR", "und-BS", "und-BT", "und-BW", "und-BZ", "und-CA", "und-CH", "und-CO", "und-CZ", "und-DE", "und-DJ", "und-DK", "und-DM", "und-DO", "und-DZ", "und-EE", "und-EG", "und-ES", "und-ET", "und-FI", "und-FJ", "und-FO", "und-FR", "und-GB", "und-GF", "und-GG", "und-GI", "und-GP", "und-GR", "und-GT", "und-GU", "und-HK", "und-HN", "und-HU", "und-ID", "und-IE", "und-IL", "und-IM", "und-IN", "und-IQ", "und-IR", "und-IS", "und-IT", "und-JE", "und-JM", "und-JO", "und-JP", "und-KE", "und-KH", "und-KR", "und-KW", "und-LA", "und-LI", "und-LT", "und-LU", "und-LY", "und-MC", "und-MH", "und-MM", "und-MO", "und-MQ", "und-MT", "und-MV", "und-MX", "und-MZ", "und-NI", "und-NL", "und-NO", "und-NP", "und-OM", "und-PA", "und-PE", "und-PH", "und-PK", "und-PL", "und-PR", "und-PT", "und-PY", "und-QA", "und-RE", "und-RU", "und-SA", "und-SD", "und-SE", "und-SG", "und-SJ", "und-SK", "und-SM", "und-SV", "und-SY", "und-TH", "und-TT", "und-TW", "und-UM", "und-US", "und-VA", "und-VE", "und-VI", "und-WS", "und-YE", "und-ZA", "und-ZW"];
|
||||
let mut metadata = icu_provider::DataResponseMetadata::default();
|
||||
let payload = if let Ok(payload) = KEYS.binary_search_by(|k| req.locale.strict_cmp(k.as_bytes()).reverse()).map(|i| *unsafe { VALUES.get_unchecked(i) }) {
|
||||
payload
|
||||
} else {
|
||||
const FALLBACKER: icu::locid_transform::fallback::LocaleFallbackerWithConfig<'static> = icu::locid_transform::fallback::LocaleFallbacker::new().for_config(<icu::calendar::provider::WeekDataV1Marker as icu_provider::KeyedDataMarker>::KEY.fallback_config());
|
||||
let mut fallback_iterator = FALLBACKER.fallback_for(req.locale.clone());
|
||||
loop {
|
||||
if let Ok(payload) = KEYS.binary_search_by(|k| fallback_iterator.get().strict_cmp(k.as_bytes()).reverse()).map(|i| *unsafe { VALUES.get_unchecked(i) }) {
|
||||
metadata.locale = Some(fallback_iterator.take());
|
||||
break payload;
|
||||
}
|
||||
fallback_iterator.step();
|
||||
}
|
||||
};
|
||||
Ok(icu_provider::DataResponse { payload: Some(icu_provider::DataPayload::from_static_ref(payload)), metadata })
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
// This file is part of ICU4X. For terms of use, please see the file
|
||||
// called LICENSE at the top level of the ICU4X source tree
|
||||
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
|
||||
|
||||
//! Data for the icu_calendar crate
|
||||
|
||||
#![no_std]
|
||||
|
||||
#[cfg(icu4x_custom_data)]
|
||||
include!(concat!(core::env!("ICU4X_DATA_DIR"), "/macros.rs"));
|
||||
#[cfg(not(icu4x_custom_data))]
|
||||
include!("../data/macros.rs");
|
Загрузка…
Ссылка в новой задаче