@@ -17,8 +17,8 @@ use crate::{
1717 options:: {
1818 ArithmeticOverflow , DifferenceOperation , DifferenceSettings , Disambiguation ,
1919 DisplayCalendar , DisplayOffset , DisplayTimeZone , OffsetDisambiguation ,
20- ResolvedRoundingOptions , RoundingIncrement , RoundingMode , ToStringRoundingOptions , Unit ,
21- UnitGroup ,
20+ ResolvedRoundingOptions , RoundingIncrement , RoundingMode , RoundingOptions ,
21+ ToStringRoundingOptions , Unit , UnitGroup ,
2222 } ,
2323 parsers:: { self , FormattableOffset , FormattableTime , IxdtfStringBuilder , Precision } ,
2424 partial:: { PartialDate , PartialTime } ,
@@ -1023,6 +1023,113 @@ impl ZonedDateTime {
10231023 )
10241024 }
10251025
1026+ /// 6.3.39 Temporal.ZonedDateTime.prototype.round
1027+ pub fn round_with_provider (
1028+ & self ,
1029+ options : RoundingOptions ,
1030+ provider : & impl TimeZoneProvider ,
1031+ ) -> TemporalResult < Self > {
1032+ // 1. Let zonedDateTime be the this value.
1033+ // 2. Perform ? RequireInternalSlot(zonedDateTime, [[InitializedTemporalZonedDateTime]]).
1034+ // 3. If roundTo is undefined, then
1035+ // a. Throw a TypeError exception.
1036+ // 4. If roundTo is a String, then
1037+ // a. Let paramString be roundTo.
1038+ // b. Set roundTo to OrdinaryObjectCreate(null).
1039+ // c. Perform ! CreateDataPropertyOrThrow(roundTo, "smallestUnit", paramString).
1040+ // 5. Else,
1041+ // a. Set roundTo to ? GetOptionsObject(roundTo).
1042+ // 6. NOTE: The following steps read options and perform independent validation in alphabetical order (GetRoundingIncrementOption reads "roundingIncrement" and GetRoundingModeOption reads "roundingMode").
1043+ // 7. Let roundingIncrement be ? GetRoundingIncrementOption(roundTo).
1044+ // 8. Let roundingMode be ? GetRoundingModeOption(roundTo, half-expand).
1045+ // 9. Let smallestUnit be ? GetTemporalUnitValuedOption(roundTo, "smallestUnit", time, required, « day »).
1046+ // 10. If smallestUnit is day, then
1047+ // a. Let maximum be 1.
1048+ // b. Let inclusive be true.
1049+ // 11. Else,
1050+ // a. Let maximum be MaximumTemporalDurationRoundingIncrement(smallestUnit).
1051+ // b. Assert: maximum is not unset.
1052+ // c. Let inclusive be false.
1053+ let resolved = ResolvedRoundingOptions :: from_datetime_options ( options) ?;
1054+ // 12. Perform ? ValidateTemporalRoundingIncrement(roundingIncrement, maximum, inclusive).
1055+ // 13. If maximum is not unset, perform ? ValidateTemporalRoundingIncrement(roundingIncrement, maximum, false).
1056+ // 13. If smallestUnit is nanosecond and roundingIncrement = 1, then
1057+ if resolved. smallest_unit == Unit :: Nanosecond
1058+ && resolved. increment == RoundingIncrement :: ONE
1059+ {
1060+ // a. Return ! CreateTemporalZonedDateTime(zonedDateTime.[[EpochNanoseconds]], zonedDateTime.[[TimeZone]], zonedDateTime.[[Calendar]]).
1061+ return Ok ( self . clone ( ) ) ;
1062+ }
1063+ // 14. Let thisNs be zonedDateTime.[[EpochNanoseconds]].
1064+ let this_ns = self . epoch_nanoseconds ( ) ;
1065+ // 15. Let timeZone be zonedDateTime.[[TimeZone]].
1066+ // 16. Let calendar be zonedDateTime.[[Calendar]].
1067+ // 17. Let isoDateTime be GetISODateTimeFor(timeZone, thisNs).
1068+ // 18. If smallestUnit is day, then
1069+ if resolved. smallest_unit == Unit :: Day {
1070+ // a. Let dateStart be isoDateTime.[[ISODate]].
1071+ let iso_start = self . tz . get_iso_datetime_for ( & self . instant , provider) ?;
1072+ // b. Let dateEnd be BalanceISODate(dateStart.[[Year]], dateStart.[[Month]], dateStart.[[Day]] + 1).
1073+ let iso_end = IsoDate :: balance (
1074+ iso_start. date . year ,
1075+ iso_start. date . month . into ( ) ,
1076+ i32:: from ( iso_start. date . day + 1 ) ,
1077+ ) ;
1078+ // c. Let startNs be ? GetStartOfDay(timeZone, dateStart).
1079+ // d. Assert: thisNs ≥ startNs.
1080+ // e. Let endNs be ? GetStartOfDay(timeZone, dateEnd).
1081+ // f. Assert: thisNs < endNs.
1082+ let start_ns = self . tz . get_start_of_day ( & iso_start. date , provider) ?;
1083+ let end_ns = self . tz . get_start_of_day ( & iso_end, provider) ?;
1084+ if !( this_ns. 0 >= start_ns. 0 && this_ns. 0 < end_ns. 0 ) {
1085+ return Err ( TemporalError :: range ( )
1086+ . with_message ( "ZonedDateTime is outside the expected day bounds" ) ) ;
1087+ }
1088+ // g. Let dayLengthNs be ℝ(endNs - startNs).
1089+ // h. Let dayProgressNs be TimeDurationFromEpochNanosecondsDifference(thisNs, startNs).
1090+ let day_len_ns =
1091+ NormalizedTimeDuration :: from_nanosecond_difference ( end_ns. 0 , start_ns. 0 ) ?;
1092+ let day_progress_ns =
1093+ NormalizedTimeDuration :: from_nanosecond_difference ( this_ns. 0 , start_ns. 0 ) ?;
1094+ // i. Let roundedDayNs be ! RoundTimeDurationToIncrement(dayProgressNs, dayLengthNs, roundingMode).
1095+ let rounded = if let Some ( increment) = NonZeroU128 :: new ( day_len_ns. 0 . unsigned_abs ( ) ) {
1096+ IncrementRounder :: < i128 > :: from_signed_num ( day_progress_ns. 0 , increment) ?
1097+ . round ( resolved. rounding_mode )
1098+ } else {
1099+ 0 // Zero-length day: round to start of day
1100+ } ;
1101+
1102+ // j. Let epochNanoseconds be AddTimeDurationToEpochNanoseconds(roundedDayNs, startNs).
1103+ let candidate = start_ns. 0 + rounded;
1104+ Instant :: try_new ( candidate) ?;
1105+ // 20. Return ! CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar).
1106+ ZonedDateTime :: try_new ( candidate, self . calendar . clone ( ) , self . tz . clone ( ) )
1107+ } else {
1108+ // 19. Else,
1109+ // a. Let roundResult be RoundISODateTime(isoDateTime, roundingIncrement, smallestUnit, roundingMode).
1110+ // b. Let offsetNanoseconds be GetOffsetNanosecondsFor(timeZone, thisNs).
1111+ // c. Let epochNanoseconds be ? InterpretISODateTimeOffset(roundResult.[[ISODate]], roundResult.[[Time]], option, offsetNanoseconds, timeZone, compatible, prefer, match-exactly).
1112+ // 20. Return ! CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar).
1113+ let iso_dt = self . tz . get_iso_datetime_for ( & self . instant , provider) ?;
1114+ let rounded_dt = iso_dt. round ( resolved) ?;
1115+ let offset_ns = self . tz . get_offset_nanos_for ( this_ns. as_i128 ( ) , provider) ?;
1116+
1117+ let epoch_ns = interpret_isodatetime_offset (
1118+ rounded_dt. date ,
1119+ Some ( rounded_dt. time ) ,
1120+ false ,
1121+ Some ( offset_ns as i64 ) ,
1122+ & self . tz ,
1123+ Disambiguation :: Compatible ,
1124+ OffsetDisambiguation :: Prefer ,
1125+ true ,
1126+ provider,
1127+ ) ?;
1128+
1129+ ZonedDateTime :: try_new ( epoch_ns. 0 , self . calendar . clone ( ) , self . tz . clone ( ) )
1130+ }
1131+ }
1132+
10261133 /// Creates an IXDTF (RFC 9557) date/time string for the provided `ZonedDateTime` according
10271134 /// to the provided display options.
10281135 pub fn to_ixdtf_string_with_provider (
@@ -1138,6 +1245,7 @@ impl ZonedDateTime {
11381245 }
11391246}
11401247
1248+ /// InterpretISODateTimeOffset
11411249#[ allow( clippy:: too_many_arguments) ]
11421250pub ( crate ) fn interpret_isodatetime_offset (
11431251 date : IsoDate ,
@@ -1275,7 +1383,8 @@ mod tests {
12751383 use super :: ZonedDateTime ;
12761384 use crate :: {
12771385 options:: {
1278- ArithmeticOverflow , DifferenceSettings , Disambiguation , OffsetDisambiguation , Unit ,
1386+ ArithmeticOverflow , DifferenceSettings , Disambiguation , OffsetDisambiguation ,
1387+ RoundingIncrement , RoundingMode , RoundingOptions , Unit ,
12791388 } ,
12801389 partial:: { PartialDate , PartialTime , PartialZonedDateTime } ,
12811390 time:: EpochNanoseconds ,
@@ -1333,6 +1442,65 @@ mod tests {
13331442 assert_eq ! ( zdt_plus_eleven. second_with_provider( provider) . unwrap( ) , 12 ) ;
13341443 }
13351444
1445+ #[ test]
1446+ // https://tc39.es/proposal-temporal/docs/zoneddatetime.html#round
1447+ fn round_with_provider_test ( ) {
1448+ let provider = & FsTzdbProvider :: default ( ) ;
1449+ let dt = "1995-12-07T03:24:30.000003500-08:00[America/Los_Angeles]" ;
1450+ let zdt = ZonedDateTime :: from_str_with_provider (
1451+ dt,
1452+ Disambiguation :: default ( ) ,
1453+ OffsetDisambiguation :: Use ,
1454+ provider,
1455+ )
1456+ . unwrap ( ) ;
1457+
1458+ let result = zdt
1459+ . round_with_provider (
1460+ RoundingOptions {
1461+ smallest_unit : Some ( Unit :: Hour ) ,
1462+ ..Default :: default ( )
1463+ } ,
1464+ provider,
1465+ )
1466+ . unwrap ( ) ;
1467+ assert_eq ! (
1468+ result. to_string_with_provider( provider) . unwrap( ) ,
1469+ "1995-12-07T03:00:00-08:00[America/Los_Angeles]"
1470+ ) ;
1471+
1472+ let result = zdt
1473+ . round_with_provider (
1474+ RoundingOptions {
1475+ smallest_unit : Some ( Unit :: Minute ) ,
1476+ increment : Some ( ( RoundingIncrement :: try_new ( 30 ) ) . unwrap ( ) ) ,
1477+ ..Default :: default ( )
1478+ } ,
1479+ provider,
1480+ )
1481+ . unwrap ( ) ;
1482+ assert_eq ! (
1483+ result. to_string_with_provider( provider) . unwrap( ) ,
1484+ "1995-12-07T03:30:00-08:00[America/Los_Angeles]"
1485+ ) ;
1486+
1487+ let result = zdt
1488+ . round_with_provider (
1489+ RoundingOptions {
1490+ smallest_unit : Some ( Unit :: Minute ) ,
1491+ increment : Some ( ( RoundingIncrement :: try_new ( 30 ) ) . unwrap ( ) ) ,
1492+ rounding_mode : Some ( RoundingMode :: Floor ) ,
1493+ ..Default :: default ( )
1494+ } ,
1495+ provider,
1496+ )
1497+ . unwrap ( ) ;
1498+ assert_eq ! (
1499+ result. to_string_with_provider( provider) . unwrap( ) ,
1500+ "1995-12-07T03:00:00-08:00[America/Los_Angeles]"
1501+ ) ;
1502+ }
1503+
13361504 #[ test]
13371505 fn zdt_from_partial ( ) {
13381506 let provider = & FsTzdbProvider :: default ( ) ;
0 commit comments