LibJS+LibUnicode: Use icu4x for Temporal calendar operations
Replace the icu4c-based calendar implementation with one built on the icu4x Rust crate (icu_calendar). The icu4c API does not expose the píngqì month-assignment algorithm used by the Chinese and Dangi lunisolar calendars. Our old code had to approximate this by walking months via epoch millisecond arithmetic and manually tracking leap month positions, which produced incorrect month codes and ordinal month numbers for certain years. The icu4x calendar crate handles píngqì natively. With this patch, which is almost a 1-to-1 mapping of ICU invocations, we pass 100% of all Temporal test262 tests. The end goal might be to use icu4x for all of our ICU needs. But it does not yet provide the APIs needed for all ECMA-402 prototypes.
This commit is contained in:
parent
8179efb38e
commit
86c8a57794
18 changed files with 1379 additions and 612 deletions
324
Cargo.lock
generated
324
Cargo.lock
generated
|
|
@ -12,6 +12,130 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
|||
name = "bytecode_def"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "calendrical_calculations"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a0b39595c6ee54a8d0900204ba4c401d0ab4eb45adaf07178e8d017541529e7"
|
||||
dependencies = [
|
||||
"core_maths",
|
||||
"displaydoc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core_maths"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30"
|
||||
dependencies = [
|
||||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_calendar"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6f0e52e009b6b16ba9c0693578796f2dd4aaa59a7f8f920423706714a89ac4e"
|
||||
dependencies = [
|
||||
"calendrical_calculations",
|
||||
"displaydoc",
|
||||
"icu_calendar_data",
|
||||
"icu_locale",
|
||||
"icu_locale_core",
|
||||
"icu_provider",
|
||||
"ixdtf",
|
||||
"tinystr",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_calendar_data"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "527f04223b17edfe0bd43baf14a0cb1b017830db65f3950dc00224860a9a446d"
|
||||
|
||||
[[package]]
|
||||
name = "icu_collections"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"potential_utf",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_locale"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "532b11722e350ab6bf916ba6eb0efe3ee54b932666afec989465f9243fe6dd60"
|
||||
dependencies = [
|
||||
"icu_collections",
|
||||
"icu_locale_core",
|
||||
"icu_locale_data",
|
||||
"icu_provider",
|
||||
"potential_utf",
|
||||
"tinystr",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_locale_core"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"litemap",
|
||||
"serde",
|
||||
"tinystr",
|
||||
"writeable",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_locale_data"
|
||||
version = "2.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c5f1d16b4c3a2642d3a719f18f6b06070ab0aef246a6418130c955ae08aa831"
|
||||
|
||||
[[package]]
|
||||
name = "icu_provider"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"icu_locale_core",
|
||||
"serde",
|
||||
"stable_deref_trait",
|
||||
"writeable",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerotrie",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ixdtf"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84de9d95a6d2547d9b77ee3f25fa0ee32e3c3a6484d47a55adebc0439c077992"
|
||||
|
||||
[[package]]
|
||||
name = "libjs_rust"
|
||||
version = "0.1.0"
|
||||
|
|
@ -23,6 +147,26 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
|
||||
|
||||
[[package]]
|
||||
name = "libunicode_rust"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"calendrical_calculations",
|
||||
"icu_calendar",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "litemap"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.4.6"
|
||||
|
|
@ -51,8 +195,188 @@ dependencies = [
|
|||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "potential_utf"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"writeable",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "synstructure"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinystr"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"serde_core",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
||||
|
||||
[[package]]
|
||||
name = "writeable"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954"
|
||||
dependencies = [
|
||||
"stable_deref_trait",
|
||||
"yoke-derive",
|
||||
"zerofrom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yoke-derive"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
|
||||
dependencies = [
|
||||
"zerofrom-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom-derive"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerotrie"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerovec"
|
||||
version = "0.11.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerovec-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerovec-derive"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"Libraries/LibJS/Rust",
|
||||
"Libraries/LibUnicode/Rust",
|
||||
]
|
||||
exclude = [
|
||||
"Libraries/LibJS/AsmIntGen",
|
||||
|
|
|
|||
|
|
@ -307,7 +307,7 @@ endif()
|
|||
target_link_libraries(LibJS PUBLIC JSClangPlugin)
|
||||
|
||||
if (ENABLE_RUST)
|
||||
corrosion_import_crate(MANIFEST_PATH Rust/Cargo.toml)
|
||||
corrosion_import_crate(MANIFEST_PATH Rust/Cargo.toml CRATES libjs_rust)
|
||||
target_link_libraries(LibJS PRIVATE libjs_rust)
|
||||
target_compile_definitions(LibJS PRIVATE ENABLE_RUST)
|
||||
install(TARGETS libjs_rust EXPORT LagomTargets
|
||||
|
|
|
|||
|
|
@ -1790,36 +1790,12 @@ bool year_contains_month_code(String const& calendar, i32 arithmetic_year, Strin
|
|||
VERIFY(is_valid_month_code_for_calendar(calendar, month_code));
|
||||
|
||||
// 2. If ! ParseMonthCode(monthCode).[[IsLeap]] is false, return true.
|
||||
auto [month_number, is_leap_month] = parse_month_code(month_code);
|
||||
if (!is_leap_month)
|
||||
if (!parse_month_code(month_code).is_leap_month)
|
||||
return true;
|
||||
|
||||
// 3. Return whether the leap month indicated by monthCode exists in the year arithmeticYear in calendar, using
|
||||
// calendar-dependent behaviour.
|
||||
if (calendar.is_one_of("chinese"sv, "dangi"sv)) {
|
||||
auto months_in_year = calendar_months_in_year(calendar, arithmetic_year);
|
||||
if (months_in_year <= 12)
|
||||
return false;
|
||||
|
||||
// Check each ordinal month to see if it matches the leap month code.
|
||||
for (u8 month = 1; month <= months_in_year; ++month) {
|
||||
auto info = Unicode::chinese_ordinal_month_code(calendar, arithmetic_year, month);
|
||||
if (info.has_value() && info->is_leap_month && info->month_number == month_number)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (calendar == "hebrew"sv) {
|
||||
if (month_number != Unicode::HEBREW_ADAR_I_MONTH_NUMBER)
|
||||
return false;
|
||||
|
||||
auto months_in_year = calendar_months_in_year(calendar, arithmetic_year);
|
||||
return months_in_year == 13;
|
||||
}
|
||||
|
||||
return false;
|
||||
return Unicode::calendar_year_contains_month_code(calendar, arithmetic_year, month_code);
|
||||
}
|
||||
|
||||
// 4.1.6 ConstrainMonthCode ( calendar, arithmeticYear, monthCode, overflow ), https://tc39.es/proposal-intl-era-monthcode/#sec-temporal-constrainmonthcode
|
||||
|
|
|
|||
|
|
@ -22,7 +22,10 @@ namespace JS::Temporal {
|
|||
extern String ISO8601_CALENDAR;
|
||||
|
||||
// 12.2 Month Codes, https://tc39.es/proposal-temporal/#sec-temporal-month-codes
|
||||
using MonthCode = Unicode::MonthCode;
|
||||
struct MonthCode {
|
||||
u8 month_number { 0 };
|
||||
bool is_leap_month { false };
|
||||
};
|
||||
|
||||
// 12.3.1 Calendar Date Records, https://tc39.es/proposal-temporal/#sec-temporal-calendar-date-records
|
||||
using CalendarDate = Unicode::CalendarDate;
|
||||
|
|
|
|||
|
|
@ -28,6 +28,12 @@ ladybird_lib(LibUnicode unicode)
|
|||
find_package(ICU 78.2 EXACT REQUIRED COMPONENTS data i18n uc)
|
||||
target_link_libraries(LibUnicode PRIVATE ICU::i18n ICU::uc ICU::data)
|
||||
|
||||
corrosion_import_crate(MANIFEST_PATH Rust/Cargo.toml CRATES libunicode_rust)
|
||||
target_link_libraries(LibUnicode PRIVATE libunicode_rust)
|
||||
install(TARGETS libunicode_rust EXPORT LagomTargets
|
||||
ARCHIVE COMPONENT Lagom_Development
|
||||
)
|
||||
|
||||
# FIXME: Add support for building LibGfx in sanitize
|
||||
# lld-link: error: /failifmismatch: mismatch detected for 'annotate_string':
|
||||
# >>> lagom-unicode.lib(TimeZone.cpp.obj) has value 1
|
||||
|
|
|
|||
|
|
@ -4,534 +4,107 @@
|
|||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/HashMap.h>
|
||||
#include <LibUnicode/Calendar.h>
|
||||
#include <LibUnicode/ICU.h>
|
||||
|
||||
#include <unicode/calendar.h>
|
||||
struct FfiISODate {
|
||||
i32 year;
|
||||
u8 month;
|
||||
u8 day;
|
||||
};
|
||||
|
||||
struct FfiOptionalISODate {
|
||||
FfiISODate iso_date;
|
||||
bool has_value;
|
||||
};
|
||||
|
||||
struct FfiCalendarDate {
|
||||
i32 year;
|
||||
u8 month;
|
||||
u8 month_code[5];
|
||||
u8 month_code_length;
|
||||
u8 day;
|
||||
u8 day_of_week;
|
||||
u16 day_of_year;
|
||||
u8 days_in_week;
|
||||
u8 days_in_month;
|
||||
u16 days_in_year;
|
||||
u8 months_in_year;
|
||||
bool in_leap_year;
|
||||
};
|
||||
|
||||
extern "C" {
|
||||
|
||||
FfiCalendarDate icu_iso_date_to_calendar_date(u8 const* calendar, size_t calendar_length, i32 iso_year, u8 iso_month, u8 iso_day);
|
||||
FfiOptionalISODate icu_calendar_date_to_iso_date(u8 const* calendar, size_t calendar_length, i32 arithmetic_year, u8 ordinal_month, u8 day);
|
||||
FfiOptionalISODate icu_calendar_month_code_to_iso_date(u8 const* calendar, size_t calendar_length, i32 iso_year, u8 const* month_code, size_t month_code_length, u8 day);
|
||||
|
||||
u8 icu_calendar_months_in_year(u8 const* calendar, size_t calendar_length, i32 arithmetic_year);
|
||||
u8 icu_calendar_days_in_month(u8 const* calendar, size_t calendar_length, i32 arithmetic_year, u8 ordinal_month);
|
||||
u8 icu_calendar_max_days_in_month_code(u8 const* calendar, size_t calendar_length, u8 const* month_code, size_t month_code_length);
|
||||
bool icu_year_contains_month_code(u8 const* calendar, size_t calendar_length, i32 arithmetic_year, u8 const* month_code, size_t month_code_length);
|
||||
|
||||
} // extern "C"
|
||||
|
||||
namespace Unicode {
|
||||
|
||||
static icu::Calendar& proleptic_gregorian_calendar()
|
||||
{
|
||||
static auto calendar_data = CalendarData::for_calendar("gregory"_string);
|
||||
VERIFY(calendar_data.has_value());
|
||||
|
||||
return calendar_data->calendar();
|
||||
}
|
||||
|
||||
static void set_icu_calendar_to_iso_date(icu::Calendar& calendar, ISODate iso_date)
|
||||
{
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
|
||||
auto& gregorian = proleptic_gregorian_calendar();
|
||||
gregorian.clear();
|
||||
gregorian.set(UCAL_EXTENDED_YEAR, iso_date.year);
|
||||
gregorian.set(UCAL_MONTH, iso_date.month - 1);
|
||||
gregorian.set(UCAL_DATE, iso_date.day);
|
||||
|
||||
auto epoch_ms = ICU_MUST(gregorian.getTime(status));
|
||||
ICU_MUST_VOID(calendar.setTime(epoch_ms, status));
|
||||
}
|
||||
|
||||
struct ChineseMonthEntry {
|
||||
u8 month { 0 };
|
||||
u8 days_in_month { 0 };
|
||||
i32 is_leap_month { 0 };
|
||||
};
|
||||
|
||||
struct ChineseYearLayout {
|
||||
u8 month_count { 0 };
|
||||
Array<ChineseMonthEntry, 14> months; // This is a 1-based array to map to ICU ordinal months.
|
||||
};
|
||||
|
||||
struct ChineseYearKey {
|
||||
String calendar;
|
||||
i32 extended_year { 0 };
|
||||
|
||||
bool operator==(ChineseYearKey const& other) const = default;
|
||||
};
|
||||
|
||||
struct ChineseExtendedYearKey {
|
||||
String calendar;
|
||||
i32 arithmetic_year { 0 };
|
||||
|
||||
bool operator==(ChineseExtendedYearKey const&) const = default;
|
||||
};
|
||||
|
||||
static HashMap<ChineseYearKey, ChineseYearLayout> s_chinese_year_layout_cache;
|
||||
static HashMap<ChineseExtendedYearKey, i32> s_chinese_extended_year_cache;
|
||||
|
||||
static i32 chinese_arithmetic_year_to_extended_year(String const& calendar_name, icu::Calendar& calendar, i32 arithmetic_year)
|
||||
{
|
||||
return s_chinese_extended_year_cache.ensure({ calendar_name, arithmetic_year }, [&]() {
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
|
||||
ISODate approximate_iso_date { arithmetic_year, 6, 15 };
|
||||
set_icu_calendar_to_iso_date(calendar, approximate_iso_date);
|
||||
|
||||
return ICU_MUST(calendar.get(UCAL_EXTENDED_YEAR, status));
|
||||
});
|
||||
}
|
||||
|
||||
static ChineseYearLayout const& chinese_year_layout(String const& calendar_name, icu::Calendar& calendar, i32 extended_year)
|
||||
{
|
||||
return s_chinese_year_layout_cache.ensure({ calendar_name, extended_year }, [&]() {
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
ChineseYearLayout layout;
|
||||
|
||||
auto cloned = adopt_own(*calendar.clone());
|
||||
cloned->set(UCAL_MONTH, 0);
|
||||
cloned->set(UCAL_IS_LEAP_MONTH, 0);
|
||||
cloned->set(UCAL_DATE, 1);
|
||||
|
||||
auto save_month_layout = [&]() {
|
||||
layout.months[++layout.month_count] = ChineseMonthEntry {
|
||||
.month = static_cast<u8>(ICU_MUST(cloned->get(UCAL_MONTH, status))),
|
||||
.days_in_month = static_cast<u8>(ICU_MUST(cloned->getActualMaximum(UCAL_DATE, status))),
|
||||
.is_leap_month = ICU_MUST(cloned->get(UCAL_IS_LEAP_MONTH, status)),
|
||||
};
|
||||
};
|
||||
|
||||
auto start_year = ICU_MUST(cloned->get(UCAL_EXTENDED_YEAR, status));
|
||||
save_month_layout();
|
||||
|
||||
for (int i = 0; i < 13; ++i) {
|
||||
cloned->add(UCAL_MONTH, 1, status);
|
||||
if (icu_failure(status))
|
||||
break;
|
||||
|
||||
auto year = cloned->get(UCAL_EXTENDED_YEAR, status);
|
||||
if (icu_failure(status) || year != start_year)
|
||||
break;
|
||||
|
||||
save_month_layout();
|
||||
}
|
||||
|
||||
return layout;
|
||||
});
|
||||
}
|
||||
|
||||
static ChineseYearLayout const& chinese_year_layout(String const& calendar_name, icu::Calendar& calendar)
|
||||
{
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
|
||||
auto extended_year = ICU_MUST(calendar.get(UCAL_EXTENDED_YEAR, status));
|
||||
return chinese_year_layout(calendar_name, calendar, extended_year);
|
||||
}
|
||||
|
||||
static bool is_hebrew_leap_year(icu::Calendar& calendar)
|
||||
{
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
|
||||
auto cloned = adopt_own(*calendar.clone());
|
||||
cloned->set(UCAL_MONTH, HEBREW_ADAR_I_MONTH_NUMBER);
|
||||
cloned->set(UCAL_DATE, 1);
|
||||
|
||||
auto month = ICU_MUST(cloned->get(UCAL_MONTH, status));
|
||||
return month == HEBREW_ADAR_I_MONTH_NUMBER;
|
||||
}
|
||||
|
||||
static i32 hebrew_ordinal_month_to_icu_month(icu::Calendar& calendar, u8 ordinal_month)
|
||||
{
|
||||
if (is_hebrew_leap_year(calendar))
|
||||
return ordinal_month - 1;
|
||||
return ordinal_month <= HEBREW_ADAR_I_MONTH_NUMBER ? ordinal_month - 1 : ordinal_month;
|
||||
}
|
||||
|
||||
static u8 compute_ordinal_month(icu::Calendar& calendar, String const& calendar_name)
|
||||
{
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
|
||||
auto month = static_cast<u8>(ICU_MUST(calendar.get(UCAL_MONTH, status)));
|
||||
|
||||
if (calendar_name.is_one_of("chinese"sv, "dangi"sv)) {
|
||||
auto const& layout = chinese_year_layout(calendar_name, calendar);
|
||||
auto is_leap_month = ICU_MUST(calendar.get(UCAL_IS_LEAP_MONTH, status));
|
||||
|
||||
for (u8 ordinal = 1; ordinal <= layout.month_count; ++ordinal) {
|
||||
if (layout.months[ordinal].month == month && layout.months[ordinal].is_leap_month == is_leap_month)
|
||||
return ordinal;
|
||||
}
|
||||
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
if (calendar_name == "hebrew"sv) {
|
||||
if (is_hebrew_leap_year(calendar))
|
||||
return month + 1;
|
||||
return month <= HEBREW_ADAR_I_MONTH_NUMBER ? month + 1 : month;
|
||||
}
|
||||
|
||||
return month + 1;
|
||||
}
|
||||
|
||||
struct ICUMonthCode {
|
||||
i32 month { 0 };
|
||||
i32 is_leap_month { 0 };
|
||||
};
|
||||
static ICUMonthCode month_code_to_icu_fields(StringView month_code, StringView calendar)
|
||||
{
|
||||
auto month_number = month_code.substring_view(1, 2).to_number<u8>().value();
|
||||
bool is_leap_month = month_code.length() == 4 && month_code[3] == 'L';
|
||||
|
||||
if (calendar == "hebrew"sv) {
|
||||
if (is_leap_month || month_number > HEBREW_ADAR_I_MONTH_NUMBER)
|
||||
return { month_number, is_leap_month };
|
||||
return { month_number - 1, 0 };
|
||||
}
|
||||
|
||||
return { month_number - 1, is_leap_month };
|
||||
}
|
||||
|
||||
static String compute_month_code(icu::Calendar& calendar, String const& calendar_name)
|
||||
{
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
|
||||
auto month = static_cast<u8>(ICU_MUST(calendar.get(UCAL_MONTH, status)));
|
||||
auto is_leap_month = ICU_MUST(calendar.get(UCAL_IS_LEAP_MONTH, status));
|
||||
|
||||
if (is_leap_month != 0) {
|
||||
if (calendar_name == "hebrew"sv)
|
||||
return String::from_utf8_without_validation(HEBREW_ADAR_I_MONTH_CODE.bytes());
|
||||
return MUST(String::formatted("M{:02}L", month + 1));
|
||||
}
|
||||
|
||||
if (calendar_name == "hebrew"sv) {
|
||||
if (month < HEBREW_ADAR_I_MONTH_NUMBER)
|
||||
return MUST(String::formatted("M{:02}", month + 1));
|
||||
if (month == HEBREW_ADAR_I_MONTH_NUMBER && is_hebrew_leap_year(calendar))
|
||||
return String::from_utf8_without_validation(HEBREW_ADAR_I_MONTH_CODE.bytes());
|
||||
return MUST(String::formatted("M{:02}", month));
|
||||
}
|
||||
|
||||
return MUST(String::formatted("M{:02}", month + 1));
|
||||
}
|
||||
|
||||
static i32 arithmetic_year_to_extended_year(String const& calendar, i32 arithmetic_year)
|
||||
{
|
||||
if (calendar == "buddhist"sv)
|
||||
return arithmetic_year + BUDDHIST_EPOCH_ISO_YEAR;
|
||||
if (calendar == "roc"sv)
|
||||
return arithmetic_year + ROC_EPOCH_ISO_YEAR;
|
||||
return arithmetic_year;
|
||||
}
|
||||
|
||||
static i32 extended_year_to_arithmetic_year(String const& calendar, i32 extended_year)
|
||||
{
|
||||
if (calendar == "buddhist"sv)
|
||||
return extended_year - BUDDHIST_EPOCH_ISO_YEAR;
|
||||
if (calendar == "roc"sv)
|
||||
return extended_year - ROC_EPOCH_ISO_YEAR;
|
||||
return extended_year;
|
||||
}
|
||||
|
||||
static void set_calendar_to_arithmetic_year(icu::Calendar& calendar, String const& calendar_name, i32 arithmetic_year)
|
||||
{
|
||||
auto extended_year = calendar_name.is_one_of("chinese"sv, "dangi"sv)
|
||||
? chinese_arithmetic_year_to_extended_year(calendar_name, calendar, arithmetic_year)
|
||||
: arithmetic_year_to_extended_year(calendar_name, arithmetic_year);
|
||||
|
||||
calendar.set(UCAL_EXTENDED_YEAR, extended_year);
|
||||
}
|
||||
|
||||
CalendarDate iso_date_to_calendar_date(String const& calendar, ISODate iso_date)
|
||||
{
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
|
||||
auto calendar_data = CalendarData::for_calendar(calendar);
|
||||
if (!calendar_data.has_value())
|
||||
return {};
|
||||
|
||||
auto& icu_calendar = calendar_data->calendar();
|
||||
set_icu_calendar_to_iso_date(icu_calendar, iso_date);
|
||||
|
||||
i32 arithmetic_year = 0;
|
||||
if (calendar.is_one_of("chinese"sv, "dangi"sv)) {
|
||||
// For Chinese/Dangi calendars, the arithmetic year is the ISO year of the start of the current calendar year.
|
||||
auto year_start = adopt_own(*icu_calendar.clone());
|
||||
year_start->set(UCAL_MONTH, 0);
|
||||
year_start->set(UCAL_DATE, 1);
|
||||
|
||||
auto epoch_ms = ICU_MUST(year_start->getTime(status));
|
||||
|
||||
auto& gregorian = proleptic_gregorian_calendar();
|
||||
ICU_MUST_VOID(gregorian.setTime(epoch_ms, status));
|
||||
|
||||
arithmetic_year = ICU_MUST(gregorian.get(UCAL_EXTENDED_YEAR, status));
|
||||
} else {
|
||||
auto extended_year = ICU_MUST(icu_calendar.get(UCAL_EXTENDED_YEAR, status));
|
||||
arithmetic_year = extended_year_to_arithmetic_year(calendar, extended_year);
|
||||
}
|
||||
|
||||
auto ordinal_month = compute_ordinal_month(icu_calendar, calendar);
|
||||
auto month_code = compute_month_code(icu_calendar, calendar);
|
||||
auto day = static_cast<u8>(ICU_MUST(icu_calendar.get(UCAL_DATE, status)));
|
||||
|
||||
// In ICU, the days of the week begin with Sunday=1. Temporal expects Monday=1 and Sunday=7 instead.
|
||||
auto day_of_week = static_cast<u8>(ICU_MUST(icu_calendar.get(UCAL_DAY_OF_WEEK, status)));
|
||||
day_of_week = day_of_week == UCAL_SUNDAY ? 7 : day_of_week - 1;
|
||||
|
||||
auto day_of_year = static_cast<u16>(ICU_MUST(icu_calendar.get(UCAL_DAY_OF_YEAR, status)));
|
||||
auto days_in_month = static_cast<u8>(ICU_MUST(icu_calendar.getActualMaximum(UCAL_DATE, status)));
|
||||
auto days_in_year = static_cast<u16>(ICU_MUST(icu_calendar.getActualMaximum(UCAL_DAY_OF_YEAR, status)));
|
||||
auto months_in_year = calendar_months_in_year(calendar, arithmetic_year);
|
||||
auto in_leap_year = ICU_MUST(icu_calendar.inTemporalLeapYear(status));
|
||||
auto result = icu_iso_date_to_calendar_date(calendar.bytes().data(), calendar.bytes().size(), iso_date.year, iso_date.month, iso_date.day);
|
||||
|
||||
return CalendarDate {
|
||||
.era = {},
|
||||
.era_year = {},
|
||||
.year = arithmetic_year,
|
||||
.month = ordinal_month,
|
||||
.month_code = move(month_code),
|
||||
.day = day,
|
||||
.day_of_week = day_of_week,
|
||||
.day_of_year = day_of_year,
|
||||
.year = result.year,
|
||||
.month = result.month,
|
||||
.month_code = String::from_utf8_without_validation({ result.month_code, result.month_code_length }),
|
||||
.day = result.day,
|
||||
.day_of_week = result.day_of_week,
|
||||
.day_of_year = result.day_of_year,
|
||||
.week_of_year = {},
|
||||
.days_in_week = 7,
|
||||
.days_in_month = days_in_month,
|
||||
.days_in_year = days_in_year,
|
||||
.months_in_year = months_in_year,
|
||||
.in_leap_year = in_leap_year,
|
||||
.days_in_week = result.days_in_week,
|
||||
.days_in_month = result.days_in_month,
|
||||
.days_in_year = result.days_in_year,
|
||||
.months_in_year = result.months_in_year,
|
||||
.in_leap_year = result.in_leap_year,
|
||||
};
|
||||
}
|
||||
|
||||
Optional<ISODate> calendar_date_to_iso_date(String const& calendar, i32 arithmetic_year, u8 ordinal_month, u8 day)
|
||||
Optional<ISODate> calendar_date_to_iso_date(String const& calendar, i32 year, u8 month, u8 day)
|
||||
{
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
|
||||
auto calendar_data = CalendarData::for_calendar(calendar);
|
||||
if (!calendar_data.has_value())
|
||||
auto result = icu_calendar_date_to_iso_date(calendar.bytes().data(), calendar.bytes().size(), year, month, day);
|
||||
if (!result.has_value)
|
||||
return {};
|
||||
|
||||
auto& icu_calendar = calendar_data->calendar();
|
||||
icu_calendar.clear();
|
||||
|
||||
set_calendar_to_arithmetic_year(icu_calendar, calendar, arithmetic_year);
|
||||
|
||||
if (calendar.is_one_of("chinese"sv, "dangi"sv)) {
|
||||
auto const& layout = chinese_year_layout(calendar, icu_calendar);
|
||||
|
||||
if (ordinal_month >= 1 && ordinal_month <= layout.month_count) {
|
||||
icu_calendar.set(UCAL_MONTH, layout.months[ordinal_month].month);
|
||||
icu_calendar.set(UCAL_IS_LEAP_MONTH, layout.months[ordinal_month].is_leap_month);
|
||||
}
|
||||
|
||||
icu_calendar.set(UCAL_DATE, day);
|
||||
} else if (calendar == "hebrew"sv) {
|
||||
auto icu_month = hebrew_ordinal_month_to_icu_month(icu_calendar, ordinal_month);
|
||||
icu_calendar.set(UCAL_MONTH, icu_month);
|
||||
icu_calendar.set(UCAL_DATE, day);
|
||||
} else {
|
||||
icu_calendar.set(UCAL_MONTH, ordinal_month - 1);
|
||||
icu_calendar.set(UCAL_DATE, day);
|
||||
}
|
||||
|
||||
icu_calendar.set(UCAL_HOUR_OF_DAY, 12);
|
||||
|
||||
auto epoch_ms = ICU_MUST(icu_calendar.getTime(status));
|
||||
|
||||
auto& gregorian = proleptic_gregorian_calendar();
|
||||
ICU_MUST_VOID(gregorian.setTime(epoch_ms, status));
|
||||
|
||||
auto iso_year = ICU_MUST(gregorian.get(UCAL_EXTENDED_YEAR, status));
|
||||
auto iso_month = ICU_MUST(gregorian.get(UCAL_MONTH, status)) + 1;
|
||||
auto iso_day = ICU_MUST(gregorian.get(UCAL_DATE, status));
|
||||
|
||||
return ISODate { iso_year, static_cast<u8>(iso_month), static_cast<u8>(iso_day) };
|
||||
return ISODate { result.iso_date.year, result.iso_date.month, result.iso_date.day };
|
||||
}
|
||||
|
||||
Optional<ISODate> calendar_month_code_to_iso_date(String const& calendar, i32 year, StringView month_code, u8 day)
|
||||
{
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
|
||||
auto calendar_data = CalendarData::for_calendar(calendar);
|
||||
if (!calendar_data.has_value())
|
||||
auto result = icu_calendar_month_code_to_iso_date(calendar.bytes().data(), calendar.bytes().size(), year, month_code.bytes().data(), month_code.length(), day);
|
||||
if (!result.has_value)
|
||||
return {};
|
||||
|
||||
auto& icu_calendar = calendar_data->calendar();
|
||||
auto& gregorian = proleptic_gregorian_calendar();
|
||||
|
||||
set_icu_calendar_to_iso_date(icu_calendar, { year, 1, 1 });
|
||||
auto start_extended_year = ICU_MUST(icu_calendar.get(UCAL_EXTENDED_YEAR, status));
|
||||
|
||||
set_icu_calendar_to_iso_date(icu_calendar, { year, 12, 31 });
|
||||
auto end_extended_year = ICU_MUST(icu_calendar.get(UCAL_EXTENDED_YEAR, status));
|
||||
|
||||
auto [icu_month, icu_is_leap_month] = month_code_to_icu_fields(month_code, calendar);
|
||||
auto is_chinese_or_dangi = calendar.is_one_of("chinese"sv, "dangi"sv);
|
||||
|
||||
Optional<ISODate> best_iso_date;
|
||||
|
||||
for (auto extended_year = start_extended_year; extended_year <= end_extended_year; ++extended_year) {
|
||||
icu_calendar.clear();
|
||||
icu_calendar.set(UCAL_EXTENDED_YEAR, extended_year);
|
||||
icu_calendar.set(UCAL_MONTH, icu_month);
|
||||
if (is_chinese_or_dangi)
|
||||
icu_calendar.set(UCAL_IS_LEAP_MONTH, icu_is_leap_month);
|
||||
icu_calendar.set(UCAL_DATE, day);
|
||||
icu_calendar.set(UCAL_HOUR_OF_DAY, 12);
|
||||
|
||||
auto epoch_ms = ICU_MUST(icu_calendar.getTime(status));
|
||||
ICU_MUST_VOID(gregorian.setTime(epoch_ms, status));
|
||||
|
||||
auto resolved_month = ICU_MUST(icu_calendar.get(UCAL_MONTH, status));
|
||||
if (resolved_month != icu_month)
|
||||
continue;
|
||||
|
||||
if (is_chinese_or_dangi) {
|
||||
auto resolved_is_leap_month = ICU_MUST(icu_calendar.get(UCAL_IS_LEAP_MONTH, status));
|
||||
if (resolved_is_leap_month != icu_is_leap_month)
|
||||
continue;
|
||||
}
|
||||
|
||||
auto resolved_day = ICU_MUST(icu_calendar.get(UCAL_DATE, status));
|
||||
if (resolved_day != day)
|
||||
continue;
|
||||
|
||||
auto iso_year = static_cast<i32>(ICU_MUST(gregorian.get(UCAL_EXTENDED_YEAR, status)));
|
||||
if (iso_year != year)
|
||||
continue;
|
||||
|
||||
auto iso_month = static_cast<u8>(ICU_MUST(gregorian.get(UCAL_MONTH, status)) + 1);
|
||||
auto iso_day = static_cast<u8>(ICU_MUST(gregorian.get(UCAL_DATE, status)));
|
||||
|
||||
if (!best_iso_date.has_value() || iso_month > best_iso_date->month || (iso_month == best_iso_date->month && iso_day > best_iso_date->day))
|
||||
best_iso_date = ISODate { iso_year, iso_month, iso_day };
|
||||
}
|
||||
|
||||
return best_iso_date;
|
||||
return ISODate { result.iso_date.year, result.iso_date.month, result.iso_date.day };
|
||||
}
|
||||
|
||||
u8 calendar_months_in_year(String const& calendar, i32 arithmetic_year)
|
||||
{
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
|
||||
auto calendar_data = CalendarData::for_calendar(calendar);
|
||||
if (!calendar_data.has_value())
|
||||
return 12;
|
||||
|
||||
auto& icu_calendar = calendar_data->calendar();
|
||||
|
||||
if (calendar.is_one_of("chinese"sv, "dangi"sv)) {
|
||||
auto extended_year = chinese_arithmetic_year_to_extended_year(calendar, icu_calendar, arithmetic_year);
|
||||
return chinese_year_layout(calendar, icu_calendar, extended_year).month_count;
|
||||
}
|
||||
|
||||
icu_calendar.clear();
|
||||
set_calendar_to_arithmetic_year(icu_calendar, calendar, arithmetic_year);
|
||||
icu_calendar.set(UCAL_MONTH, 0);
|
||||
icu_calendar.set(UCAL_IS_LEAP_MONTH, 0);
|
||||
icu_calendar.set(UCAL_DATE, 1);
|
||||
|
||||
if (calendar == "hebrew"sv)
|
||||
return is_hebrew_leap_year(icu_calendar) ? 13 : 12;
|
||||
|
||||
return static_cast<u8>(ICU_MUST(icu_calendar.getActualMaximum(UCAL_MONTH, status)) + 1);
|
||||
return icu_calendar_months_in_year(calendar.bytes().data(), calendar.bytes().size(), arithmetic_year);
|
||||
}
|
||||
|
||||
u8 calendar_days_in_month(String const& calendar, i32 arithmetic_year, u8 ordinal_month)
|
||||
{
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
|
||||
auto calendar_data = CalendarData::for_calendar(calendar);
|
||||
if (!calendar_data.has_value())
|
||||
return 30;
|
||||
|
||||
auto& icu_calendar = calendar_data->calendar();
|
||||
|
||||
if (calendar.is_one_of("chinese"sv, "dangi"sv)) {
|
||||
auto extended_year = chinese_arithmetic_year_to_extended_year(calendar, icu_calendar, arithmetic_year);
|
||||
auto const& layout = chinese_year_layout(calendar, icu_calendar, extended_year);
|
||||
|
||||
if (ordinal_month >= 1 && ordinal_month <= layout.month_count)
|
||||
return layout.months[ordinal_month].days_in_month;
|
||||
|
||||
return 30;
|
||||
}
|
||||
|
||||
icu_calendar.clear();
|
||||
set_calendar_to_arithmetic_year(icu_calendar, calendar, arithmetic_year);
|
||||
|
||||
if (calendar == "hebrew"sv) {
|
||||
auto icu_month = hebrew_ordinal_month_to_icu_month(icu_calendar, ordinal_month);
|
||||
icu_calendar.set(UCAL_MONTH, icu_month);
|
||||
} else {
|
||||
icu_calendar.set(UCAL_MONTH, ordinal_month - 1);
|
||||
}
|
||||
|
||||
icu_calendar.set(UCAL_DATE, 1);
|
||||
|
||||
return static_cast<u8>(ICU_MUST(icu_calendar.getActualMaximum(UCAL_DATE, status)));
|
||||
return icu_calendar_days_in_month(calendar.bytes().data(), calendar.bytes().size(), arithmetic_year, ordinal_month);
|
||||
}
|
||||
|
||||
u8 calendar_max_days_in_month_code(String const& calendar, StringView month_code)
|
||||
{
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
|
||||
auto calendar_data = CalendarData::for_calendar(calendar);
|
||||
if (!calendar_data.has_value())
|
||||
return 30;
|
||||
|
||||
auto& icu_calendar = calendar_data->calendar();
|
||||
|
||||
set_icu_calendar_to_iso_date(icu_calendar, { 1970, 7, 1 });
|
||||
auto base_extended_year = ICU_MUST(icu_calendar.get(UCAL_EXTENDED_YEAR, status));
|
||||
|
||||
auto [icu_month, icu_is_leap] = month_code_to_icu_fields(month_code, calendar);
|
||||
u8 max_days_in_month = 0;
|
||||
|
||||
for (auto offset = -2; offset <= 2; ++offset) {
|
||||
icu_calendar.clear();
|
||||
icu_calendar.set(UCAL_EXTENDED_YEAR, base_extended_year + offset);
|
||||
icu_calendar.set(UCAL_MONTH, icu_month);
|
||||
icu_calendar.set(UCAL_DATE, 1);
|
||||
|
||||
if (icu_is_leap != 0 || (calendar == "hebrew"sv && month_code == HEBREW_ADAR_I_MONTH_CODE)) {
|
||||
auto resolved = ICU_MUST(icu_calendar.get(UCAL_MONTH, status));
|
||||
if (resolved != icu_month)
|
||||
continue;
|
||||
}
|
||||
|
||||
auto days_in_month = static_cast<u8>(ICU_MUST(icu_calendar.getActualMaximum(UCAL_DATE, status)));
|
||||
max_days_in_month = max(max_days_in_month, days_in_month);
|
||||
}
|
||||
|
||||
return max_days_in_month > 0 ? max_days_in_month : 30;
|
||||
return icu_calendar_max_days_in_month_code(calendar.bytes().data(), calendar.bytes().size(), month_code.bytes().data(), month_code.length());
|
||||
}
|
||||
|
||||
Optional<MonthCode> chinese_ordinal_month_code(String const& calendar, i32 arithmetic_year, u8 ordinal_month)
|
||||
bool calendar_year_contains_month_code(String const& calendar, i32 arithmetic_year, StringView month_code)
|
||||
{
|
||||
ASSERT(calendar.is_one_of("chinese"sv, "dangi"sv));
|
||||
|
||||
auto calendar_data = CalendarData::for_calendar(calendar);
|
||||
if (!calendar_data.has_value())
|
||||
return {};
|
||||
|
||||
auto& icu_calendar = calendar_data->calendar();
|
||||
|
||||
auto extended_year = chinese_arithmetic_year_to_extended_year(calendar, icu_calendar, arithmetic_year);
|
||||
auto const& layout = chinese_year_layout(calendar, icu_calendar, extended_year);
|
||||
|
||||
if (ordinal_month < 1 || ordinal_month > layout.month_count)
|
||||
return {};
|
||||
|
||||
auto const& entry = layout.months[ordinal_month];
|
||||
return MonthCode { static_cast<u8>(entry.month + 1), entry.is_leap_month != 0 };
|
||||
return icu_year_contains_month_code(calendar.bytes().data(), calendar.bytes().size(), arithmetic_year, month_code.bytes().data(), month_code.length());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace AK {
|
||||
|
||||
template<>
|
||||
struct Traits<Unicode::ChineseYearKey> : public DefaultTraits<Unicode::ChineseYearKey> {
|
||||
static unsigned hash(Unicode::ChineseYearKey const& key) { return pair_int_hash(key.calendar.hash(), key.extended_year); }
|
||||
};
|
||||
|
||||
template<>
|
||||
struct Traits<Unicode::ChineseExtendedYearKey> : public DefaultTraits<Unicode::ChineseExtendedYearKey> {
|
||||
static unsigned hash(Unicode::ChineseExtendedYearKey const& key) { return pair_int_hash(key.calendar.hash(), key.arithmetic_year); }
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,12 +19,6 @@ struct ISODate {
|
|||
u8 day { 0 };
|
||||
};
|
||||
|
||||
// 12.2 Month Codes, https://tc39.es/proposal-temporal/#sec-temporal-month-codes
|
||||
struct MonthCode {
|
||||
u8 month_number { 0 };
|
||||
bool is_leap_month { false };
|
||||
};
|
||||
|
||||
// 14.3 The Year-Week Record Specification Type, https://tc39.es/proposal-temporal/#sec-year-week-record-specification-type
|
||||
struct YearWeek {
|
||||
Optional<u8> week;
|
||||
|
|
@ -49,12 +43,6 @@ struct CalendarDate {
|
|||
bool in_leap_year { false };
|
||||
};
|
||||
|
||||
constexpr inline auto HEBREW_ADAR_I_MONTH_CODE = "M05L"sv;
|
||||
constexpr inline auto HEBREW_ADAR_I_MONTH_NUMBER = 5u;
|
||||
|
||||
constexpr inline auto BUDDHIST_EPOCH_ISO_YEAR = -543;
|
||||
constexpr inline auto ROC_EPOCH_ISO_YEAR = 1911;
|
||||
|
||||
CalendarDate iso_date_to_calendar_date(String const& calendar, ISODate);
|
||||
Optional<ISODate> calendar_date_to_iso_date(String const& calendar, i32 year, u8 month, u8 day);
|
||||
Optional<ISODate> calendar_month_code_to_iso_date(String const& calendar, i32 year, StringView month_code, u8 day);
|
||||
|
|
@ -62,7 +50,6 @@ Optional<ISODate> calendar_month_code_to_iso_date(String const& calendar, i32 ye
|
|||
u8 calendar_months_in_year(String const& calendar, i32 arithmetic_year);
|
||||
u8 calendar_days_in_month(String const& calendar, i32 arithmetic_year, u8 ordinal_month);
|
||||
u8 calendar_max_days_in_month_code(String const& calendar, StringView month_code);
|
||||
|
||||
Optional<MonthCode> chinese_ordinal_month_code(String const& calendar, i32 arithmetic_year, u8 ordinal_month);
|
||||
bool calendar_year_contains_month_code(String const& calendar, i32 arithmetic_year, StringView month_code);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -628,7 +628,15 @@ static void apply_time_zone_to_formatter(icu::SimpleDateFormat& formatter, icu::
|
|||
auto* calendar = icu::Calendar::createInstance(time_zone_data->time_zone(), locale, status);
|
||||
verify_icu_success(status);
|
||||
|
||||
CalendarData::adjust_time_range_for_proleptic_calendar(*calendar);
|
||||
if (auto* gregorian_calendar = as_if<icu::GregorianCalendar>(*calendar)) {
|
||||
// https://tc39.es/ecma262/#sec-time-values-and-time-range
|
||||
// A time value supports a slightly smaller range of -8,640,000,000,000,000 to 8,640,000,000,000,000 milliseconds.
|
||||
static constexpr double ECMA_262_MINIMUM_TIME = -8.64E15;
|
||||
|
||||
gregorian_calendar->setGregorianChange(ECMA_262_MINIMUM_TIME, status);
|
||||
verify_icu_success(status);
|
||||
}
|
||||
|
||||
formatter.adoptCalendar(calendar);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2024-2025, Tim Flynn <trflynn89@ladybird.org>
|
||||
* Copyright (c) 2024-2026, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
|
@ -10,7 +10,6 @@
|
|||
#include <LibUnicode/ICU.h>
|
||||
|
||||
#include <unicode/dtptngen.h>
|
||||
#include <unicode/gregocal.h>
|
||||
#include <unicode/locdspnm.h>
|
||||
#include <unicode/numsys.h>
|
||||
#include <unicode/tznames.h>
|
||||
|
|
@ -18,7 +17,6 @@
|
|||
namespace Unicode {
|
||||
|
||||
static HashMap<String, OwnPtr<LocaleData>> s_locale_cache;
|
||||
static HashMap<String, OwnPtr<CalendarData>> s_calendar_cache;
|
||||
static HashMap<String, OwnPtr<TimeZoneData>> s_time_zone_cache;
|
||||
|
||||
Optional<LocaleData&> LocaleData::for_locale(StringView locale)
|
||||
|
|
@ -116,53 +114,6 @@ icu::TimeZoneNames& LocaleData::time_zone_names()
|
|||
return *m_time_zone_names;
|
||||
}
|
||||
|
||||
Optional<CalendarData&> CalendarData::for_calendar(String const& calendar)
|
||||
{
|
||||
auto& calendar_data = s_calendar_cache.ensure(calendar, [&]() -> OwnPtr<CalendarData> {
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
|
||||
auto const* legacy_calendar = uloc_toLegacyType("calendar", ByteString(calendar).characters());
|
||||
if (!legacy_calendar)
|
||||
return nullptr;
|
||||
|
||||
auto locale_data = LocaleData::for_locale("und"sv);
|
||||
VERIFY(locale_data.has_value());
|
||||
|
||||
locale_data->locale().setKeywordValue("calendar", legacy_calendar, status);
|
||||
if (icu_failure(status))
|
||||
return nullptr;
|
||||
|
||||
auto icu_calendar = adopt_own_if_nonnull(icu::Calendar::createInstance(locale_data->locale(), status));
|
||||
if (icu_failure(status))
|
||||
return nullptr;
|
||||
|
||||
return adopt_own(*new CalendarData { icu_calendar.release_nonnull() });
|
||||
});
|
||||
|
||||
if (calendar_data)
|
||||
return *calendar_data;
|
||||
return {};
|
||||
}
|
||||
|
||||
void CalendarData::adjust_time_range_for_proleptic_calendar(icu::Calendar& icu_calendar)
|
||||
{
|
||||
// https://tc39.es/ecma262/#sec-time-values-and-time-range
|
||||
// A time value supports a slightly smaller range of -8,640,000,000,000,000 to 8,640,000,000,000,000 milliseconds.
|
||||
static constexpr UDate ECMA_262_MINIMUM_TIME = -8.64E15;
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
|
||||
if (auto* gregorian_calendar = as_if<icu::GregorianCalendar>(icu_calendar)) {
|
||||
gregorian_calendar->setGregorianChange(ECMA_262_MINIMUM_TIME, status);
|
||||
verify_icu_success(status);
|
||||
}
|
||||
}
|
||||
|
||||
CalendarData::CalendarData(NonnullOwnPtr<icu::Calendar> calendar)
|
||||
: m_calendar(move(calendar))
|
||||
{
|
||||
adjust_time_range_for_proleptic_calendar(*m_calendar);
|
||||
}
|
||||
|
||||
Optional<TimeZoneData&> TimeZoneData::for_time_zone(StringView time_zone)
|
||||
{
|
||||
auto time_zone_data = s_time_zone_cache.get(time_zone);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2024-2025, Tim Flynn <trflynn89@ladybird.org>
|
||||
* Copyright (c) 2024-2026, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
|
@ -25,7 +25,6 @@
|
|||
#include <unicode/uversion.h>
|
||||
|
||||
U_NAMESPACE_BEGIN
|
||||
class Calendar;
|
||||
class DateTimePatternGenerator;
|
||||
class LocaleDisplayNames;
|
||||
class NumberingSystem;
|
||||
|
|
@ -69,20 +68,6 @@ private:
|
|||
Optional<DigitalFormat> m_digital_format;
|
||||
};
|
||||
|
||||
class CalendarData {
|
||||
public:
|
||||
static Optional<CalendarData&> for_calendar(String const& calendar);
|
||||
|
||||
static void adjust_time_range_for_proleptic_calendar(icu::Calendar&);
|
||||
|
||||
ALWAYS_INLINE icu::Calendar& calendar() { return *m_calendar; }
|
||||
|
||||
private:
|
||||
explicit CalendarData(NonnullOwnPtr<icu::Calendar>);
|
||||
|
||||
NonnullOwnPtr<icu::Calendar> m_calendar;
|
||||
};
|
||||
|
||||
class TimeZoneData {
|
||||
public:
|
||||
static Optional<TimeZoneData&> for_time_zone(StringView time_zone);
|
||||
|
|
@ -113,20 +98,6 @@ inline void verify_icu_success(UErrorCode code, SourceLocation location = Source
|
|||
}
|
||||
}
|
||||
|
||||
#define ICU_MUST(expression) \
|
||||
({ \
|
||||
/* Ignore -Wshadow to allow nesting the macro. */ \
|
||||
AK_IGNORE_DIAGNOSTIC("-Wshadow", auto _temporary_result = (expression)); \
|
||||
verify_icu_success(status); \
|
||||
_temporary_result; \
|
||||
})
|
||||
|
||||
#define ICU_MUST_VOID(expression) \
|
||||
({ \
|
||||
expression; \
|
||||
verify_icu_success(status); \
|
||||
})
|
||||
|
||||
ALWAYS_INLINE icu::StringPiece icu_string_piece(StringView string)
|
||||
{
|
||||
return { string.characters_without_null_termination(), static_cast<i32>(string.length()) };
|
||||
|
|
|
|||
13
Libraries/LibUnicode/Rust/Cargo.toml
Normal file
13
Libraries/LibUnicode/Rust/Cargo.toml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "libunicode_rust"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[lib]
|
||||
crate-type = ["staticlib"]
|
||||
|
||||
# After changing dependencies, regenerate the Flatpak sources:
|
||||
# python3 Meta/CMake/flatpak/generate-cargo-sources.py
|
||||
[dependencies]
|
||||
calendrical_calculations = "0.2"
|
||||
icu_calendar = { version = "2.1.1", features = ["compiled_data"] }
|
||||
604
Libraries/LibUnicode/Rust/src/calendar.rs
Normal file
604
Libraries/LibUnicode/Rust/src/calendar.rs
Normal file
|
|
@ -0,0 +1,604 @@
|
|||
/*
|
||||
* Copyright (c) 2026-present, the Ladybird developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
use calendrical_calculations::rata_die::RataDie;
|
||||
use icu_calendar::{AnyCalendar, AnyCalendarKind, Date, types::MonthCode};
|
||||
use std::panic::{AssertUnwindSafe, catch_unwind};
|
||||
|
||||
#[repr(C)]
|
||||
pub struct FfiISODate {
|
||||
pub year: i32,
|
||||
pub month: u8,
|
||||
pub day: u8,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct FfiOptionalISODate {
|
||||
pub iso_date: FfiISODate,
|
||||
pub has_value: bool,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct FfiCalendarDate {
|
||||
pub year: i32,
|
||||
pub month: u8,
|
||||
pub month_code: [u8; 5], // e.g. "M01\0\0" or "M05L\0"
|
||||
pub month_code_length: u8,
|
||||
pub day: u8,
|
||||
pub day_of_week: u8,
|
||||
pub day_of_year: u16,
|
||||
pub days_in_week: u8,
|
||||
pub days_in_month: u8,
|
||||
pub days_in_year: u16,
|
||||
pub months_in_year: u8,
|
||||
pub in_leap_year: bool,
|
||||
}
|
||||
|
||||
const EMPTY_ISO_DATE: FfiOptionalISODate = FfiOptionalISODate {
|
||||
iso_date: FfiISODate {
|
||||
year: 0,
|
||||
month: 0,
|
||||
day: 0,
|
||||
},
|
||||
has_value: false,
|
||||
};
|
||||
|
||||
const EMPTY_CALENDAR_DATE: FfiCalendarDate = FfiCalendarDate {
|
||||
year: 0,
|
||||
month: 0,
|
||||
month_code: [0; 5],
|
||||
month_code_length: 0,
|
||||
day: 0,
|
||||
day_of_week: 0,
|
||||
day_of_year: 0,
|
||||
days_in_week: 7,
|
||||
days_in_month: 0,
|
||||
days_in_year: 0,
|
||||
months_in_year: 0,
|
||||
in_leap_year: false,
|
||||
};
|
||||
|
||||
fn abort_on_panic<F: FnOnce() -> R, R>(f: F) -> R {
|
||||
match catch_unwind(AssertUnwindSafe(f)) {
|
||||
Ok(result) => result,
|
||||
Err(_) => std::process::abort(),
|
||||
}
|
||||
}
|
||||
|
||||
/// SAFETY: All FFI string inputs are ASCII calendar names (e.g. "chinese") or month codes (e.g. "M01").
|
||||
fn ascii_string_from_ffi<'a>(string: *const u8, length: usize) -> &'a str {
|
||||
let bytes = unsafe { std::slice::from_raw_parts(string, length) };
|
||||
debug_assert!(bytes.is_ascii(), "Expected ASCII input from FFI");
|
||||
unsafe { std::str::from_utf8_unchecked(bytes) }
|
||||
}
|
||||
|
||||
fn iso_date_to_ffi(iso_date: Option<FfiISODate>) -> FfiOptionalISODate {
|
||||
match iso_date {
|
||||
Some(iso_date) => FfiOptionalISODate {
|
||||
iso_date,
|
||||
has_value: true,
|
||||
},
|
||||
None => EMPTY_ISO_DATE,
|
||||
}
|
||||
}
|
||||
|
||||
fn make_calendar(calendar_name: &str) -> Option<AnyCalendar> {
|
||||
let kind = match calendar_name {
|
||||
"iso8601" => AnyCalendarKind::Iso,
|
||||
"gregory" => AnyCalendarKind::Gregorian,
|
||||
"buddhist" => AnyCalendarKind::Buddhist,
|
||||
"chinese" => AnyCalendarKind::Chinese,
|
||||
"coptic" => AnyCalendarKind::Coptic,
|
||||
"dangi" => AnyCalendarKind::Dangi,
|
||||
"ethiopic" => AnyCalendarKind::Ethiopian,
|
||||
"ethioaa" => AnyCalendarKind::EthiopianAmeteAlem,
|
||||
"hebrew" => AnyCalendarKind::Hebrew,
|
||||
"indian" => AnyCalendarKind::Indian,
|
||||
"islamic-civil" | "islamicc" => AnyCalendarKind::HijriTabularTypeIIFriday,
|
||||
"islamic-tbla" => AnyCalendarKind::HijriTabularTypeIIThursday,
|
||||
"islamic-umalqura" => AnyCalendarKind::HijriUmmAlQura,
|
||||
"islamic" => AnyCalendarKind::HijriSimulatedMecca,
|
||||
"japanese" => AnyCalendarKind::Japanese,
|
||||
"persian" => AnyCalendarKind::Persian,
|
||||
"roc" => AnyCalendarKind::Roc,
|
||||
_ => return None,
|
||||
};
|
||||
Some(AnyCalendar::new(kind))
|
||||
}
|
||||
|
||||
fn is_chinese_or_dangi(calendar_name: &str) -> bool {
|
||||
calendar_name == "chinese" || calendar_name == "dangi"
|
||||
}
|
||||
|
||||
fn parse_month_code(month_code: &str) -> Option<MonthCode> {
|
||||
if month_code.len() < 3 || !month_code.starts_with('M') {
|
||||
return None;
|
||||
}
|
||||
|
||||
let month_number: u8 = month_code[1..3].parse().ok()?;
|
||||
let is_leap_month = month_code.len() == 4 && month_code.as_bytes()[3] == b'L';
|
||||
|
||||
if is_leap_month {
|
||||
MonthCode::new_leap(month_number)
|
||||
} else {
|
||||
MonthCode::new_normal(month_number)
|
||||
}
|
||||
}
|
||||
|
||||
fn encode_month_code(month_code: &MonthCode) -> ([u8; 5], u8) {
|
||||
let serialized = month_code.0.as_str();
|
||||
let bytes = serialized.as_bytes();
|
||||
|
||||
let mut buffer = [0u8; 5];
|
||||
let length = bytes.len().min(5);
|
||||
buffer[..length].copy_from_slice(&bytes[..length]);
|
||||
|
||||
(buffer, length as u8)
|
||||
}
|
||||
|
||||
struct ChineseMonthInfo {
|
||||
month_code: MonthCode,
|
||||
days_in_month: u8,
|
||||
}
|
||||
|
||||
/// Collect months starting from a known year-start date, iterating via RataDie arithmetic.
|
||||
fn collect_year_months(
|
||||
calendar: &AnyCalendar,
|
||||
year_start: &Date<AnyCalendar>,
|
||||
extended_year: i32,
|
||||
) -> Vec<ChineseMonthInfo> {
|
||||
let months_in_year = year_start.months_in_year();
|
||||
|
||||
let mut result = Vec::with_capacity(months_in_year as usize);
|
||||
let mut current_date = year_start.clone();
|
||||
|
||||
for _ in 0..months_in_year {
|
||||
let month_code = current_date.month().standard_code;
|
||||
let days_in_month = current_date.days_in_month();
|
||||
|
||||
result.push(ChineseMonthInfo {
|
||||
month_code,
|
||||
days_in_month,
|
||||
});
|
||||
|
||||
// Advance to day 1 of next month via RataDie arithmetic.
|
||||
let rata_die =
|
||||
RataDie::new(current_date.to_rata_die().to_i64_date() + days_in_month as i64);
|
||||
|
||||
let next_date = Date::from_rata_die(rata_die, calendar.clone());
|
||||
if next_date.year().extended_year() != extended_year {
|
||||
break;
|
||||
}
|
||||
|
||||
current_date = next_date;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Look up the ICU4X extended year for a Chinese/Dangi arithmetic year.
|
||||
fn chinese_or_dangi_extended_year(calendar: &AnyCalendar, arithmetic_year: i32) -> Option<i32> {
|
||||
// Chinese new year falls in Jan/Feb. Use June 15 to land in the right calendar year.
|
||||
let approximate_iso = Date::try_new_iso(arithmetic_year, 6, 15).ok()?;
|
||||
let calendar_date = Date::new_from_iso(approximate_iso, calendar.clone());
|
||||
|
||||
Some(calendar_date.year().extended_year())
|
||||
}
|
||||
|
||||
/// Build a list of month info for each ordinal month in a Chinese/Dangi year.
|
||||
fn chinese_year_months(
|
||||
calendar: &AnyCalendar,
|
||||
arithmetic_year: i32,
|
||||
) -> Option<Vec<ChineseMonthInfo>> {
|
||||
let extended_year = chinese_or_dangi_extended_year(calendar, arithmetic_year)?;
|
||||
let month_one_code = MonthCode::new_normal(1)?;
|
||||
|
||||
let year_start =
|
||||
Date::try_new_from_codes(None, extended_year, month_one_code, 1, calendar.clone()).ok()?;
|
||||
|
||||
Some(collect_year_months(calendar, &year_start, extended_year))
|
||||
}
|
||||
|
||||
/// Construct a calendar date from an arithmetic year and ordinal month.
|
||||
fn make_calendar_date_from_ordinal(
|
||||
calendar_name: &str,
|
||||
calendar: &AnyCalendar,
|
||||
arithmetic_year: i32,
|
||||
ordinal_month: u8,
|
||||
day: u8,
|
||||
) -> Option<Date<AnyCalendar>> {
|
||||
if is_chinese_or_dangi(calendar_name) {
|
||||
let months = chinese_year_months(calendar, arithmetic_year)?;
|
||||
|
||||
let month_index = (ordinal_month as usize).checked_sub(1)?;
|
||||
if month_index >= months.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let month_code = months[month_index].month_code;
|
||||
let extended_year = chinese_or_dangi_extended_year(calendar, arithmetic_year)?;
|
||||
|
||||
Date::try_new_from_codes(None, extended_year, month_code, day, calendar.clone()).ok()
|
||||
} else if calendar_name == "hebrew" {
|
||||
// Determine if this is a leap year by trying to construct a date with M05L.
|
||||
let adar_i_code = MonthCode::new_leap(5)?;
|
||||
let is_leap_year =
|
||||
Date::try_new_from_codes(None, arithmetic_year, adar_i_code, 1, calendar.clone())
|
||||
.is_ok();
|
||||
|
||||
let month_code = if is_leap_year {
|
||||
if ordinal_month == 6 {
|
||||
MonthCode::new_leap(5)?
|
||||
} else if ordinal_month > 6 {
|
||||
MonthCode::new_normal(ordinal_month - 1)?
|
||||
} else {
|
||||
MonthCode::new_normal(ordinal_month)?
|
||||
}
|
||||
} else {
|
||||
MonthCode::new_normal(ordinal_month)?
|
||||
};
|
||||
|
||||
Date::try_new_from_codes(None, arithmetic_year, month_code, day, calendar.clone()).ok()
|
||||
} else {
|
||||
let month_code = MonthCode::new_normal(ordinal_month)?;
|
||||
Date::try_new_from_codes(None, arithmetic_year, month_code, day, calendar.clone()).ok()
|
||||
}
|
||||
}
|
||||
|
||||
fn iso_date_to_calendar_date_impl(
|
||||
calendar_name: &str,
|
||||
iso_year: i32,
|
||||
iso_month: u8,
|
||||
iso_day: u8,
|
||||
) -> Option<FfiCalendarDate> {
|
||||
let calendar = make_calendar(calendar_name)?;
|
||||
|
||||
let iso_date = Date::try_new_iso(iso_year, iso_month, iso_day).ok()?;
|
||||
let calendar_date = Date::new_from_iso(iso_date, calendar.clone());
|
||||
|
||||
let (arithmetic_year, ordinal_month) = if is_chinese_or_dangi(calendar_name) {
|
||||
let extended_year = calendar_date.year().extended_year();
|
||||
|
||||
let month_one_code = MonthCode::new_normal(1)?;
|
||||
let current_month_code = calendar_date.month().standard_code;
|
||||
|
||||
let year_start =
|
||||
Date::try_new_from_codes(None, extended_year, month_one_code, 1, calendar.clone())
|
||||
.ok()?;
|
||||
|
||||
let ordinal = collect_year_months(&calendar, &year_start, extended_year)
|
||||
.iter()
|
||||
.position(|m| m.month_code == current_month_code)
|
||||
.map(|i| (i + 1) as u8)
|
||||
.unwrap_or(1);
|
||||
|
||||
(year_start.to_iso().extended_year(), ordinal)
|
||||
} else {
|
||||
(
|
||||
calendar_date.extended_year(),
|
||||
calendar_date.month().ordinal as u8,
|
||||
)
|
||||
};
|
||||
|
||||
let month_code = calendar_date.month().standard_code;
|
||||
let (month_code_buffer, month_code_length) = encode_month_code(&month_code);
|
||||
|
||||
Some(FfiCalendarDate {
|
||||
year: arithmetic_year,
|
||||
month: ordinal_month,
|
||||
month_code: month_code_buffer,
|
||||
month_code_length,
|
||||
day: calendar_date.day_of_month().0,
|
||||
day_of_week: calendar_date.day_of_week() as u8,
|
||||
day_of_year: calendar_date.day_of_year().0,
|
||||
days_in_week: 7,
|
||||
days_in_month: calendar_date.days_in_month(),
|
||||
days_in_year: calendar_date.days_in_year(),
|
||||
months_in_year: calendar_date.months_in_year(),
|
||||
in_leap_year: calendar_date.is_in_leap_year(),
|
||||
})
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// `calendar` must point to a valid UTF-8 string of `calendar_length` bytes.
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn icu_iso_date_to_calendar_date(
|
||||
calendar: *const u8,
|
||||
calendar_length: usize,
|
||||
iso_year: i32,
|
||||
iso_month: u8,
|
||||
iso_day: u8,
|
||||
) -> FfiCalendarDate {
|
||||
abort_on_panic(|| {
|
||||
let calendar_name = ascii_string_from_ffi(calendar, calendar_length);
|
||||
|
||||
iso_date_to_calendar_date_impl(calendar_name, iso_year, iso_month, iso_day)
|
||||
.unwrap_or(EMPTY_CALENDAR_DATE)
|
||||
})
|
||||
}
|
||||
|
||||
fn calendar_date_to_iso_date_impl(
|
||||
calendar_name: &str,
|
||||
arithmetic_year: i32,
|
||||
ordinal_month: u8,
|
||||
day: u8,
|
||||
) -> Option<FfiISODate> {
|
||||
let calendar = make_calendar(calendar_name)?;
|
||||
|
||||
let calendar_date = make_calendar_date_from_ordinal(
|
||||
calendar_name,
|
||||
&calendar,
|
||||
arithmetic_year,
|
||||
ordinal_month,
|
||||
day,
|
||||
)?;
|
||||
|
||||
let iso_date = calendar_date.to_iso();
|
||||
|
||||
Some(FfiISODate {
|
||||
year: iso_date.extended_year(),
|
||||
month: iso_date.month().ordinal as u8,
|
||||
day: iso_date.day_of_month().0,
|
||||
})
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// `calendar` must point to a valid UTF-8 string of `calendar_length` bytes.
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn icu_calendar_date_to_iso_date(
|
||||
calendar: *const u8,
|
||||
calendar_length: usize,
|
||||
arithmetic_year: i32,
|
||||
ordinal_month: u8,
|
||||
day: u8,
|
||||
) -> FfiOptionalISODate {
|
||||
abort_on_panic(|| {
|
||||
let calendar_name = ascii_string_from_ffi(calendar, calendar_length);
|
||||
|
||||
iso_date_to_ffi(calendar_date_to_iso_date_impl(
|
||||
calendar_name,
|
||||
arithmetic_year,
|
||||
ordinal_month,
|
||||
day,
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
fn calendar_month_code_to_iso_date_impl(
|
||||
calendar_name: &str,
|
||||
month_code_string: &str,
|
||||
iso_year: i32,
|
||||
day: u8,
|
||||
) -> Option<FfiISODate> {
|
||||
let calendar = make_calendar(calendar_name)?;
|
||||
let month_code = parse_month_code(month_code_string)?;
|
||||
|
||||
let iso_jan1 = Date::try_new_iso(iso_year, 1, 1).ok()?;
|
||||
let iso_dec31 = Date::try_new_iso(iso_year, 12, 31).ok()?;
|
||||
|
||||
let calendar_jan1 = Date::new_from_iso(iso_jan1, calendar.clone());
|
||||
let calendar_dec31 = Date::new_from_iso(iso_dec31, calendar.clone());
|
||||
|
||||
let start_extended_year = calendar_jan1.extended_year();
|
||||
let end_extended_year = calendar_dec31.extended_year();
|
||||
|
||||
let mut best_iso_date: Option<FfiISODate> = None;
|
||||
|
||||
for extended_year in start_extended_year..=end_extended_year {
|
||||
let Ok(candidate) =
|
||||
Date::try_new_from_codes(None, extended_year, month_code, day, calendar.clone())
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if candidate.month().standard_code != month_code || candidate.day_of_month().0 != day {
|
||||
continue;
|
||||
}
|
||||
|
||||
let iso_date = candidate.to_iso();
|
||||
if iso_date.extended_year() != iso_year {
|
||||
continue;
|
||||
}
|
||||
|
||||
let candidate_date = FfiISODate {
|
||||
year: iso_date.extended_year(),
|
||||
month: iso_date.month().ordinal,
|
||||
day: iso_date.day_of_month().0,
|
||||
};
|
||||
|
||||
best_iso_date = Some(match best_iso_date {
|
||||
Some(b) if (b.month, b.day) > (candidate_date.month, candidate_date.day) => b,
|
||||
_ => candidate_date,
|
||||
});
|
||||
}
|
||||
|
||||
best_iso_date
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// `calendar` must point to a valid UTF-8 string of `calendar_length` bytes.
|
||||
/// `month_code` must point to a valid UTF-8 string of `month_code_length` bytes.
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn icu_calendar_month_code_to_iso_date(
|
||||
calendar: *const u8,
|
||||
calendar_length: usize,
|
||||
iso_year: i32,
|
||||
month_code: *const u8,
|
||||
month_code_length: usize,
|
||||
day: u8,
|
||||
) -> FfiOptionalISODate {
|
||||
abort_on_panic(|| {
|
||||
let calendar_name = ascii_string_from_ffi(calendar, calendar_length);
|
||||
let month_code_string = ascii_string_from_ffi(month_code, month_code_length);
|
||||
|
||||
iso_date_to_ffi(calendar_month_code_to_iso_date_impl(
|
||||
calendar_name,
|
||||
month_code_string,
|
||||
iso_year,
|
||||
day,
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
fn calendar_months_in_year_impl(calendar_name: &str, arithmetic_year: i32) -> Option<u8> {
|
||||
let calendar = make_calendar(calendar_name)?;
|
||||
|
||||
if is_chinese_or_dangi(calendar_name) {
|
||||
let months = chinese_year_months(&calendar, arithmetic_year)?;
|
||||
return Some(months.len() as u8);
|
||||
}
|
||||
|
||||
let month_one_code = MonthCode::new_normal(1)?;
|
||||
|
||||
let date = Date::try_new_from_codes(None, arithmetic_year, month_one_code, 1, calendar).ok()?;
|
||||
Some(date.months_in_year())
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// `calendar` must point to a valid UTF-8 string of `calendar_length` bytes.
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn icu_calendar_months_in_year(
|
||||
calendar: *const u8,
|
||||
calendar_length: usize,
|
||||
arithmetic_year: i32,
|
||||
) -> u8 {
|
||||
abort_on_panic(|| {
|
||||
let calendar_name = ascii_string_from_ffi(calendar, calendar_length);
|
||||
|
||||
calendar_months_in_year_impl(calendar_name, arithmetic_year).unwrap_or(12)
|
||||
})
|
||||
}
|
||||
|
||||
fn calendar_days_in_month_impl(
|
||||
calendar_name: &str,
|
||||
arithmetic_year: i32,
|
||||
ordinal_month: u8,
|
||||
) -> Option<u8> {
|
||||
let calendar = make_calendar(calendar_name)?;
|
||||
|
||||
if is_chinese_or_dangi(calendar_name) {
|
||||
let months = chinese_year_months(&calendar, arithmetic_year)?;
|
||||
let month_index = (ordinal_month as usize).checked_sub(1)?;
|
||||
|
||||
return months.get(month_index).map(|m| m.days_in_month);
|
||||
}
|
||||
|
||||
let date = make_calendar_date_from_ordinal(
|
||||
calendar_name,
|
||||
&calendar,
|
||||
arithmetic_year,
|
||||
ordinal_month,
|
||||
1,
|
||||
)?;
|
||||
|
||||
Some(date.days_in_month())
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// `calendar` must point to a valid UTF-8 string of `calendar_length` bytes.
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn icu_calendar_days_in_month(
|
||||
calendar: *const u8,
|
||||
calendar_length: usize,
|
||||
arithmetic_year: i32,
|
||||
ordinal_month: u8,
|
||||
) -> u8 {
|
||||
abort_on_panic(|| {
|
||||
let calendar_name = ascii_string_from_ffi(calendar, calendar_length);
|
||||
|
||||
calendar_days_in_month_impl(calendar_name, arithmetic_year, ordinal_month).unwrap_or(30)
|
||||
})
|
||||
}
|
||||
|
||||
fn calendar_max_days_in_month_code_impl(
|
||||
calendar_name: &str,
|
||||
month_code_string: &str,
|
||||
) -> Option<u8> {
|
||||
let calendar = make_calendar(calendar_name)?;
|
||||
let month_code = parse_month_code(month_code_string)?;
|
||||
|
||||
let base_iso_date = Date::try_new_iso(1970, 7, 1).ok()?;
|
||||
let base_calendar_date = Date::new_from_iso(base_iso_date, calendar.clone());
|
||||
let base_extended_year = base_calendar_date.extended_year();
|
||||
|
||||
let mut max_days_in_month: u8 = 0;
|
||||
|
||||
for offset in -2i32..=2 {
|
||||
let extended_year = base_extended_year + offset;
|
||||
|
||||
if let Ok(date) =
|
||||
Date::try_new_from_codes(None, extended_year, month_code, 1, calendar.clone())
|
||||
&& date.month().standard_code == month_code
|
||||
{
|
||||
max_days_in_month = max_days_in_month.max(date.days_in_month());
|
||||
}
|
||||
}
|
||||
|
||||
if max_days_in_month > 0 {
|
||||
Some(max_days_in_month)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// `calendar` must point to a valid UTF-8 string of `calendar_length` bytes.
|
||||
/// `month_code` must point to a valid UTF-8 string of `month_code_length` bytes.
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn icu_calendar_max_days_in_month_code(
|
||||
calendar: *const u8,
|
||||
calendar_length: usize,
|
||||
month_code: *const u8,
|
||||
month_code_length: usize,
|
||||
) -> u8 {
|
||||
abort_on_panic(|| {
|
||||
let calendar_name = ascii_string_from_ffi(calendar, calendar_length);
|
||||
let month_code = ascii_string_from_ffi(month_code, month_code_length);
|
||||
|
||||
calendar_max_days_in_month_code_impl(calendar_name, month_code).unwrap_or(30)
|
||||
})
|
||||
}
|
||||
|
||||
fn year_contains_month_code_impl(
|
||||
calendar_name: &str,
|
||||
arithmetic_year: i32,
|
||||
month_code_string: &str,
|
||||
) -> Option<bool> {
|
||||
let calendar = make_calendar(calendar_name)?;
|
||||
let month_code = parse_month_code(month_code_string)?;
|
||||
|
||||
let contains_month_code = if is_chinese_or_dangi(calendar_name) {
|
||||
let months = chinese_year_months(&calendar, arithmetic_year)?;
|
||||
months.iter().any(|m| m.month_code == month_code)
|
||||
} else {
|
||||
matches!(
|
||||
Date::try_new_from_codes(None, arithmetic_year, month_code, 1, calendar.clone()),
|
||||
Ok(date) if date.month().standard_code == month_code
|
||||
)
|
||||
};
|
||||
|
||||
Some(contains_month_code)
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// `calendar` must point to a valid UTF-8 string of `calendar_length` bytes.
|
||||
/// `month_code` must point to a valid UTF-8 string of `month_code_length` bytes.
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn icu_year_contains_month_code(
|
||||
calendar: *const u8,
|
||||
calendar_length: usize,
|
||||
arithmetic_year: i32,
|
||||
month_code: *const u8,
|
||||
month_code_length: usize,
|
||||
) -> bool {
|
||||
abort_on_panic(|| {
|
||||
let calendar_name = ascii_string_from_ffi(calendar, calendar_length);
|
||||
let month_code_string = ascii_string_from_ffi(month_code, month_code_length);
|
||||
|
||||
year_contains_month_code_impl(calendar_name, arithmetic_year, month_code_string)
|
||||
.unwrap_or(false)
|
||||
})
|
||||
}
|
||||
7
Libraries/LibUnicode/Rust/src/lib.rs
Normal file
7
Libraries/LibUnicode/Rust/src/lib.rs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* Copyright (c) 2026-present, the Ladybird developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
mod calendar;
|
||||
|
|
@ -12,6 +12,97 @@
|
|||
"sha256": "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8",
|
||||
"dest": "cargo/vendor/autocfg-1.5.0"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"archive-type": "tar-gzip",
|
||||
"url": "https://static.crates.io/crates/calendrical_calculations/calendrical_calculations-0.2.3.crate",
|
||||
"sha256": "3a0b39595c6ee54a8d0900204ba4c401d0ab4eb45adaf07178e8d017541529e7",
|
||||
"dest": "cargo/vendor/calendrical_calculations-0.2.3"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"archive-type": "tar-gzip",
|
||||
"url": "https://static.crates.io/crates/core_maths/core_maths-0.1.1.crate",
|
||||
"sha256": "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30",
|
||||
"dest": "cargo/vendor/core_maths-0.1.1"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"archive-type": "tar-gzip",
|
||||
"url": "https://static.crates.io/crates/displaydoc/displaydoc-0.2.5.crate",
|
||||
"sha256": "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0",
|
||||
"dest": "cargo/vendor/displaydoc-0.2.5"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"archive-type": "tar-gzip",
|
||||
"url": "https://static.crates.io/crates/icu_calendar/icu_calendar-2.1.1.crate",
|
||||
"sha256": "d6f0e52e009b6b16ba9c0693578796f2dd4aaa59a7f8f920423706714a89ac4e",
|
||||
"dest": "cargo/vendor/icu_calendar-2.1.1"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"archive-type": "tar-gzip",
|
||||
"url": "https://static.crates.io/crates/icu_calendar_data/icu_calendar_data-2.1.1.crate",
|
||||
"sha256": "527f04223b17edfe0bd43baf14a0cb1b017830db65f3950dc00224860a9a446d",
|
||||
"dest": "cargo/vendor/icu_calendar_data-2.1.1"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"archive-type": "tar-gzip",
|
||||
"url": "https://static.crates.io/crates/icu_collections/icu_collections-2.1.1.crate",
|
||||
"sha256": "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43",
|
||||
"dest": "cargo/vendor/icu_collections-2.1.1"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"archive-type": "tar-gzip",
|
||||
"url": "https://static.crates.io/crates/icu_locale/icu_locale-2.1.1.crate",
|
||||
"sha256": "532b11722e350ab6bf916ba6eb0efe3ee54b932666afec989465f9243fe6dd60",
|
||||
"dest": "cargo/vendor/icu_locale-2.1.1"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"archive-type": "tar-gzip",
|
||||
"url": "https://static.crates.io/crates/icu_locale_core/icu_locale_core-2.1.1.crate",
|
||||
"sha256": "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6",
|
||||
"dest": "cargo/vendor/icu_locale_core-2.1.1"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"archive-type": "tar-gzip",
|
||||
"url": "https://static.crates.io/crates/icu_locale_data/icu_locale_data-2.1.2.crate",
|
||||
"sha256": "1c5f1d16b4c3a2642d3a719f18f6b06070ab0aef246a6418130c955ae08aa831",
|
||||
"dest": "cargo/vendor/icu_locale_data-2.1.2"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"archive-type": "tar-gzip",
|
||||
"url": "https://static.crates.io/crates/icu_provider/icu_provider-2.1.1.crate",
|
||||
"sha256": "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614",
|
||||
"dest": "cargo/vendor/icu_provider-2.1.1"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"archive-type": "tar-gzip",
|
||||
"url": "https://static.crates.io/crates/ixdtf/ixdtf-0.6.4.crate",
|
||||
"sha256": "84de9d95a6d2547d9b77ee3f25fa0ee32e3c3a6484d47a55adebc0439c077992",
|
||||
"dest": "cargo/vendor/ixdtf-0.6.4"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"archive-type": "tar-gzip",
|
||||
"url": "https://static.crates.io/crates/libm/libm-0.2.16.crate",
|
||||
"sha256": "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981",
|
||||
"dest": "cargo/vendor/libm-0.2.16"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"archive-type": "tar-gzip",
|
||||
"url": "https://static.crates.io/crates/litemap/litemap-0.8.1.crate",
|
||||
"sha256": "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77",
|
||||
"dest": "cargo/vendor/litemap-0.8.1"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"archive-type": "tar-gzip",
|
||||
|
|
@ -33,6 +124,76 @@
|
|||
"sha256": "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841",
|
||||
"dest": "cargo/vendor/num-traits-0.2.19"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"archive-type": "tar-gzip",
|
||||
"url": "https://static.crates.io/crates/potential_utf/potential_utf-0.1.4.crate",
|
||||
"sha256": "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77",
|
||||
"dest": "cargo/vendor/potential_utf-0.1.4"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"archive-type": "tar-gzip",
|
||||
"url": "https://static.crates.io/crates/proc-macro2/proc-macro2-1.0.106.crate",
|
||||
"sha256": "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934",
|
||||
"dest": "cargo/vendor/proc-macro2-1.0.106"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"archive-type": "tar-gzip",
|
||||
"url": "https://static.crates.io/crates/quote/quote-1.0.45.crate",
|
||||
"sha256": "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924",
|
||||
"dest": "cargo/vendor/quote-1.0.45"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"archive-type": "tar-gzip",
|
||||
"url": "https://static.crates.io/crates/serde/serde-1.0.228.crate",
|
||||
"sha256": "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e",
|
||||
"dest": "cargo/vendor/serde-1.0.228"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"archive-type": "tar-gzip",
|
||||
"url": "https://static.crates.io/crates/serde_core/serde_core-1.0.228.crate",
|
||||
"sha256": "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad",
|
||||
"dest": "cargo/vendor/serde_core-1.0.228"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"archive-type": "tar-gzip",
|
||||
"url": "https://static.crates.io/crates/serde_derive/serde_derive-1.0.228.crate",
|
||||
"sha256": "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79",
|
||||
"dest": "cargo/vendor/serde_derive-1.0.228"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"archive-type": "tar-gzip",
|
||||
"url": "https://static.crates.io/crates/stable_deref_trait/stable_deref_trait-1.2.1.crate",
|
||||
"sha256": "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596",
|
||||
"dest": "cargo/vendor/stable_deref_trait-1.2.1"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"archive-type": "tar-gzip",
|
||||
"url": "https://static.crates.io/crates/syn/syn-2.0.117.crate",
|
||||
"sha256": "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99",
|
||||
"dest": "cargo/vendor/syn-2.0.117"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"archive-type": "tar-gzip",
|
||||
"url": "https://static.crates.io/crates/synstructure/synstructure-0.13.2.crate",
|
||||
"sha256": "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2",
|
||||
"dest": "cargo/vendor/synstructure-0.13.2"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"archive-type": "tar-gzip",
|
||||
"url": "https://static.crates.io/crates/tinystr/tinystr-0.8.2.crate",
|
||||
"sha256": "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869",
|
||||
"dest": "cargo/vendor/tinystr-0.8.2"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"archive-type": "tar-gzip",
|
||||
|
|
@ -40,14 +201,101 @@
|
|||
"sha256": "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5",
|
||||
"dest": "cargo/vendor/unicode-ident-1.0.22"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"archive-type": "tar-gzip",
|
||||
"url": "https://static.crates.io/crates/writeable/writeable-0.6.2.crate",
|
||||
"sha256": "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9",
|
||||
"dest": "cargo/vendor/writeable-0.6.2"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"archive-type": "tar-gzip",
|
||||
"url": "https://static.crates.io/crates/yoke/yoke-0.8.1.crate",
|
||||
"sha256": "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954",
|
||||
"dest": "cargo/vendor/yoke-0.8.1"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"archive-type": "tar-gzip",
|
||||
"url": "https://static.crates.io/crates/yoke-derive/yoke-derive-0.8.1.crate",
|
||||
"sha256": "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d",
|
||||
"dest": "cargo/vendor/yoke-derive-0.8.1"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"archive-type": "tar-gzip",
|
||||
"url": "https://static.crates.io/crates/zerofrom/zerofrom-0.1.6.crate",
|
||||
"sha256": "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5",
|
||||
"dest": "cargo/vendor/zerofrom-0.1.6"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"archive-type": "tar-gzip",
|
||||
"url": "https://static.crates.io/crates/zerofrom-derive/zerofrom-derive-0.1.6.crate",
|
||||
"sha256": "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502",
|
||||
"dest": "cargo/vendor/zerofrom-derive-0.1.6"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"archive-type": "tar-gzip",
|
||||
"url": "https://static.crates.io/crates/zerotrie/zerotrie-0.2.3.crate",
|
||||
"sha256": "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851",
|
||||
"dest": "cargo/vendor/zerotrie-0.2.3"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"archive-type": "tar-gzip",
|
||||
"url": "https://static.crates.io/crates/zerovec/zerovec-0.11.5.crate",
|
||||
"sha256": "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002",
|
||||
"dest": "cargo/vendor/zerovec-0.11.5"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"archive-type": "tar-gzip",
|
||||
"url": "https://static.crates.io/crates/zerovec-derive/zerovec-derive-0.11.2.crate",
|
||||
"sha256": "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3",
|
||||
"dest": "cargo/vendor/zerovec-derive-0.11.2"
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"commands": [
|
||||
"echo '{\"files\": {}, \"package\": \"c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8\"}' > cargo/vendor/autocfg-1.5.0/.cargo-checksum.json",
|
||||
"echo '{\"files\": {}, \"package\": \"3a0b39595c6ee54a8d0900204ba4c401d0ab4eb45adaf07178e8d017541529e7\"}' > cargo/vendor/calendrical_calculations-0.2.3/.cargo-checksum.json",
|
||||
"echo '{\"files\": {}, \"package\": \"77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30\"}' > cargo/vendor/core_maths-0.1.1/.cargo-checksum.json",
|
||||
"echo '{\"files\": {}, \"package\": \"97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0\"}' > cargo/vendor/displaydoc-0.2.5/.cargo-checksum.json",
|
||||
"echo '{\"files\": {}, \"package\": \"d6f0e52e009b6b16ba9c0693578796f2dd4aaa59a7f8f920423706714a89ac4e\"}' > cargo/vendor/icu_calendar-2.1.1/.cargo-checksum.json",
|
||||
"echo '{\"files\": {}, \"package\": \"527f04223b17edfe0bd43baf14a0cb1b017830db65f3950dc00224860a9a446d\"}' > cargo/vendor/icu_calendar_data-2.1.1/.cargo-checksum.json",
|
||||
"echo '{\"files\": {}, \"package\": \"4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43\"}' > cargo/vendor/icu_collections-2.1.1/.cargo-checksum.json",
|
||||
"echo '{\"files\": {}, \"package\": \"532b11722e350ab6bf916ba6eb0efe3ee54b932666afec989465f9243fe6dd60\"}' > cargo/vendor/icu_locale-2.1.1/.cargo-checksum.json",
|
||||
"echo '{\"files\": {}, \"package\": \"edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6\"}' > cargo/vendor/icu_locale_core-2.1.1/.cargo-checksum.json",
|
||||
"echo '{\"files\": {}, \"package\": \"1c5f1d16b4c3a2642d3a719f18f6b06070ab0aef246a6418130c955ae08aa831\"}' > cargo/vendor/icu_locale_data-2.1.2/.cargo-checksum.json",
|
||||
"echo '{\"files\": {}, \"package\": \"85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614\"}' > cargo/vendor/icu_provider-2.1.1/.cargo-checksum.json",
|
||||
"echo '{\"files\": {}, \"package\": \"84de9d95a6d2547d9b77ee3f25fa0ee32e3c3a6484d47a55adebc0439c077992\"}' > cargo/vendor/ixdtf-0.6.4/.cargo-checksum.json",
|
||||
"echo '{\"files\": {}, \"package\": \"b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981\"}' > cargo/vendor/libm-0.2.16/.cargo-checksum.json",
|
||||
"echo '{\"files\": {}, \"package\": \"6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77\"}' > cargo/vendor/litemap-0.8.1/.cargo-checksum.json",
|
||||
"echo '{\"files\": {}, \"package\": \"a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9\"}' > cargo/vendor/num-bigint-0.4.6/.cargo-checksum.json",
|
||||
"echo '{\"files\": {}, \"package\": \"7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f\"}' > cargo/vendor/num-integer-0.1.46/.cargo-checksum.json",
|
||||
"echo '{\"files\": {}, \"package\": \"071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841\"}' > cargo/vendor/num-traits-0.2.19/.cargo-checksum.json",
|
||||
"echo '{\"files\": {}, \"package\": \"b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77\"}' > cargo/vendor/potential_utf-0.1.4/.cargo-checksum.json",
|
||||
"echo '{\"files\": {}, \"package\": \"8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934\"}' > cargo/vendor/proc-macro2-1.0.106/.cargo-checksum.json",
|
||||
"echo '{\"files\": {}, \"package\": \"41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924\"}' > cargo/vendor/quote-1.0.45/.cargo-checksum.json",
|
||||
"echo '{\"files\": {}, \"package\": \"9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e\"}' > cargo/vendor/serde-1.0.228/.cargo-checksum.json",
|
||||
"echo '{\"files\": {}, \"package\": \"41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad\"}' > cargo/vendor/serde_core-1.0.228/.cargo-checksum.json",
|
||||
"echo '{\"files\": {}, \"package\": \"d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79\"}' > cargo/vendor/serde_derive-1.0.228/.cargo-checksum.json",
|
||||
"echo '{\"files\": {}, \"package\": \"6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596\"}' > cargo/vendor/stable_deref_trait-1.2.1/.cargo-checksum.json",
|
||||
"echo '{\"files\": {}, \"package\": \"e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99\"}' > cargo/vendor/syn-2.0.117/.cargo-checksum.json",
|
||||
"echo '{\"files\": {}, \"package\": \"728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2\"}' > cargo/vendor/synstructure-0.13.2/.cargo-checksum.json",
|
||||
"echo '{\"files\": {}, \"package\": \"42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869\"}' > cargo/vendor/tinystr-0.8.2/.cargo-checksum.json",
|
||||
"echo '{\"files\": {}, \"package\": \"9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5\"}' > cargo/vendor/unicode-ident-1.0.22/.cargo-checksum.json",
|
||||
"echo '{\"files\": {}, \"package\": \"9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9\"}' > cargo/vendor/writeable-0.6.2/.cargo-checksum.json",
|
||||
"echo '{\"files\": {}, \"package\": \"72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954\"}' > cargo/vendor/yoke-0.8.1/.cargo-checksum.json",
|
||||
"echo '{\"files\": {}, \"package\": \"b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d\"}' > cargo/vendor/yoke-derive-0.8.1/.cargo-checksum.json",
|
||||
"echo '{\"files\": {}, \"package\": \"50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5\"}' > cargo/vendor/zerofrom-0.1.6/.cargo-checksum.json",
|
||||
"echo '{\"files\": {}, \"package\": \"d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502\"}' > cargo/vendor/zerofrom-derive-0.1.6/.cargo-checksum.json",
|
||||
"echo '{\"files\": {}, \"package\": \"2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851\"}' > cargo/vendor/zerotrie-0.2.3/.cargo-checksum.json",
|
||||
"echo '{\"files\": {}, \"package\": \"6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002\"}' > cargo/vendor/zerovec-0.11.5/.cargo-checksum.json",
|
||||
"echo '{\"files\": {}, \"package\": \"eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3\"}' > cargo/vendor/zerovec-derive-0.11.2/.cargo-checksum.json",
|
||||
"mkdir -p .cargo",
|
||||
"printf '[source.crates-io]\\nreplace-with = \"vendored-sources\"\\n\\n[source.vendored-sources]\\ndirectory = \"cargo/vendor\"\\n' > .cargo/config.toml"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -116,6 +116,45 @@ describe("correct behavior", () => {
|
|||
}).toThrowWithMessage(RangeError, "day");
|
||||
});
|
||||
|
||||
test("chinese calendar extreme dates", () => {
|
||||
const farPast = Temporal.PlainDate.from({ calendar: "chinese", year: -250000, month: 1, day: 1 });
|
||||
expect(farPast.year).toBe(-250000);
|
||||
|
||||
const farFuture = Temporal.PlainDate.from({ calendar: "chinese", year: 250000, month: 1, day: 1 });
|
||||
expect(farFuture.year).toBe(250000);
|
||||
});
|
||||
|
||||
test("dangi calendar extreme dates", () => {
|
||||
const farPast = Temporal.PlainDate.from({ calendar: "dangi", year: -250000, month: 1, day: 1 });
|
||||
expect(farPast.year).toBe(-250000);
|
||||
|
||||
const farFuture = Temporal.PlainDate.from({ calendar: "dangi", year: 250000, month: 1, day: 1 });
|
||||
expect(farFuture.year).toBe(250000);
|
||||
});
|
||||
|
||||
test("hebrew calendar epoch year (Rosh Hashanah postponement)", () => {
|
||||
const expectedCheshvan = [29, 30, 30, 29, 29, 30, 30, 29, 29, 30, 29];
|
||||
const expectedKislev = [30, 30, 30, 29, 30, 30, 30, 30, 29, 30, 30];
|
||||
|
||||
for (let year = 0; year < expectedCheshvan.length; ++year) {
|
||||
const cheshvan = Temporal.PlainDate.from({
|
||||
calendar: "hebrew",
|
||||
year,
|
||||
monthCode: "M02",
|
||||
day: 30,
|
||||
});
|
||||
expect(cheshvan.day).toBe(expectedCheshvan[year]);
|
||||
|
||||
const kislev = Temporal.PlainDate.from({
|
||||
calendar: "hebrew",
|
||||
year,
|
||||
monthCode: "M03",
|
||||
day: 30,
|
||||
});
|
||||
expect(kislev.day).toBe(expectedKislev[year]);
|
||||
}
|
||||
});
|
||||
|
||||
test("japanese era boundary: Heisei to Reiwa", () => {
|
||||
// April 30, 2019 is the last day of Heisei.
|
||||
const lastHeisei = Temporal.PlainDate.from("2019-04-30[u-ca=japanese]");
|
||||
|
|
|
|||
|
|
@ -3,6 +3,25 @@ describe("correct behavior", () => {
|
|||
const date = new Temporal.PlainDate(2021, 7, 23);
|
||||
expect(date.daysInYear).toBe(365);
|
||||
});
|
||||
|
||||
test("chinese calendar days in year", () => {
|
||||
const expectedDays = {
|
||||
2020: 384,
|
||||
2021: 354,
|
||||
2022: 355,
|
||||
2023: 384,
|
||||
2024: 354,
|
||||
2025: 384,
|
||||
2026: 354,
|
||||
2027: 354,
|
||||
2028: 384,
|
||||
};
|
||||
|
||||
for (const [year, days] of Object.entries(expectedDays)) {
|
||||
const date = Temporal.PlainDate.from({ year: Number(year), month: 1, day: 1, calendar: "chinese" });
|
||||
expect(date.daysInYear).toBe(days);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
|
|
|
|||
|
|
@ -31,6 +31,43 @@ describe("correct behavior", () => {
|
|||
expect(plainDate.monthCode).toBe("M02L");
|
||||
});
|
||||
|
||||
test("chinese calendar leap month codes across decades", () => {
|
||||
const leapMonthCases = [
|
||||
{ year: 2001, month: 5, monthCode: "M04L" },
|
||||
{ year: 2004, month: 3, monthCode: "M02L" },
|
||||
{ year: 2006, month: 8, monthCode: "M07L" },
|
||||
{ year: 2009, month: 6, monthCode: "M05L" },
|
||||
{ year: 2012, month: 5, monthCode: "M04L" },
|
||||
{ year: 2017, month: 7, monthCode: "M06L" },
|
||||
{ year: 2020, month: 5, monthCode: "M04L" },
|
||||
{ year: 2023, month: 3, monthCode: "M02L" },
|
||||
{ year: 2025, month: 7, monthCode: "M06L" },
|
||||
{ year: 2028, month: 6, monthCode: "M05L" },
|
||||
];
|
||||
|
||||
for (const { year, month, monthCode } of leapMonthCases) {
|
||||
const fromMonth = Temporal.PlainDate.from({
|
||||
year,
|
||||
month,
|
||||
day: 1,
|
||||
calendar: "chinese",
|
||||
});
|
||||
expect(fromMonth.monthCode).toBe(monthCode);
|
||||
expect(fromMonth.month).toBe(month);
|
||||
|
||||
const fromCode = Temporal.PlainDate.from({
|
||||
year,
|
||||
monthCode,
|
||||
day: 1,
|
||||
calendar: "chinese",
|
||||
});
|
||||
expect(fromCode.monthCode).toBe(monthCode);
|
||||
expect(fromCode.month).toBe(month);
|
||||
|
||||
expect(fromMonth.equals(fromCode)).toBeTrue();
|
||||
}
|
||||
});
|
||||
|
||||
test("coptic calendar 13th month", () => {
|
||||
// The Coptic calendar has a 13th month (Nasie).
|
||||
// 2024-09-06 falls in month 13 of Coptic year 1740.
|
||||
|
|
|
|||
Loading…
Reference in a new issue