Files
secure-javascript-container/javascript-engine/external/boa/boa_engine/src/builtins/date/utils.rs
2023-01-20 22:36:19 +08:00

200 lines
7.3 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use chrono::{Datelike, Local, NaiveDateTime, TimeZone, Timelike};
use crate::value::IntegerOrNan;
/// The absolute maximum value of a timestamp
pub(super) const MAX_TIMESTAMP: i64 = 864 * 10i64.pow(13);
/// The number of milliseconds in a second.
pub(super) const MILLIS_PER_SECOND: i64 = 1000;
/// The number of milliseconds in a minute.
pub(super) const MILLIS_PER_MINUTE: i64 = MILLIS_PER_SECOND * 60;
/// The number of milliseconds in an hour.
pub(super) const MILLIS_PER_HOUR: i64 = MILLIS_PER_MINUTE * 60;
/// The number of milliseconds in a day.
pub(super) const MILLIS_PER_DAY: i64 = MILLIS_PER_HOUR * 24;
// https://tc39.es/ecma262/multipage/numbers-and-dates.html#sec-time-values-and-time-range
//
// The smaller range supported by a time value as specified in this section is approximately -273,790 to 273,790
// years relative to 1970.
pub(super) const MIN_YEAR: i64 = -300_000;
pub(super) const MAX_YEAR: i64 = -MIN_YEAR;
pub(super) const MIN_MONTH: i64 = MIN_YEAR * 12;
pub(super) const MAX_MONTH: i64 = MAX_YEAR * 12;
/// Calculates the absolute day number from the year number.
pub(super) const fn day_from_year(year: i64) -> i64 {
// Taken from https://chromium.googlesource.com/v8/v8/+/refs/heads/main/src/date/date.cc#496
// Useful to avoid negative divisions and overflows on 32-bit platforms (if we plan to support them).
const YEAR_DELTA: i64 = 399_999;
const fn day(year: i64) -> i64 {
let year = year + YEAR_DELTA;
365 * year + year / 4 - year / 100 + year / 400
}
assert!(MIN_YEAR <= year && year <= MAX_YEAR);
day(year) - day(1970)
}
/// Abstract operation [`MakeTime`][spec].
///
/// [spec]: https://tc39.es/ecma262/multipage/numbers-and-dates.html#sec-maketime
pub(super) fn make_time(hour: i64, min: i64, sec: i64, ms: i64) -> Option<i64> {
// 1. If hour is not finite or min is not finite or sec is not finite or ms is not finite, return NaN.
// 2. Let h be 𝔽(! ToIntegerOrInfinity(hour)).
// 3. Let m be 𝔽(! ToIntegerOrInfinity(min)).
// 4. Let s be 𝔽(! ToIntegerOrInfinity(sec)).
// 5. Let milli be 𝔽(! ToIntegerOrInfinity(ms)).
// 6. Let t be ((h * msPerHour + m * msPerMinute) + s * msPerSecond) + milli, performing the arithmetic according to IEEE 754-2019 rules (that is, as if using the ECMAScript operators * and +).
// 7. Return t.
let h_ms = hour.checked_mul(MILLIS_PER_HOUR)?;
let m_ms = min.checked_mul(MILLIS_PER_MINUTE)?;
let s_ms = sec.checked_mul(MILLIS_PER_SECOND)?;
h_ms.checked_add(m_ms)?.checked_add(s_ms)?.checked_add(ms)
}
/// Abstract operation [`MakeDay`][spec].
///
/// [spec]: https://tc39.es/ecma262/multipage/numbers-and-dates.html#sec-makeday
pub(super) fn make_day(mut year: i64, mut month: i64, date: i64) -> Option<i64> {
// 1. If year is not finite or month is not finite or date is not finite, return NaN.
// 2. Let y be 𝔽(! ToIntegerOrInfinity(year)).
// 3. Let m be 𝔽(! ToIntegerOrInfinity(month)).
// 4. Let dt be 𝔽(! ToIntegerOrInfinity(date)).
if !(MIN_YEAR..=MAX_YEAR).contains(&year) || !(MIN_MONTH..=MAX_MONTH).contains(&month) {
return None;
}
// At this point, we've already asserted that year and month are much less than its theoretical
// maximum and minimum values (i64::MAX/MIN), so we don't need to do checked operations.
// 5. Let ym be y + 𝔽(floor((m) / 12)).
// 6. If ym is not finite, return NaN.
year += month / 12;
// 7. Let mn be 𝔽((m) modulo 12).
month %= 12;
if month < 0 {
month += 12;
year -= 1;
}
// 8. Find a finite time value t such that YearFromTime(t) is ym and MonthFromTime(t) is mn and DateFromTime(t) is
// 1𝔽; but if this is not possible (because some argument is out of range), return NaN.
let month = usize::try_from(month).expect("month must be between 0 and 11 at this point");
let mut day = day_from_year(year);
// Consider leap years when calculating the cumulative days added to the year from the input month
if (year % 4 != 0) || (year % 100 == 0 && year % 400 != 0) {
day += [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334][month];
} else {
day += [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335][month];
}
// 9. Return Day(t) + dt - 1𝔽.
(day - 1).checked_add(date)
}
/// Abstract operation [`MakeDate`][spec].
///
/// [spec]: https://tc39.es/ecma262/multipage/numbers-and-dates.html#sec-makedate
pub(super) fn make_date(day: i64, time: i64) -> Option<i64> {
// 1. If day is not finite or time is not finite, return NaN.
// 2. Let tv be day × msPerDay + time.
// 3. If tv is not finite, return NaN.
// 4. Return tv.
day.checked_mul(MILLIS_PER_DAY)?.checked_add(time)
}
/// Abstract operation [`TimeClip`][spec]
/// Returns the timestamp (number of milliseconds) if it is in the expected range.
/// Otherwise, returns `None`.
///
/// [spec]: https://tc39.es/ecma262/#sec-timeclip
pub(super) fn time_clip(time: i64) -> Option<i64> {
// 1. If time is not finite, return NaN.
// 2. If abs((time)) > 8.64 × 10^15, return NaN.
// 3. Return 𝔽(! ToIntegerOrInfinity(time)).
(time.checked_abs()? <= MAX_TIMESTAMP).then_some(time)
}
#[derive(Default, Debug, Clone, Copy)]
pub(super) struct DateParameters {
pub(super) year: Option<IntegerOrNan>,
pub(super) month: Option<IntegerOrNan>,
pub(super) date: Option<IntegerOrNan>,
pub(super) hour: Option<IntegerOrNan>,
pub(super) minute: Option<IntegerOrNan>,
pub(super) second: Option<IntegerOrNan>,
pub(super) millisecond: Option<IntegerOrNan>,
}
/// Replaces some (or all) parameters of `date` with the specified parameters
pub(super) fn replace_params(
datetime: NaiveDateTime,
params: DateParameters,
local: bool,
) -> Option<NaiveDateTime> {
let DateParameters {
year,
month,
date,
hour,
minute,
second,
millisecond,
} = params;
let datetime = if local {
Local.from_utc_datetime(&datetime).naive_local()
} else {
datetime
};
let year = match year {
Some(i) => i.as_integer()?,
None => i64::from(datetime.year()),
};
let month = match month {
Some(i) => i.as_integer()?,
None => i64::from(datetime.month() - 1),
};
let date = match date {
Some(i) => i.as_integer()?,
None => i64::from(datetime.day()),
};
let hour = match hour {
Some(i) => i.as_integer()?,
None => i64::from(datetime.hour()),
};
let minute = match minute {
Some(i) => i.as_integer()?,
None => i64::from(datetime.minute()),
};
let second = match second {
Some(i) => i.as_integer()?,
None => i64::from(datetime.second()),
};
let millisecond = match millisecond {
Some(i) => i.as_integer()?,
None => i64::from(datetime.timestamp_subsec_millis()),
};
let new_day = make_day(year, month, date)?;
let new_time = make_time(hour, minute, second, millisecond)?;
let mut ts = make_date(new_day, new_time)?;
if local {
ts = Local
.from_local_datetime(&NaiveDateTime::from_timestamp_millis(ts)?)
.earliest()?
.naive_utc()
.timestamp_millis();
}
NaiveDateTime::from_timestamp_millis(time_clip(ts)?)
}