Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 10 additions & 7 deletions src/builtins/core/zoned_date_time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1301,17 +1301,20 @@ impl ZonedDateTime {
// c. Let startNs be ? GetStartOfDay(timeZone, dateStart).
// d. Assert: thisNs ≥ startNs.
// e. Let endNs be ? GetStartOfDay(timeZone, dateEnd).
// f. Assert: thisNs < endNs.
// f. [Struck out in spec] Assert: thisNs < endNs.
let start = self.time_zone.get_start_of_day(&iso_start.date, provider)?;
let end = self.time_zone.get_start_of_day(&iso_end, provider)?;
if !(this_ns.0 >= start.ns.0 && this_ns.0 < end.ns.0) {
if this_ns.0 < start.ns.0 {
return Err(TemporalError::range().with_enum(ErrorMessage::ZDTOutOfDayBounds));
}
// g. Let dayLengthNs be ℝ(endNs - startNs).
// h. Let dayProgressNs be TimeDurationFromEpochNanosecondsDifference(thisNs, startNs).
// g. Set thisNs to min(thisNs, endNs - 1).
let this_ns_val = this_ns.0.min(end.ns.0 - 1);
// h. Let dayLengthNs be ℝ(endNs - startNs).
// i. Let dayProgressNs be TimeDurationFromEpochNanosecondsDifference(thisNs, startNs).
let day_len_ns = TimeDuration::from_nanosecond_difference(end.ns.0, start.ns.0)?;
let day_progress_ns = TimeDuration::from_nanosecond_difference(this_ns.0, start.ns.0)?;
// i. Let roundedDayNs be ! RoundTimeDurationToIncrement(dayProgressNs, dayLengthNs, roundingMode).
let day_progress_ns =
TimeDuration::from_nanosecond_difference(this_ns_val, start.ns.0)?;
// j. Let roundedDayNs be ! RoundTimeDurationToIncrement(dayProgressNs, dayLengthNs, roundingMode).
let rounded = if let Some(increment) = NonZeroU128::new(day_len_ns.0.unsigned_abs()) {
IncrementRounder::<i128>::from_signed_num(day_progress_ns.0, increment)?
.round(resolved.rounding_mode)
Expand All @@ -1326,7 +1329,7 @@ impl ZonedDateTime {
end.offset
};

// j. Let epochNanoseconds be AddTimeDurationToEpochNanoseconds(roundedDayNs, startNs).
// k. Let epochNanoseconds be AddTimeDurationToEpochNanoseconds(roundedDayNs, startNs).
let candidate = start.ns.0 + rounded;
Instant::try_new(candidate)?;
// 20. Return ! CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar).
Expand Down
101 changes: 101 additions & 0 deletions src/builtins/core/zoned_date_time/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1278,3 +1278,104 @@ fn hours_in_day_dst_changes() {
assert_eq!(spring.hours_in_day_with_provider(provider), Ok(23.0));
})
}

// Case where midnight occurs twice (e.g., Antarctica/Casey on 2010-03-05).
// Upstream tests: https://github.com/tc39/test262/pull/5047
#[test]
fn test_same_date_starts_twice() {
test_all_providers!(provider: {
let zdt1 = ZonedDateTime::from_utf8_with_provider(
b"2010-03-04T23:10:00+11:00[Antarctica/Casey]",
Disambiguation::Compatible,
OffsetDisambiguation::Use,
provider,
);
if zdt1.is_err() {
std::println!("Antarctica/Casey not supported by provider, skipping test.");
return;
}
let zdt1 = zdt1.unwrap();

let zdt2 = ZonedDateTime::from_utf8_with_provider(
b"2010-03-05T00:45:00+11:00[Antarctica/Casey]",
Disambiguation::Compatible,
OffsetDisambiguation::Use,
provider,
).unwrap();

let zdt3 = ZonedDateTime::from_utf8_with_provider(
b"2010-03-04T23:10:00+08:00[Antarctica/Casey]",
Disambiguation::Compatible,
OffsetDisambiguation::Use,
provider,
).unwrap();

let zdt4 = ZonedDateTime::from_utf8_with_provider(
b"2010-03-05T00:45:00+08:00[Antarctica/Casey]",
Disambiguation::Compatible,
OffsetDisambiguation::Use,
provider,
).unwrap();

let start_of_march4 = "2010-03-04T00:00:00+11:00[Antarctica/Casey]";
let start_of_march5 = "2010-03-05T00:00:00+11:00[Antarctica/Casey]";
let start_of_march6 = "2010-03-06T00:00:00+08:00[Antarctica/Casey]";

// Hours in day
assert_eq!(zdt1.hours_in_day_with_provider(provider).unwrap(), 24.0);
assert_eq!(zdt2.hours_in_day_with_provider(provider).unwrap(), 27.0);
assert_eq!(zdt3.hours_in_day_with_provider(provider).unwrap(), 24.0);
assert_eq!(zdt4.hours_in_day_with_provider(provider).unwrap(), 27.0);

// Start of day
assert_eq!(zdt1.start_of_day_with_provider(provider).unwrap().to_string_with_provider(provider).unwrap(), start_of_march4);
assert_eq!(zdt2.start_of_day_with_provider(provider).unwrap().to_string_with_provider(provider).unwrap(), start_of_march5);
assert_eq!(zdt3.start_of_day_with_provider(provider).unwrap().to_string_with_provider(provider).unwrap(), start_of_march4);
assert_eq!(zdt4.start_of_day_with_provider(provider).unwrap().to_string_with_provider(provider).unwrap(), start_of_march5);

// Rounding down
for rounding_mode in [RoundingMode::Floor, RoundingMode::Trunc] {
let options = RoundingOptions {
smallest_unit: Some(Unit::Day),
rounding_mode: Some(rounding_mode),
..Default::default()
};
assert_eq!(zdt1.round_with_provider(options, provider).unwrap().to_string_with_provider(provider).unwrap(), start_of_march4);
assert_eq!(zdt2.round_with_provider(options, provider).unwrap().to_string_with_provider(provider).unwrap(), start_of_march5);
assert_eq!(zdt3.round_with_provider(options, provider).unwrap().to_string_with_provider(provider).unwrap(), start_of_march4);
assert_eq!(zdt4.round_with_provider(options, provider).unwrap().to_string_with_provider(provider).unwrap(), start_of_march5);
}

// Rounding to nearest
for rounding_mode in [
RoundingMode::HalfCeil,
RoundingMode::HalfEven,
RoundingMode::HalfExpand,
RoundingMode::HalfFloor,
RoundingMode::HalfTrunc,
] {
let options = RoundingOptions {
smallest_unit: Some(Unit::Day),
rounding_mode: Some(rounding_mode),
..Default::default()
};
assert_eq!(zdt1.round_with_provider(options, provider).unwrap().to_string_with_provider(provider).unwrap(), start_of_march5);
assert_eq!(zdt2.round_with_provider(options, provider).unwrap().to_string_with_provider(provider).unwrap(), start_of_march5);
assert_eq!(zdt3.round_with_provider(options, provider).unwrap().to_string_with_provider(provider).unwrap(), start_of_march5);
assert_eq!(zdt4.round_with_provider(options, provider).unwrap().to_string_with_provider(provider).unwrap(), start_of_march5);
}

// Rounding up
for rounding_mode in [RoundingMode::Ceil, RoundingMode::Expand] {
let options = RoundingOptions {
smallest_unit: Some(Unit::Day),
rounding_mode: Some(rounding_mode),
..Default::default()
};
assert_eq!(zdt1.round_with_provider(options, provider).unwrap().to_string_with_provider(provider).unwrap(), start_of_march5);
assert_eq!(zdt2.round_with_provider(options, provider).unwrap().to_string_with_provider(provider).unwrap(), start_of_march6);
assert_eq!(zdt3.round_with_provider(options, provider).unwrap().to_string_with_provider(provider).unwrap(), start_of_march5);
assert_eq!(zdt4.round_with_provider(options, provider).unwrap().to_string_with_provider(provider).unwrap(), start_of_march6);
}
})
}
Loading