Produce JS-visible string results as UTF-16 at their source, including numeric formatting, BigInt and BigFraction formatting, URI encoding, console formatting, parser errors, regular expression errors, Intl and Temporal records, LibUnicode locale boundaries, and LibWeb bindings. Handle fractional radix formatting through the UTF-16 builder view.
177 lines
6.9 KiB
C++
177 lines
6.9 KiB
C++
/*
|
||
* Copyright (c) 2026, Tim Flynn <trflynn89@ladybird.org>
|
||
*
|
||
* SPDX-License-Identifier: BSD-2-Clause
|
||
*/
|
||
|
||
#include <LibUnicode/Calendar.h>
|
||
#include <LibUnicode/RustFFI.h>
|
||
|
||
namespace Unicode {
|
||
|
||
static constexpr bool is_valid_month_code_string(Utf16View month_code)
|
||
{
|
||
// MonthCode :::
|
||
// M00L
|
||
// M0 NonZeroDigit L[opt]
|
||
// M NonZeroDigit DecimalDigit L[opt]
|
||
auto length = month_code.length_in_code_units();
|
||
|
||
if (length != 3 && length != 4)
|
||
return false;
|
||
|
||
if (month_code.code_unit_at(0) != 'M')
|
||
return false;
|
||
|
||
if (!is_ascii_digit(month_code.code_unit_at(1)) || !is_ascii_digit(month_code.code_unit_at(2)))
|
||
return false;
|
||
|
||
if (length == 3 && month_code.code_unit_at(1) == '0' && month_code.code_unit_at(2) == '0')
|
||
return false;
|
||
if (length == 4 && month_code.code_unit_at(3) != 'L')
|
||
return false;
|
||
|
||
return true;
|
||
}
|
||
|
||
// 12.2.1 ParseMonthCode ( argument ), https://tc39.es/proposal-temporal/#sec-temporal-parsemonthcode
|
||
Optional<MonthCode> parse_month_code(Utf16View month_code)
|
||
{
|
||
// 3. If ParseText(StringToCodePoints(monthCode), MonthCode) is a List of errors, throw a RangeError exception.
|
||
if (!is_valid_month_code_string(month_code))
|
||
return {};
|
||
|
||
// 4. Let isLeapMonth be false.
|
||
auto is_leap_month = false;
|
||
|
||
// 5. If the length of monthCode = 4, then
|
||
if (month_code.length_in_code_units() == 4) {
|
||
// a. Assert: The fourth code unit of monthCode is 0x004C (LATIN CAPITAL LETTER L).
|
||
VERIFY(month_code.code_unit_at(3) == 'L');
|
||
|
||
// b. Set isLeapMonth to true.
|
||
is_leap_month = true;
|
||
}
|
||
|
||
// 6. Let monthCodeDigits be the substring of monthCode from 1 to 3.
|
||
|
||
// 7. Let monthNumber be ℝ(StringToNumber(monthCodeDigits)).
|
||
auto month_number = static_cast<u8>((month_code.code_unit_at(1) - '0') * 10 + (month_code.code_unit_at(2) - '0'));
|
||
|
||
// 8. Return the Record { [[MonthNumber]]: monthNumber, [[IsLeapMonth]]: isLeapMonth }.
|
||
return MonthCode { month_number, is_leap_month };
|
||
}
|
||
|
||
// 12.2.2 CreateMonthCode ( monthNumber, isLeapMonth ), https://tc39.es/proposal-temporal/#sec-temporal-createmonthcode
|
||
Utf16String create_month_code(u8 month_number, bool is_leap_month)
|
||
{
|
||
// 1. Assert: If isLeapMonth is false, monthNumber > 0.
|
||
if (!is_leap_month)
|
||
VERIFY(month_number > 0);
|
||
|
||
// 2. Let numberPart be ToZeroPaddedDecimalString(monthNumber, 2).
|
||
|
||
// 3. If isLeapMonth is true, then
|
||
if (is_leap_month) {
|
||
// a. Return the string-concatenation of the code unit 0x004D (LATIN CAPITAL LETTER M), numberPart, and the
|
||
// code unit 0x004C (LATIN CAPITAL LETTER L).
|
||
u8 month_code[] = { 'M', static_cast<u8>('0' + (month_number / 10)), static_cast<u8>('0' + (month_number % 10)), 'L' };
|
||
return Utf16String::from_ascii_without_validation(month_code);
|
||
}
|
||
|
||
// 4. Return the string-concatenation of the code unit 0x004D (LATIN CAPITAL LETTER M) and numberPart.
|
||
u8 month_code[] = { 'M', static_cast<u8>('0' + (month_number / 10)), static_cast<u8>('0' + (month_number % 10)) };
|
||
return Utf16String::from_ascii_without_validation(month_code);
|
||
}
|
||
|
||
static ReadonlyBytes month_code_as_ascii_bytes(Utf16View month_code, Bytes buffer)
|
||
{
|
||
VERIFY(month_code.length_in_code_units() <= buffer.size());
|
||
|
||
for (size_t i = 0; i < month_code.length_in_code_units(); ++i) {
|
||
auto code_unit = month_code.code_unit_at(i);
|
||
VERIFY(is_ascii(code_unit));
|
||
buffer[i] = static_cast<u8>(code_unit);
|
||
}
|
||
|
||
return { buffer.data(), month_code.length_in_code_units() };
|
||
}
|
||
|
||
CalendarDate iso_date_to_calendar_date(StringView calendar, ISODate iso_date)
|
||
{
|
||
auto result = FFI::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 = result.year,
|
||
.month = result.month,
|
||
.month_code = Utf16String::from_ascii_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 = 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(StringView calendar, i32 year, u8 month, u8 day)
|
||
{
|
||
auto result = FFI::icu_calendar_date_to_iso_date(calendar.bytes().data(), calendar.bytes().size(), year, month, day);
|
||
if (!result.has_value)
|
||
return {};
|
||
|
||
return ISODate { result.iso_date.year, result.iso_date.month, result.iso_date.day };
|
||
}
|
||
|
||
Optional<ISODate> iso_year_and_month_code_to_iso_date(StringView calendar, i32 year, Utf16View month_code, u8 day)
|
||
{
|
||
u8 month_code_buffer[4];
|
||
auto month_code_bytes = month_code_as_ascii_bytes(month_code, month_code_buffer);
|
||
auto result = FFI::icu_iso_year_and_month_code_to_iso_date(calendar.bytes().data(), calendar.bytes().size(), year, month_code_bytes.data(), month_code_bytes.size(), day);
|
||
if (!result.has_value)
|
||
return {};
|
||
|
||
return ISODate { result.iso_date.year, result.iso_date.month, result.iso_date.day };
|
||
}
|
||
|
||
Optional<ISODate> calendar_year_and_month_code_to_iso_date(StringView calendar, i32 arithmetic_year, Utf16View month_code, u8 day)
|
||
{
|
||
u8 month_code_buffer[4];
|
||
auto month_code_bytes = month_code_as_ascii_bytes(month_code, month_code_buffer);
|
||
auto result = FFI::icu_calendar_year_and_month_code_to_iso_date(calendar.bytes().data(), calendar.bytes().size(), arithmetic_year, month_code_bytes.data(), month_code_bytes.size(), day);
|
||
if (!result.has_value)
|
||
return {};
|
||
|
||
return ISODate { result.iso_date.year, result.iso_date.month, result.iso_date.day };
|
||
}
|
||
|
||
u8 calendar_months_in_year(StringView calendar, i32 arithmetic_year)
|
||
{
|
||
return FFI::icu_calendar_months_in_year(calendar.bytes().data(), calendar.bytes().size(), arithmetic_year);
|
||
}
|
||
|
||
u8 calendar_days_in_month(StringView calendar, i32 arithmetic_year, u8 ordinal_month)
|
||
{
|
||
return FFI::icu_calendar_days_in_month(calendar.bytes().data(), calendar.bytes().size(), arithmetic_year, ordinal_month);
|
||
}
|
||
|
||
u8 calendar_max_days_in_month_code(StringView calendar, Utf16View month_code)
|
||
{
|
||
u8 month_code_buffer[4];
|
||
auto month_code_bytes = month_code_as_ascii_bytes(month_code, month_code_buffer);
|
||
return FFI::icu_calendar_max_days_in_month_code(calendar.bytes().data(), calendar.bytes().size(), month_code_bytes.data(), month_code_bytes.size());
|
||
}
|
||
|
||
bool calendar_year_contains_month_code(StringView calendar, i32 arithmetic_year, Utf16View month_code)
|
||
{
|
||
u8 month_code_buffer[4];
|
||
auto month_code_bytes = month_code_as_ascii_bytes(month_code, month_code_buffer);
|
||
return FFI::icu_year_contains_month_code(calendar.bytes().data(), calendar.bytes().size(), arithmetic_year, month_code_bytes.data(), month_code_bytes.size());
|
||
}
|
||
|
||
}
|