Skip to content

Commit bb7ddea

Browse files
committed
tmp
1 parent 209c6ad commit bb7ddea

1 file changed

Lines changed: 56 additions & 35 deletions

File tree

src/core/vm/temporal.rs

Lines changed: 56 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -499,11 +499,13 @@ impl<'gc> VM<'gc> {
499499
Ok(value) => value,
500500
Err(err) => return self.temporal_throw(ctx, err),
501501
};
502-
// Use to_plain_date_time to correctly handle non-ISO calendars:
503-
// value.year() is the calendar year (e.g. Buddhist 0 for ISO -543), not ISO year.
504-
// to_plain_date_time() works from the internal IsoDate directly.
505-
let date_time = value.to_plain_date_time(plain_time);
506-
match date_time.and_then(|value| value.to_zoned_date_time(time_zone, Disambiguation::Compatible)) {
502+
// Use PlainDate::to_zoned_date_time which handles two cases correctly:
503+
// 1. plain_time = None → uses get_start_of_day (spec-correct start-of-day)
504+
// 2. plain_time = Some → uses Compatible disambiguation for the given local time
505+
// Using to_plain_date_time() + to_zoned_date_time() would always go via midnight,
506+
// breaking start-of-day for timezones where midnight is in a DST gap.
507+
// PlainDate internally uses IsoDate so non-ISO calendar years are handled correctly.
508+
match value.to_zoned_date_time(time_zone, plain_time) {
507509
Ok(value) => {
508510
let ctor_value = self.temporal_intrinsic_ctor_value("ZonedDateTime");
509511
self.temporal_wrap_zoned_date_time(ctx, ctor_value.as_ref(), &value)
@@ -1863,25 +1865,25 @@ impl<'gc> VM<'gc> {
18631865
Ok(value) => value,
18641866
Err(err) => return self.temporal_throw(ctx, err),
18651867
};
1866-
let self_ref = self
1867-
.temporal_plain_year_month_reference_day_slot(receiver)
1868-
.unwrap_or_else(|| value.reference_day());
1869-
let other_ref = self
1870-
.temporal_plain_year_month_reference_day_slot(Some(arg))
1871-
.unwrap_or_else(|| other.reference_day());
1872-
Value::Boolean(
1873-
(
1874-
value.year(),
1875-
value.month(),
1876-
self_ref,
1877-
Self::temporal_canonical_calendar_id(value.calendar().identifier()),
1878-
) == (
1879-
other.year(),
1880-
other.month(),
1881-
other_ref,
1882-
Self::temporal_canonical_calendar_id(other.calendar().identifier()),
1883-
),
1884-
)
1868+
// Spec: PlainYearMonth.equals compares [[ISODate]] + calendar identifier.
1869+
// Use to_ixdtf_string(Always) to get the canonical ISO date representation
1870+
// (includes the ISO day for all calendars). Canonicalize calendar IDs before
1871+
// comparing so aliases like "islamicc" == "islamic-civil" are treated as equal.
1872+
let canonicalize_pym_repr = |s: String| -> String {
1873+
// Replace calendar ID in "[u-ca=ID]" annotation with canonical form
1874+
if let Some(bracket_pos) = s.find('[') {
1875+
let date_part = &s[..bracket_pos];
1876+
let inner = &s[bracket_pos + 1..s.len().saturating_sub(1)];
1877+
let cal_id = inner.strip_prefix("u-ca=").unwrap_or(inner);
1878+
let canonical = Self::temporal_canonical_calendar_id(cal_id);
1879+
format!("{date_part}[u-ca={canonical}]")
1880+
} else {
1881+
s
1882+
}
1883+
};
1884+
let self_str = canonicalize_pym_repr(value.to_ixdtf_string(DisplayCalendar::Always));
1885+
let other_str = canonicalize_pym_repr(other.to_ixdtf_string(DisplayCalendar::Always));
1886+
Value::Boolean(self_str == other_str)
18851887
}
18861888
"temporal.plainYearMonth.toString" => self.temporal_plain_year_month_to_string(ctx, receiver, args.first()),
18871889
"temporal.plainYearMonth.toLocaleString" => {
@@ -2052,7 +2054,8 @@ impl<'gc> VM<'gc> {
20522054
let Some(value) = self.temporal_expect_plain_month_day(ctx, receiver) else {
20532055
return Value::Undefined;
20542056
};
2055-
let fields = match self.temporal_plain_month_day_to_plain_date_arg(ctx, args.first()) {
2057+
let pmd_calendar = value.calendar().clone();
2058+
let fields = match self.temporal_plain_month_day_to_plain_date_arg(ctx, args.first(), &pmd_calendar) {
20562059
Ok(value) => value,
20572060
Err(err) => return self.temporal_throw(ctx, err),
20582061
};
@@ -4322,22 +4325,28 @@ impl<'gc> VM<'gc> {
43224325
&mut self,
43234326
ctx: &GcContext<'gc>,
43244327
value: Option<&Value<'gc>>,
4328+
pmd_calendar: &Calendar,
43254329
) -> Result<CalendarFields, TemporalError> {
43264330
let Some(value) = value else {
43274331
return Err(TemporalError::r#type().with_message("Temporal.PlainMonthDay.prototype.toPlainDate requires an object"));
43284332
};
43294333
if !self.temporal_is_object_like(value) {
43304334
return Err(TemporalError::r#type().with_message("Temporal.PlainMonthDay.prototype.toPlainDate requires an object"));
43314335
}
4332-
// Read era/eraYear first (before year) so that eraYear: Infinity throws RangeError,
4333-
// not TypeError from the missing-year check below.
4334-
let dummy_era_calendar = Calendar::try_from_utf8(b"gregory").unwrap_or_default();
4335-
let (era, era_year) = self.temporal_read_era_fields_if_needed(ctx, value, &dummy_era_calendar, "Invalid Temporal input")?;
4336+
// For ISO calendars, spec says only `year` is read. Reading era/eraYear changes
4337+
// the operation order vs the spec (which reads only get year, year.valueOf).
4338+
if *pmd_calendar == Calendar::ISO {
4339+
let year = self
4340+
.temporal_optional_trunc_i32_property(ctx, value, "year")?
4341+
.ok_or_else(|| TemporalError::r#type().with_message("year is required"))?;
4342+
return Ok(CalendarFields::new().with_year(year));
4343+
}
4344+
// Non-ISO: read era/eraYear FIRST so that eraYear: Infinity throws RangeError
4345+
// before the missing-year TypeError.
4346+
let (era, era_year) = self.temporal_read_era_fields_if_needed(ctx, value, pmd_calendar, "Invalid Temporal input")?;
43364347
let year = self.temporal_optional_trunc_i32_property(ctx, value, "year")?;
4337-
// If era+eraYear provided, build a fields object so the calendar resolves the ISO year.
43384348
if era.is_some() || era_year.is_some() {
4339-
let fields = CalendarFields::new().with_era(era).with_era_year(era_year).with_optional_year(year);
4340-
return Ok(fields);
4349+
return Ok(CalendarFields::new().with_era(era).with_era_year(era_year).with_optional_year(year));
43414350
}
43424351
let year = year.ok_or_else(|| TemporalError::r#type().with_message("year is required"))?;
43434352
Ok(CalendarFields::new().with_year(year))
@@ -5610,11 +5619,13 @@ impl<'gc> VM<'gc> {
56105619
let valid = if let Some(rest) = value.strip_prefix('+').or_else(|| value.strip_prefix('-')) {
56115620
let base = rest.split('.').next().unwrap_or(rest);
56125621
let fraction = rest.strip_prefix(base).unwrap_or("");
5622+
// Fractions (.N) are only allowed when seconds are present (HH:MM:SS or HHMMSS formats).
5623+
let has_seconds = matches!(base.as_bytes(), [_, _, b':', _, _, b':', _, _] | [_, _, _, _, _, _]);
56135624
let valid_basic = matches!(base.as_bytes(), [a, b, b':', c, d] if a.is_ascii_digit() && b.is_ascii_digit() && c.is_ascii_digit() && d.is_ascii_digit())
56145625
|| matches!(base.as_bytes(), [a, b, c, d] if a.is_ascii_digit() && b.is_ascii_digit() && c.is_ascii_digit() && d.is_ascii_digit())
56155626
|| matches!(base.as_bytes(), [a, b, b':', c, d, b':', e, f] if a.is_ascii_digit() && b.is_ascii_digit() && c.is_ascii_digit() && d.is_ascii_digit() && *e == b'0' && *f == b'0')
56165627
|| matches!(base.as_bytes(), [a, b, c, d, e, f] if a.is_ascii_digit() && b.is_ascii_digit() && c.is_ascii_digit() && d.is_ascii_digit() && *e == b'0' && *f == b'0');
5617-
valid_basic && fraction.chars().all(|ch| ch == '.' || ch == '0')
5628+
valid_basic && (fraction.is_empty() || (has_seconds && fraction.chars().all(|ch| ch == '.' || ch == '0')))
56185629
} else {
56195630
false
56205631
};
@@ -6586,9 +6597,19 @@ impl<'gc> VM<'gc> {
65866597

65876598
if let Some(reference_iso_date) = reference_iso_date
65886599
&& let Ok(reference_date) = PlainDate::from_utf8(reference_iso_date.as_bytes())
6589-
&& let Ok(reconstructed) = reference_date.with_calendar(calendar.clone()).to_plain_year_month()
65906600
{
6591-
return Some(reconstructed);
6601+
// reference_date is in ISO calendar; its year/month/day ARE the ISO components.
6602+
// Build PYM directly with these ISO components so iso.day is preserved
6603+
// (going through to_plain_year_month() drops the day, normalizing it to 1).
6604+
if let Ok(reconstructed) = PlainYearMonth::new_with_overflow(
6605+
reference_date.year(), // ISO year (ISO calendar: year() == iso.year)
6606+
reference_date.month(), // ISO month
6607+
Some(reference_date.day()), // ISO day preserved
6608+
calendar.clone(),
6609+
Overflow::Constrain,
6610+
) {
6611+
return Some(reconstructed);
6612+
}
65926613
}
65936614

65946615
let partial = PartialYearMonth {

0 commit comments

Comments
 (0)