Skip to content

Commit

Permalink
feat(@formatjs/intl-durationformat): implement stage-3 spec
Browse files Browse the repository at this point in the history
part of #4257
  • Loading branch information
Long Ho committed Nov 13, 2023
1 parent cb96395 commit 01bcfc7
Show file tree
Hide file tree
Showing 22 changed files with 975 additions and 102 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"deploy": "NO_UPDATE_NOTIFIER=1 DEPLOYMENT_BRANCH=main USE_SSH=true bazel run //website:deploy",
"docs": "NO_UPDATE_NOTIFIER=1 bazel run //website:serve",
"examples": "bazel run //packages/react-intl/examples",
"format": "bazel run //:format",
"format": "bazel run //:format && bazel run //tools:buildifier",
"karma:ci": "bazel test :karma-ci",
"karma:local": "bazel test :karma",
"postinstall": "husky install",
Expand Down
17 changes: 7 additions & 10 deletions packages/ecma402-abstract/GetNumberOption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,17 @@

import {DefaultNumberOption} from './DefaultNumberOption'

export function GetNumberOption<T extends object, K extends keyof T>(
export function GetNumberOption<
T extends object,
K extends keyof T,
F extends number | undefined,
>(
options: T,
property: K,
minimum: number,
maximum: number,
fallback: number
): number
export function GetNumberOption<T extends object, K extends keyof T>(
options: T,
property: K,
minimum: number,
maximum: number,
fallback: number | undefined
): number | undefined {
fallback: F
): F extends number ? number : number | undefined {
const val = options[property]
// @ts-expect-error
return DefaultNumberOption(val, minimum, maximum, fallback)
Expand Down
2 changes: 1 addition & 1 deletion packages/ecma402-abstract/GetOption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export function GetOption<T extends object, K extends keyof T, F>(
opts: T,
prop: K,
type: 'string' | 'boolean',
values: T[K][] | undefined,
values: readonly T[K][] | undefined,
fallback: F
): Exclude<T[K], undefined> | F {
if (typeof opts !== 'object') {
Expand Down
2 changes: 1 addition & 1 deletion packages/icu-skeleton-parser/date-time.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ export function parseDateTimeSkeleton(
result.timeZoneName = len < 4 ? 'short' : 'long'
break
case 'Z': // 1..3, 4, 5: The ISO8601 varios formats
case 'O': // 1, 4: miliseconds in day short, long
case 'O': // 1, 4: milliseconds in day short, long
case 'v': // 1, 4: generic non-location format
case 'V': // 1, 2, 3, 4: time zone ID or city
case 'X': // 1, 2, 3, 4: The ISO8601 varios formats
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import {
Formats,
IntlDateTimeFormatInternal,
DateTimeFormatLocaleInternalData,
CanonicalizeLocaleList,
invariant,
GetOption,
IsValidTimeZoneName,
CanonicalizeTimeZoneName,
DateTimeFormat,
DateTimeFormatLocaleInternalData,
Formats,
GetNumberOption,
DateTimeFormat as DateTimeFormat,
GetOption,
IntlDateTimeFormatInternal,
IsValidTimeZoneName,
invariant,
} from '@formatjs/ecma402-abstract'
import {ResolveLocale} from '@formatjs/intl-localematcher'
import {BasicFormatMatcher} from './BasicFormatMatcher'
import {BestFitFormatMatcher} from './BestFitFormatMatcher'
import {DATE_TIME_PROPS} from './utils'
import {DateTimeStyleFormat} from './DateTimeStyleFormat'
import {ToDateTimeOptions} from './ToDateTimeOptions'
import {ResolveLocale} from '@formatjs/intl-localematcher'
import {DATE_TIME_PROPS} from './utils'

function isTimeRelated(opt: Opt) {
for (const prop of ['hour', 'minute', 'second'] as Array<
Expand Down Expand Up @@ -236,7 +236,6 @@ export function InitializeDateTimeFormat(
'fractionalSecondDigits',
1,
3,
// @ts-expect-error
undefined
) as 1

Expand Down
2 changes: 1 addition & 1 deletion packages/intl-datetimeformat/src/abstract/skeleton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ function matchSkeletonPattern(
// Zone
case 'z': // 1..3, 4: specific non-location format
case 'Z': // 1..3, 4, 5: The ISO8601 varios formats
case 'O': // 1, 4: miliseconds in day short, long
case 'O': // 1, 4: milliseconds in day short, long
case 'v': // 1, 4: generic non-location format
case 'V': // 1, 2, 3, 4: time zone ID or city
case 'X': // 1, 2, 3, 4: The ISO8601 varios formats
Expand Down
14 changes: 13 additions & 1 deletion packages/intl-durationformat/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,15 @@ npm_package(
visibility = ["//visibility:public"],
)

SRCS = glob(["*.ts"])
SRCS = glob([
"*.ts",
"src/**/*",
])

SRC_DEPS = [
":node_modules/@formatjs/ecma402-abstract",
":node_modules/@formatjs/intl-localematcher",
":node_modules/tslib",
]

ts_compile(
Expand Down Expand Up @@ -61,6 +65,14 @@ write_source_files(
files = {"tsconfig.json": "//tools:tsconfig.golden.json"},
)

# numbering-systems
write_source_files(
name = "numbering-systems",
files = {
"src/numbering-systems.generated.ts": "//packages/intl-numberformat:src/numbering-systems.generated.ts",
},
)

# package_json_test(
# name="package_json_test",
# deps=SRC_DEPS
Expand Down
66 changes: 1 addition & 65 deletions packages/intl-durationformat/index.ts
Original file line number Diff line number Diff line change
@@ -1,65 +1 @@
import {
CanonicalizeLocaleList,
GetOption,
ToObject,
} from '@formatjs/ecma402-abstract'
import {ResolveLocale} from '@formatjs/intl-localematcher'
export interface DurationFormatOptions {
style: 'long' | 'short' | 'narrow' | 'digital'
smallestUnit: 'second' | 'minute' | 'hour' | 'day' | 'month' | 'year'
largestUnit: 'second' | 'minute' | 'hour' | 'day' | 'month' | 'year'
hideZeroValues:
| 'all'
| 'leadingAndTrailing'
| 'leadingOnly'
| 'trailingOnly'
| 'none'
numberingSystem: string
round: boolean
}

export class DurationFormat {
constructor(locales?: string | string[], options?: DurationFormatOptions) {
// test262/test/intl402/ListFormat/constructor/constructor/newtarget-undefined.js
// Cannot use `new.target` bc of IE11 & TS transpiles it to something else
const newTarget =
this && this instanceof DurationFormat ? this.constructor : void 0
if (!newTarget) {
throw new TypeError("Intl.DurationFormat must be called with 'new'")
}
const requestedLocales = CanonicalizeLocaleList(locales)
const opt: any = Object.create(null)
const opts = options === undefined ? Object.create(null) : ToObject(options)
const matcher = GetOption(
opts,
'localeMatcher',
'string',
['best fit', 'lookup'],
'best fit'
)
opt.localeMatcher = matcher
// const numberingSystem = GetOption(
// opts,
// 'numberingSystem',
// 'string',
// undefined,
// undefined
// );
// @ts-expect-error TODO
const {localeData} = DurationFormat
const r = ResolveLocale(
localeData.availableLocales,
requestedLocales,
opt,
// [[RelevantExtensionKeys]] slot, which is a constant
['nu'],
localeData,
DurationFormat.getDefaultLocale
)
console.log('TODO', r)
}
static __defaultLocale: string
static getDefaultLocale() {
return DurationFormat.__defaultLocale
}
}
export * from './src/core'
14 changes: 14 additions & 0 deletions packages/intl-durationformat/src/abstract/DurationRecordSign.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {TABLE_1} from '../constants'
import {DurationRecord} from '../types'

export function DurationRecordSign(record: DurationRecord): -1 | 0 | 1 {
for (const key of TABLE_1) {
if (record[key] < 0) {
return -1
}
if (record[key] > 0) {
return 1
}
}
return 0
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import {GetOption} from '@formatjs/ecma402-abstract'

export function GetDurationUnitOptions<T extends object>(
unit: keyof T,
options: T,
baseStyle: Exclude<T[keyof T], undefined>,
stylesList: readonly T[keyof T][],
digitalBase: Exclude<T[keyof T], undefined>,
prevStyle: string
) {
let style = GetOption(options, unit, 'string', stylesList, undefined)
let displayDefault = 'always'
if (style === undefined) {
if (baseStyle === 'digital') {
if (unit !== 'hours' && unit !== 'minutes' && unit !== 'seconds') {
displayDefault = 'auto'
}
style = digitalBase
} else {
displayDefault = 'auto'
if (prevStyle === 'numeric' || prevStyle === '2-digit') {
style = 'numeric' as Exclude<T[keyof T], undefined>
} else {
style = baseStyle
}
}
}
let displayField = `${unit as string}Display` as keyof T
let display = GetOption(
options,
displayField,
'string',
['always', 'auto'] as Array<T[keyof T]>,
displayDefault
)

if (prevStyle === 'numeric' || prevStyle === '2-digit') {
if (style !== 'numeric' && style !== '2-digit') {
throw new RangeError("Can't mix numeric and non-numeric styles")
} else if (unit === 'minutes' || unit === 'seconds') {
style = '2-digit' as Exclude<T[keyof T], undefined>
}
if (
style === 'numeric' &&
display === 'always' &&
(unit === 'milliseconds' ||
unit === 'microseconds' ||
unit === 'nanoseconds')
) {
throw new RangeError(
"Can't display milliseconds, microseconds, or nanoseconds in numeric format"
)
}
}
return {
style,
display,
}
}
19 changes: 19 additions & 0 deletions packages/intl-durationformat/src/abstract/IsValidDurationRecord.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {invariant} from '@formatjs/ecma402-abstract'
import {TABLE_1} from '../constants'
import {DurationRecord} from '../types'
import {DurationRecordSign} from './DurationRecordSign'

export function IsValidDurationRecord(record: DurationRecord): boolean {
const sign = DurationRecordSign(record)
for (const key of TABLE_1) {
const v = record[key]
invariant(isFinite(Number(v)), `${key} is not finite`)
if (v < 0 && sign > 0) {
return false
}
if (v > 0 && sign < 0) {
return false
}
}
return true
}
Loading

0 comments on commit 01bcfc7

Please sign in to comment.