Skip to content

Commit

Permalink
fix(@formatjs/intl-durationformat): fix partitioning logic
Browse files Browse the repository at this point in the history
  • Loading branch information
Long Ho committed Nov 13, 2023
1 parent d13c90c commit 4f5a1fc
Show file tree
Hide file tree
Showing 7 changed files with 204 additions and 127 deletions.
29 changes: 12 additions & 17 deletions packages/ecma402-abstract/DefaultNumberOption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,19 @@
* @param max
* @param fallback
*/
export function DefaultNumberOption(
val: any,
export function DefaultNumberOption<F extends number | undefined>(
inputVal: unknown,
min: number,
max: number,
fallback: number
): number
export function DefaultNumberOption(
val: any,
min: number,
max: number,
fallback: number | undefined
): number | undefined {
if (val !== undefined) {
val = Number(val)
if (isNaN(val) || val < min || val > max) {
throw new RangeError(`${val} is outside of range [${min}, ${max}]`)
}
return Math.floor(val)
fallback: F
): F extends number ? number : number | undefined {
if (inputVal === undefined) {
// @ts-expect-error
return fallback
}
const val = Number(inputVal)
if (isNaN(val) || val < min || val > max) {
throw new RangeError(`${val} is outside of range [${min}, ${max}]`)
}
return fallback
return Math.floor(val)
}
1 change: 0 additions & 1 deletion packages/ecma402-abstract/GetNumberOption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,5 @@ export function GetNumberOption<
fallback: F
): F extends number ? number : number | undefined {
const val = options[property]
// @ts-expect-error
return DefaultNumberOption(val, minimum, maximum, fallback)
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import {DefaultNumberOption} from '../DefaultNumberOption'
import {GetNumberOption} from '../GetNumberOption'
import {GetOption} from '../GetOption'
import {
NumberFormatDigitInternalSlots,
NumberFormatDigitOptions,
NumberFormatNotation,
NumberFormatDigitInternalSlots,
} from '../types/number'
import {GetNumberOption} from '../GetNumberOption'
import {DefaultNumberOption} from '../DefaultNumberOption'
import {GetOption} from '../GetOption'

/**
* https://tc39.es/ecma402/#sec-setnfdigitoptions
Expand Down Expand Up @@ -55,11 +55,10 @@ export function SetNumberFormatDigitOptions(
}
if (needFd) {
if (hasFd) {
// @ts-expect-error
mnfd = DefaultNumberOption(mnfd, 0, 20, undefined)
// @ts-expect-error
mxfd = DefaultNumberOption(mxfd, 0, 20, undefined)
if (mnfd === undefined) {
// @ts-expect-error
mnfd = Math.min(mnfdDefault, mxfd)
} else if (mxfd === undefined) {
mxfd = Math.max(mxfdDefault, mnfd)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {invariant} from '@formatjs/ecma402-abstract'
import {NumberFormatOptions, invariant} from '@formatjs/ecma402-abstract'
import {TABLE_2} from '../constants'
import {DurationFormat} from '../core'
import {getInternalSlots} from '../get_internal_slots'
Expand All @@ -19,90 +19,93 @@ export function PartitionDurationFormatPattern(
}
const numberingSystem = internalSlots.numberingSystem
const separator = dataLocaleData.digitalFormat[numberingSystem]
while (!done) {
let nextStyle
for (const row of TABLE_2) {
let value = duration[row.valueField]
const style = internalSlots[row.styleSlot]
const display = internalSlots[row.displaySlot]
const {unit, numberFormatUnit} = row
const nfOpts = Object.create(null)
if (
unit === 'seconds' ||
unit === 'milliseconds' ||
unit === 'microseconds'
) {

for (let i = 0; i < TABLE_2.length && !done; i++) {
const row = TABLE_2[i]
let value = duration[row.valueField]
const style = internalSlots[row.styleSlot]
const display = internalSlots[row.displaySlot]
const {unit, numberFormatUnit} = row

const nfOpts: NumberFormatOptions = Object.create(null)
if (
unit === 'seconds' ||
unit === 'milliseconds' ||
unit === 'microseconds'
) {
let nextStyle
if (unit === 'seconds') {
nextStyle = internalSlots.milliseconds
} else if (unit === 'milliseconds') {
nextStyle = internalSlots.microseconds
} else {
nextStyle = internalSlots.nanoseconds
}
if (nextStyle === 'numeric') {
if (unit === 'seconds') {
nextStyle = internalSlots.milliseconds
value +=
duration.milliseconds / 1e3 +
duration.microseconds / 1e6 +
duration.nanoseconds / 1e9
} else if (unit === 'milliseconds') {
nextStyle = internalSlots.microseconds
value += duration.microseconds / 1e3 + duration.nanoseconds / 1e6
} else {
nextStyle = internalSlots.nanoseconds
}
if (nextStyle === 'numeric') {
if (unit === 'seconds') {
value = value +=
duration.milliseconds / 1e3 +
duration.microseconds / 1e6 +
duration.nanoseconds / 1e9
} else if (unit === 'milliseconds') {
value = value +=
duration.microseconds / 1e3 + duration.nanoseconds / 1e6
} else {
value = value += duration.nanoseconds / 1e3
}
if (internalSlots.fractionalDigits === undefined) {
nfOpts.minimumFractionDigits = 0
nfOpts.maximumFractionDigits = 9
} else {
nfOpts.minimumFractionDigits = internalSlots.fractionalDigits
nfOpts.maximumFractionDigits = internalSlots.fractionalDigits
}
nfOpts.roundingMode = 'trunc'
done = true
value += duration.nanoseconds / 1e3
}
}
if (value !== 0 || display !== 'auto') {
nfOpts.numberingSystem = internalSlots.numberingSystem
if (style === '2-digit') {
nfOpts.minimumIntegerDigits = 2
}
if (style !== '2-digit' && style !== 'numeric') {
nfOpts.style = 'unit'
nfOpts.unit = numberFormatUnit
nfOpts.unitDisplay = style
}
const nf = new Intl.NumberFormat(internalSlots.locale, nfOpts)
let list: DurationFormatPart[]
if (!separated) {
list = []
if (internalSlots.fractionalDigits === undefined) {
nfOpts.maximumFractionDigits = 9
nfOpts.minimumFractionDigits = 0
} else {
list = result[result.length - 1]
list.push({
type: 'literal',
value: separator,
})
nfOpts.maximumFractionDigits = internalSlots.fractionalDigits
nfOpts.minimumFractionDigits = internalSlots.fractionalDigits
}
let parts = nf.formatToParts(value)
parts.forEach(part => {
list.push({
type: part.type,
value: part.value,
unit: numberFormatUnit,
})
nfOpts.roundingMode = 'trunc'
done = true
}
}
if (value !== 0 || display !== 'auto') {
nfOpts.numberingSystem = internalSlots.numberingSystem
if (style === '2-digit') {
nfOpts.minimumIntegerDigits = 2
}
if (style !== '2-digit' && style !== 'numeric') {
nfOpts.style = 'unit'
nfOpts.unit = numberFormatUnit
nfOpts.unitDisplay = style
}
const nf = new Intl.NumberFormat(
internalSlots.locale,
nfOpts as Intl.NumberFormatOptions
)
let list: DurationFormatPart[]
if (!separated) {
list = []
} else {
list = result[result.length - 1]
list.push({
type: 'literal',
value: separator,
})
if (!separated) {
if (style === '2-digit' || style === 'numeric') {
separated = true
}
result.push(list)
}
let parts = nf.formatToParts(value)
parts.forEach(({type, value}) => {
list.push({
type,
value,
unit: numberFormatUnit,
})
})
if (!separated) {
if (style === '2-digit' || style === 'numeric') {
separated = true
}
} else {
separated = false
result.push(list)
}
} else {
separated = false
}
}
const lfOpts = Object.create(null)
const lfOpts: Intl.ListFormatOptions = Object.create(null)
lfOpts.type = 'unit'
let listStyle = internalSlots.style
if (listStyle === 'digital') {
Expand Down
28 changes: 1 addition & 27 deletions packages/intl-durationformat/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,6 @@
// Public --------------------------------------------------------------------------------------------------------------

export interface DurationFormatOptions {
localeMatcher: 'best fit' | 'lookup'
style: 'long' | 'short' | 'narrow' | 'digital'
years: 'long' | 'short' | 'narrow'
yearsDisplay: 'always' | 'auto'
months: 'long' | 'short' | 'narrow'
monthsDisplay: 'always' | 'auto'
weeks: 'long' | 'short' | 'narrow'
weeksDisplay: 'always' | 'auto'
days: 'long' | 'short' | 'narrow'
daysDisplay: 'always' | 'auto'
hours: 'long' | 'short' | 'narrow' | 'numeric'
hoursDisplay: 'always' | 'auto'
minutes: 'long' | 'short' | 'narrow' | 'numeric' | '2-digit'
minutesDisplay: 'always' | 'auto'
seconds: 'long' | 'short' | 'narrow' | 'numeric' | '2-digit'
secondsDisplay: 'always' | 'auto'
milliseconds: 'long' | 'short' | 'narrow' | 'numeric'
millisecondsDisplay: 'always' | 'auto'
microseconds: 'long' | 'short' | 'narrow' | 'numeric'
microsecondsDisplay: 'always' | 'auto'
nanoseconds: 'long' | 'short' | 'narrow' | 'numeric'
nanosecondsDisplay: 'always' | 'auto'
fractionalDigits?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
numberingSystem: string
round: boolean
}
export type DurationFormatOptions = Partial<ResolvedDurationFormatOptions>

export interface ResolvedDurationFormatOptions {
localeMatcher: 'best fit' | 'lookup'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Intl.DurationFormat resolvedOptions 1`] = `
{
"days": "short",
"daysDisplay": "auto",
"fractionalDigits": undefined,
"hours": "short",
"hoursDisplay": "auto",
"locale": "en",
"microseconds": "short",
"microsecondsDisplay": "auto",
"milliseconds": "short",
"millisecondsDisplay": "auto",
"minutes": "short",
"minutesDisplay": "auto",
"months": "short",
"monthsDisplay": "auto",
"nanoseconds": "short",
"nanosecondsDisplay": "auto",
"numberingSystem": "latn",
"seconds": "short",
"secondsDisplay": "auto",
"style": "short",
"weeks": "short",
"weeksDisplay": "auto",
"years": "short",
"yearsDisplay": "auto",
}
`;
81 changes: 79 additions & 2 deletions packages/intl-durationformat/tests/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,83 @@ DurationFormat.__addLocaleData({
},
locale: 'en',
})
test.skip('Intl.DurationFormat', function () {
expect(new DurationFormat('en').format({years: 1})).toBe('1 year')
test('Intl.DurationFormat resolvedOptions', function () {
expect(new DurationFormat('en').resolvedOptions()).toMatchSnapshot()
})

test('Intl.DurationFormat format', function () {
expect(new DurationFormat('en').format({years: 1})).toBe('1 yr')
expect(new DurationFormat('en').format({years: 1, months: 2})).toBe(
'1 yr, 2 mths'
)
expect(new DurationFormat('en').format({years: 1, months: 2, days: 3})).toBe(
'1 yr, 2 mths, 3 days'
)
expect(
new DurationFormat('en').format({years: 1, months: 2, days: 3, hours: 4})
).toBe('1 yr, 2 mths, 3 days, 4 hr')
expect(
new DurationFormat('en').format({
years: 1,
months: 2,
days: 3,
hours: 4,
minutes: 5,
})
).toBe('1 yr, 2 mths, 3 days, 4 hr, 5 min')
expect(
new DurationFormat('en').format({
years: 1,
months: 2,
days: 3,
hours: 4,
minutes: 5,
seconds: 6,
})
).toBe('1 yr, 2 mths, 3 days, 4 hr, 5 min, 6 sec')
expect(
new DurationFormat('en').format({
years: 1,
months: 2,
days: 3,
hours: 4,
minutes: 5,
seconds: 6,
milliseconds: 7,
})
).toBe('1 yr, 2 mths, 3 days, 4 hr, 5 min, 6 sec, 7 ms')
})

test('Intl.DurationFormat format digital', function () {
expect(new DurationFormat('en', {style: 'digital'}).format({years: 1})).toBe(
'1 yr, 0:00:00'
)
})

test.skip('Intl.DurationFormat format with NumberFormatV3', function () {
expect(
new DurationFormat('en').format({
years: 1,
months: 2,
days: 3,
hours: 4,
minutes: 5,
seconds: 6,
milliseconds: 7,
microseconds: 8,
})
).toBe('1 yr, 2 mths, 3 days, 4 hr, 5 min, 6 sec, 7 ms, 8 μs')
expect(
new DurationFormat('en').format({
years: 1,
months: 2,
days: 3,
hours: 4,
minutes: 5,
seconds: 6,
milliseconds: 7,
microseconds: 8,
nanoseconds: 9,
})
).toBe('1 yr, 2 mths, 3 days, 4 hr, 5 min, 6 sec, 7 ms, 8 μs, 9 ns')
})

0 comments on commit 4f5a1fc

Please sign in to comment.