Skip to content

Commit f936f7a

Browse files
committed
Reimplement duration rounding.
1 parent a80c1bf commit f936f7a

File tree

4 files changed

+178
-177
lines changed

4 files changed

+178
-177
lines changed

Diff for: src/duration.ts

+56-76
Original file line numberDiff line numberDiff line change
@@ -119,94 +119,74 @@ 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+
122132
interface RoundingOpts {
123133
relativeTo: Date | number
124134
}
125135

126136
export function roundToSingleUnit(duration: Duration, {relativeTo = Date.now()}: Partial<RoundingOpts> = {}): Duration {
127-
relativeTo = new Date(relativeTo)
128137
if (duration.blank) return duration
129-
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
142-
}
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
138+
const referenceDate = new Date(relativeTo)
139+
const specifiedDate = applyDuration(referenceDate, duration)
140+
const [sign, subtrahend, minuend] =
141+
specifiedDate < referenceDate ? [-1, referenceDate, specifiedDate] : [1, specifiedDate, referenceDate]
142+
const subtrahendWithoutTime = new Date(subtrahend)
143+
subtrahendWithoutTime.setHours(0)
144+
subtrahendWithoutTime.setMinutes(0)
145+
subtrahendWithoutTime.setSeconds(0)
146+
subtrahendWithoutTime.setMilliseconds(0)
147+
const minuendWithoutTime = new Date(minuend)
148+
minuendWithoutTime.setHours(0)
149+
minuendWithoutTime.setMinutes(0)
150+
minuendWithoutTime.setSeconds(0)
151+
minuendWithoutTime.setMilliseconds(0)
152+
if (
153+
subtrahendWithoutTime.getTime() === minuendWithoutTime.getTime() ||
154+
subtrahend.getTime() - minuend.getTime() < 1000 * 60 * 60 * 12
155+
) {
156+
const difference = Math.round((subtrahend.getTime() - minuend.getTime()) / 1000)
157+
let hours = Math.floor(difference / 3600)
158+
let minutes = Math.floor((difference % 3600) / 60)
159+
const seconds = Math.floor(difference % 60)
160+
if (hours === 0) {
161+
if (seconds >= durationRoundingThresholds[5]) minutes += 1
162+
if (minutes >= durationRoundingThresholds[4]) {
163+
return new Duration(0, 0, 0, 0, 1 * sign) // 1 hour.
164+
}
165+
if (minutes === 0) {
166+
return new Duration(0, 0, 0, 0, 0, 0, seconds * sign)
178167
} else {
179-
days = daysDiff
168+
return new Duration(0, 0, 0, 0, 0, minutes * sign)
180169
}
181-
months = years = 0
182-
} else if (monthsDiff <= 11) {
183-
months = monthsDiff
184-
years = 0
185170
} else {
186-
months = 0
187-
years = yearDiff * sign
171+
if (hours < 23 && minutes >= durationRoundingThresholds[4]) hours += 1
172+
return new Duration(0, 0, 0, 0, hours * sign)
188173
}
189-
if (months || years) days = 0
190174
}
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
175+
const days = Math.round((subtrahendWithoutTime.getTime() - minuendWithoutTime.getTime()) / (1000 * 60 * 60 * 24))
176+
const months =
177+
subtrahend.getFullYear() * 12 + subtrahend.getMonth() - (minuend.getFullYear() * 12 + minuend.getMonth())
178+
if (months === 0 || days <= 26) {
179+
if (days >= 6) {
180+
return new Duration(0, 0, Math.floor((days + 1) / 7) * sign) // Weeks.
181+
} else {
182+
return new Duration(0, 0, 0, days * sign)
183+
}
184+
}
185+
if (months < 12) {
186+
return new Duration(0, months * sign)
187+
} else {
188+
return new Duration((subtrahend.getFullYear() - minuend.getFullYear()) * sign)
198189
}
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-
)
210190
}
211191

212192
export function getRelativeTimeUnit(

Diff for: src/relative-time-element.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {Duration, elapsedTime, getRelativeTimeUnit, isDuration, roundToSingleUnit, Unit, unitNames} from './duration.js'
1+
import {Duration, Unit, elapsedTime, getRelativeTimeUnit, isDuration, roundToSingleUnit, unitNames} from './duration.js'
22
const HTMLElement = globalThis.HTMLElement || (null as unknown as typeof window['HTMLElement'])
33

44
export type DeprecatedFormat = 'auto' | 'micro' | 'elapsed'
@@ -157,6 +157,7 @@ export class RelativeTimeElement extends HTMLElement implements Intl.DateTimeFor
157157
const tense = this.tense
158158
let empty = emptyDuration
159159
if (format === 'micro') {
160+
// TODO: Switch to `roundBalancedToSingleUnit` after integrating the new `elapsedTime` implementation.
160161
duration = roundToSingleUnit(duration)
161162
empty = microEmptyDuration
162163
if ((this.tense === 'past' && duration.sign !== -1) || (this.tense === 'future' && duration.sign !== 1)) {

0 commit comments

Comments
 (0)