@@ -3665,6 +3665,9 @@ export function RejectDuration(y, mon, w, d, h, min, s, ms, µs, ns) {
3665
3665
const propSign = MathSign ( prop ) ;
3666
3666
if ( propSign !== 0 && propSign !== sign ) throw new RangeError ( 'mixed-sign values not allowed as duration fields' ) ;
3667
3667
}
3668
+ if ( MathAbs ( y ) >= 2 ** 32 || MathAbs ( mon ) >= 2 ** 32 || MathAbs ( w ) >= 2 ** 32 ) {
3669
+ throw new RangeError ( 'years, months, and weeks must be < 2³²' ) ;
3670
+ }
3668
3671
const msResult = TruncatingDivModByPowerOf10 ( ms , 3 ) ;
3669
3672
const µsResult = TruncatingDivModByPowerOf10 ( µs , 6 ) ;
3670
3673
const nsResult = TruncatingDivModByPowerOf10 ( ns , 9 ) ;
@@ -5014,6 +5017,49 @@ export function RoundNumberToIncrement(quantity, increment, mode) {
5014
5017
return quotient . multiply ( increment ) ;
5015
5018
}
5016
5019
5020
+ export function RoundJSNumberToIncrement ( quantity , increment , mode ) {
5021
+ let quotient = MathTrunc ( quantity / increment ) ;
5022
+ const remainder = quantity % increment ;
5023
+ if ( remainder === 0 ) return quantity ;
5024
+ const sign = remainder < 0 ? - 1 : 1 ;
5025
+ const tiebreaker = MathAbs ( remainder * 2 ) ;
5026
+ const tie = tiebreaker === increment ;
5027
+ const expandIsNearer = tiebreaker > increment ;
5028
+ switch ( mode ) {
5029
+ case 'ceil' :
5030
+ if ( sign > 0 ) quotient += sign ;
5031
+ break ;
5032
+ case 'floor' :
5033
+ if ( sign < 0 ) quotient += sign ;
5034
+ break ;
5035
+ case 'expand' :
5036
+ // always expand if there is a remainder
5037
+ quotient += sign ;
5038
+ break ;
5039
+ case 'trunc' :
5040
+ // no change needed, because divmod is a truncation
5041
+ break ;
5042
+ case 'halfCeil' :
5043
+ if ( expandIsNearer || ( tie && sign > 0 ) ) quotient += sign ;
5044
+ break ;
5045
+ case 'halfFloor' :
5046
+ if ( expandIsNearer || ( tie && sign < 0 ) ) quotient += sign ;
5047
+ break ;
5048
+ case 'halfExpand' :
5049
+ // "half up away from zero"
5050
+ if ( expandIsNearer || tie ) quotient += sign ;
5051
+ break ;
5052
+ case 'halfTrunc' :
5053
+ if ( expandIsNearer ) quotient += sign ;
5054
+ break ;
5055
+ case 'halfEven' : {
5056
+ if ( expandIsNearer || ( tie && quotient % 2 === 1 ) ) quotient += sign ;
5057
+ break ;
5058
+ }
5059
+ }
5060
+ return quotient * increment ;
5061
+ }
5062
+
5017
5063
export function RoundInstant ( epochNs , increment , unit , roundingMode ) {
5018
5064
let { remainder } = NonNegativeBigIntDivmod ( epochNs , DAY_NANOS ) ;
5019
5065
const wholeDays = epochNs . minus ( remainder ) ;
@@ -5328,20 +5374,10 @@ export function RoundDuration(
5328
5374
const oneYear = new TemporalDuration ( days < 0 ? - 1 : 1 ) ;
5329
5375
let { days : oneYearDays } = MoveRelativeDate ( calendarRec , plainRelativeTo , oneYear ) ;
5330
5376
5331
- // Note that `nanoseconds` below (here and in similar code for months,
5332
- // weeks, and days further below) isn't actually nanoseconds for the
5333
- // full date range. Instead, it's a BigInt representation of total
5334
- // days multiplied by the number of nanoseconds in the last day of
5335
- // the duration. This lets us do days-or-larger rounding using BigInt
5336
- // math which reduces precision loss.
5337
5377
oneYearDays = MathAbs ( oneYearDays ) ;
5338
5378
if ( oneYearDays === 0 ) throw new RangeError ( 'custom calendar reported that a year is 0 days long' ) ;
5339
- const divisor = bigInt ( oneYearDays ) . multiply ( dayLengthNs ) ;
5340
- const nanoseconds = divisor . multiply ( years ) . plus ( bigInt ( days ) . multiply ( dayLengthNs ) ) . plus ( norm . totalNs ) ;
5341
- const rounded = RoundNumberToIncrement ( nanoseconds , divisor . multiply ( increment ) . toJSNumber ( ) , roundingMode ) ;
5342
- const { quotient, remainder } = nanoseconds . divmod ( divisor ) ;
5343
- total = quotient . toJSNumber ( ) + remainder . toJSNumber ( ) / divisor ;
5344
- years = rounded . divide ( divisor ) . toJSNumber ( ) ;
5379
+ total = years + ( days + norm . fdiv ( dayLengthNs ) ) / oneYearDays ;
5380
+ years = RoundJSNumberToIncrement ( total , increment , roundingMode ) ;
5345
5381
months = weeks = days = 0 ;
5346
5382
norm = TimeDuration . ZERO ;
5347
5383
break ;
@@ -5385,12 +5421,8 @@ export function RoundDuration(
5385
5421
5386
5422
oneMonthDays = MathAbs ( oneMonthDays ) ;
5387
5423
if ( oneMonthDays === 0 ) throw new RangeError ( 'custom calendar reported that a month is 0 days long' ) ;
5388
- const divisor = bigInt ( oneMonthDays ) . multiply ( dayLengthNs ) ;
5389
- const nanoseconds = divisor . multiply ( months ) . plus ( bigInt ( days ) . multiply ( dayLengthNs ) ) . plus ( norm . totalNs ) ;
5390
- const rounded = RoundNumberToIncrement ( nanoseconds , divisor . multiply ( increment ) , roundingMode ) ;
5391
- const { quotient, remainder } = nanoseconds . divmod ( divisor ) ;
5392
- total = quotient . toJSNumber ( ) + remainder . toJSNumber ( ) / divisor ;
5393
- months = rounded . divide ( divisor ) . toJSNumber ( ) ;
5424
+ total = months + ( days + norm . fdiv ( dayLengthNs ) ) / oneMonthDays ;
5425
+ months = RoundJSNumberToIncrement ( total , increment , roundingMode ) ;
5394
5426
weeks = days = 0 ;
5395
5427
norm = TimeDuration . ZERO ;
5396
5428
break ;
@@ -5424,23 +5456,15 @@ export function RoundDuration(
5424
5456
5425
5457
oneWeekDays = MathAbs ( oneWeekDays ) ;
5426
5458
if ( oneWeekDays === 0 ) throw new RangeError ( 'custom calendar reported that a week is 0 days long' ) ;
5427
- const divisor = bigInt ( oneWeekDays ) . multiply ( dayLengthNs ) ;
5428
- const nanoseconds = divisor . multiply ( weeks ) . plus ( bigInt ( days ) . multiply ( dayLengthNs ) ) . plus ( norm . totalNs ) ;
5429
- const rounded = RoundNumberToIncrement ( nanoseconds , divisor . multiply ( increment ) , roundingMode ) ;
5430
- const { quotient, remainder } = nanoseconds . divmod ( divisor ) ;
5431
- total = quotient . toJSNumber ( ) + remainder . toJSNumber ( ) / divisor ;
5432
- weeks = rounded . divide ( divisor ) . toJSNumber ( ) ;
5459
+ total = weeks + ( days + norm . fdiv ( dayLengthNs ) ) / oneWeekDays ;
5460
+ weeks = RoundJSNumberToIncrement ( total , increment , roundingMode ) ;
5433
5461
days = 0 ;
5434
5462
norm = TimeDuration . ZERO ;
5435
5463
break ;
5436
5464
}
5437
5465
case 'day' : {
5438
- const divisor = bigInt ( dayLengthNs ) ;
5439
- const nanoseconds = divisor . multiply ( days ) . plus ( norm . totalNs ) ;
5440
- const rounded = RoundNumberToIncrement ( nanoseconds , divisor . multiply ( increment ) , roundingMode ) ;
5441
- const { quotient, remainder } = nanoseconds . divmod ( divisor ) ;
5442
- total = quotient . toJSNumber ( ) + remainder . toJSNumber ( ) / divisor ;
5443
- days = rounded . divide ( divisor ) . toJSNumber ( ) ;
5466
+ total = days + norm . fdiv ( dayLengthNs ) ;
5467
+ days = RoundJSNumberToIncrement ( total , increment , roundingMode ) ;
5444
5468
norm = TimeDuration . ZERO ;
5445
5469
break ;
5446
5470
}
0 commit comments