@@ -119,101 +119,68 @@ export function elapsedTime(date: Date, precision: Unit = 'second', now = Date.n
119
119
)
120
120
}
121
121
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
+
122
132
interface RoundingOpts {
123
133
relativeTo : Date | number
124
134
}
125
135
126
136
export 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 {
128
144
if ( duration . blank ) return duration
129
145
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
142
161
}
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
190
165
}
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
198
168
}
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 )
210
181
}
211
182
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 ] {
217
184
if ( rounded . blank ) return [ 0 , 'second' ]
218
185
for ( const unit of unitNames ) {
219
186
if ( unit === 'millisecond' ) continue
0 commit comments