@@ -119,101 +119,68 @@ export function elapsedTime(date: Date, precision: Unit = 'second', now = Date.n
119119 )
120120}
121121
122+ const durationRoundingThresholds = [
123+ Infinity , // Year
124+ 11 , // Month
125+ 28 , // Day
126+ 21 , // Hour
127+ 55 , // Minute
128+ 55 , // Second
129+ 900 , // Millisecond
130+ ]
131+
122132interface RoundingOpts {
123133 relativeTo : Date | number
124134}
125135
126136export function roundToSingleUnit ( duration : Duration , { relativeTo = Date . now ( ) } : Partial < RoundingOpts > = { } ) : Duration {
127- relativeTo = new Date ( relativeTo )
137+ return roundBalancedToSingleUnit (
138+ // TODO: Remove the positive sign in `+relativeTo` after integrating the new `elapsedTime` implementation.
139+ elapsedTime ( applyDuration ( new Date ( relativeTo ) , duration ) , 'millisecond' , + relativeTo ) ,
140+ )
141+ }
142+
143+ export function roundBalancedToSingleUnit ( duration : Duration ) : Duration {
128144 if ( duration . blank ) return duration
129145 const sign = duration . sign
130- let years = Math . abs ( duration . years )
131- let months = Math . abs ( duration . months )
132- let weeks = Math . abs ( duration . weeks )
133- let days = Math . abs ( duration . days )
134- let hours = Math . abs ( duration . hours )
135- let minutes = Math . abs ( duration . minutes )
136- let seconds = Math . abs ( duration . seconds )
137- let milliseconds = Math . abs ( duration . milliseconds )
138-
139- if ( milliseconds >= 900 ) seconds += Math . round ( milliseconds / 1000 )
140- if ( seconds || minutes || hours || days || weeks || months || years ) {
141- milliseconds = 0
146+ const values = [
147+ Math . abs ( duration . years ) ,
148+ Math . abs ( duration . months ) ,
149+ Math . abs ( duration . days ) ,
150+ Math . abs ( duration . hours ) ,
151+ Math . abs ( duration . minutes ) ,
152+ Math . abs ( duration . seconds ) ,
153+ Math . abs ( duration . milliseconds ) ,
154+ ]
155+ let biggestUnitIndex = values . findIndex ( v => v > 0 )
156+ const roundedLowerUnit =
157+ biggestUnitIndex < values . length - 1 &&
158+ values [ biggestUnitIndex + 1 ] >= durationRoundingThresholds [ biggestUnitIndex + 1 ]
159+ if ( roundedLowerUnit ) {
160+ values [ biggestUnitIndex ] += 1
142161 }
143-
144- if ( seconds >= 55 ) minutes += Math . round ( seconds / 60 )
145- if ( minutes || hours || days || weeks || months || years ) seconds = 0
146-
147- if ( minutes >= 55 ) hours += Math . round ( minutes / 60 )
148- if ( hours || days || weeks || months || years ) minutes = 0
149-
150- if ( days && hours >= 12 ) days += Math . round ( hours / 24 )
151- if ( ! days && hours >= 21 ) days += Math . round ( hours / 24 )
152- if ( days || weeks || months || years ) hours = 0
153-
154- // Resolve calendar dates
155- const currentYear = relativeTo . getFullYear ( )
156- const currentMonth = relativeTo . getMonth ( )
157- const currentDate = relativeTo . getDate ( )
158- if ( days >= 27 || years + months + days ) {
159- const newMonthDate = new Date ( relativeTo )
160- newMonthDate . setDate ( 1 )
161- newMonthDate . setMonth ( currentMonth + months * sign + 1 )
162- newMonthDate . setDate ( 0 )
163- const monthDateCorrection = Math . max ( 0 , currentDate - newMonthDate . getDate ( ) )
164-
165- const newDate = new Date ( relativeTo )
166- newDate . setFullYear ( currentYear + years * sign )
167- newDate . setDate ( currentDate - monthDateCorrection )
168- newDate . setMonth ( currentMonth + months * sign )
169- newDate . setDate ( currentDate - monthDateCorrection + days * sign )
170- const yearDiff = newDate . getFullYear ( ) - relativeTo . getFullYear ( )
171- const monthDiff = newDate . getMonth ( ) - relativeTo . getMonth ( )
172- const daysDiff = Math . abs ( Math . round ( ( Number ( newDate ) - Number ( relativeTo ) ) / 86400000 ) ) + monthDateCorrection
173- const monthsDiff = Math . abs ( yearDiff * 12 + monthDiff )
174- if ( daysDiff < 27 ) {
175- if ( days >= 6 ) {
176- weeks += Math . round ( days / 7 )
177- days = 0
178- } else {
179- days = daysDiff
180- }
181- months = years = 0
182- } else if ( monthsDiff <= 11 ) {
183- months = monthsDiff
184- years = 0
185- } else {
186- months = 0
187- years = yearDiff * sign
188- }
189- if ( months || years ) days = 0
162+ if ( values [ biggestUnitIndex ] >= durationRoundingThresholds [ biggestUnitIndex ] ) {
163+ -- biggestUnitIndex
164+ values [ biggestUnitIndex ] = 1
190165 }
191- if ( years ) months = 0
192-
193- if ( weeks >= 4 ) months += Math . round ( weeks / 4 )
194- if ( months || years ) weeks = 0
195- if ( days && weeks && ! months && ! years ) {
196- weeks += Math . round ( days / 7 )
197- days = 0
166+ for ( let i = biggestUnitIndex + 1 ; i < values . length ; ++ i ) {
167+ values [ i ] = 0
198168 }
199-
200- return new Duration (
201- years * sign ,
202- months * sign ,
203- weeks * sign ,
204- days * sign ,
205- hours * sign ,
206- minutes * sign ,
207- seconds * sign ,
208- milliseconds * sign ,
209- )
169+ if ( biggestUnitIndex === 2 && values [ 2 ] >= 6 ) {
170+ const weeks = Math . max ( 1 , Math . floor ( ( values [ 2 ] + ( roundedLowerUnit ? 0 : 1 ) ) / 7 ) )
171+ if ( weeks < 4 ) {
172+ return new Duration ( 0 , 0 , weeks * sign )
173+ }
174+ values [ biggestUnitIndex ] = 0
175+ -- biggestUnitIndex
176+ values [ biggestUnitIndex ] = 1
177+ }
178+ values [ biggestUnitIndex ] *= sign
179+ values . splice ( 2 , 0 , 0 )
180+ return new Duration ( ...values )
210181}
211182
212- export function getRelativeTimeUnit (
213- duration : Duration ,
214- opts ?: Partial < RoundingOpts > ,
215- ) : [ number , Intl . RelativeTimeFormatUnit ] {
216- const rounded = roundToSingleUnit ( duration , opts )
183+ export function getRoundedRelativeTimeUnit ( rounded : Duration ) : [ number , Intl . RelativeTimeFormatUnit ] {
217184 if ( rounded . blank ) return [ 0 , 'second' ]
218185 for ( const unit of unitNames ) {
219186 if ( unit === 'millisecond' ) continue
0 commit comments