Comparing version 1.28.1 to 3.4.0
{ | ||
"name": "luxon", | ||
"version": "1.28.1", | ||
"version": "3.4.0", | ||
"description": "Immutable date wrapper", | ||
@@ -11,2 +11,9 @@ "author": "Isaac Cambron", | ||
"repository": "https://github.com/moment/luxon", | ||
"exports": { | ||
".": { | ||
"import": "./src/luxon.js", | ||
"require": "./build/node/luxon.js" | ||
}, | ||
"./package.json": "./package.json" | ||
}, | ||
"scripts": { | ||
@@ -18,16 +25,12 @@ "build": "babel-node tasks/buildAll.js", | ||
"test": "jest --coverage", | ||
"docs": "esdoc -c docs/index.js", | ||
"site": "cp -r site/** build/", | ||
"lint": "eslint --quiet src test benchmarks", | ||
"lint!": "npm run format && npm run lint", | ||
"api-docs": "mkdir -p build && documentation build src/luxon.js -f html -o build/api-docs && sed -i.bak 's/<\\/body>/<script src=\"\\..\\/global\\/luxon.js\"><\\/script><script>console.log(\"You can try Luxon right here using the `luxon` global, like `luxon.DateTime.now()`\");<\\/script><\\/body>/g' build/api-docs/index.html && rm build/api-docs/index.html.bak", | ||
"copy-site": "mkdir -p build && rsync -a docs/ build/docs && rsync -a site/ build", | ||
"site": "npm run api-docs && npm run copy-site", | ||
"format": "prettier --write 'src/**/*.js' 'test/**/*.js' 'benchmarks/*.js'", | ||
"format-check": "prettier --check 'src/**/*.js' 'test/**/*.js' 'benchmarks/*.js'", | ||
"benchmark": "babel-node benchmarks/index.js", | ||
"codecov": "codecov", | ||
"check-doc-coverage": "babel-node tasks/docCoverage" | ||
"prepack": "babel-node tasks/buildAll.js", | ||
"prepare": "husky install" | ||
}, | ||
"husky": { | ||
"hooks": { | ||
"pre-commit": "lint-staged" | ||
} | ||
}, | ||
"lint-staged": { | ||
@@ -39,37 +42,21 @@ "*.{js,json}": [ | ||
"devDependencies": { | ||
"@babel/core": "^7.9.0", | ||
"@babel/node": "^7.8.7", | ||
"@babel/plugin-external-helpers": "^7.8.3", | ||
"@babel/preset-env": "^7.9.5", | ||
"@rollup/plugin-commonjs": "^11.1.0", | ||
"@rollup/plugin-node-resolve": "^7.1.3", | ||
"babel-core": "^7.0.0-bridge.0", | ||
"babel-eslint": "latest", | ||
"babel-jest": "^25.3.0", | ||
"@babel/core": "^7.18.6", | ||
"@babel/node": "^7.18.6", | ||
"@babel/plugin-external-helpers": "^7.18.6", | ||
"@babel/preset-env": "^7.18.6", | ||
"@rollup/plugin-babel": "^5.3.0", | ||
"@rollup/plugin-commonjs": "^19.0.0", | ||
"@rollup/plugin-node-resolve": "^13.0.0", | ||
"babel-jest": "^28.1.2", | ||
"benchmark": "latest", | ||
"codecov": ">= 3.6.5", | ||
"core-js": "^3.6.5", | ||
"esdoc": "^1.1.0", | ||
"esdoc-standard-plugin": "latest", | ||
"eslint": "6.4.0", | ||
"eslint-config-defaults": "latest", | ||
"eslint-config-prettier": "6.3.0", | ||
"eslint-config-standard": "^14.1.1", | ||
"eslint-plugin-babel": "latest", | ||
"eslint-plugin-import": "^2.20.2", | ||
"eslint-plugin-node": "10.0.0", | ||
"eslint-plugin-prettier": "3.1.1", | ||
"eslint-plugin-promise": "latest", | ||
"eslint-plugin-react": "^7.19.0", | ||
"eslint-plugin-standard": "^4.0.1", | ||
"codecov": "latest", | ||
"documentation": "latest", | ||
"fs-extra": "^6.0.1", | ||
"full-icu": "^1.3.1", | ||
"husky": "^4.2.5", | ||
"jest": "^25.3.0", | ||
"lint-staged": "^10.1.3", | ||
"prettier": "1.14.3", | ||
"rollup": "^1.32.1", | ||
"rollup-plugin-babel": "^4.4.0", | ||
"rollup-plugin-babel-minify": "^6.2.0", | ||
"uglify-es": "^3.3.9" | ||
"husky": "^7.0.0", | ||
"jest": "^29.4.3", | ||
"lint-staged": "^13.2.1", | ||
"prettier": "latest", | ||
"rollup": "^2.52.7", | ||
"rollup-plugin-terser": "^7.0.2", | ||
"uglify-js": "^3.13.10" | ||
}, | ||
@@ -82,3 +69,3 @@ "main": "build/node/luxon.js", | ||
"engines": { | ||
"node": "*" | ||
"node": ">=12" | ||
}, | ||
@@ -96,2 +83,4 @@ "files": [ | ||
"build/global/luxon.min.js.map", | ||
"build/es6/luxon.js", | ||
"build/es6/luxon.js.map", | ||
"src" | ||
@@ -98,0 +87,0 @@ ], |
# Luxon | ||
[![MIT License][license-image]][license] [![Build Status][travis-image]][travis-url] [![NPM version][npm-version-image]][npm-url] [![Coverage Status][test-coverage-image]][test-coverage-url] [![Doc coverage][doc-coverage-image]][doc-url] [![PRs welcome][contributing-image]][contributing-url] | ||
[![MIT License][license-image]][license] [![Build Status][github-action-image]][github-action-url] [![NPM version][npm-version-image]][npm-url] [![Coverage Status][test-coverage-image]][test-coverage-url] [![PRs welcome][contributing-image]][contributing-url] | ||
@@ -11,2 +11,6 @@ Luxon is a library for working with dates and times in JavaScript. | ||
## Upgrading to 3.0 | ||
[Guide](https://moment.github.io/luxon/#upgrading) | ||
## Features | ||
@@ -20,11 +24,11 @@ * DateTime, Duration, and Interval types. | ||
[Download/install instructions](https://moment.github.io/luxon/docs/manual/install.html) | ||
[Download/install instructions](https://moment.github.io/luxon/#/install) | ||
## Documentation | ||
* [General documentation][doc-url] | ||
* [API docs](https://moment.github.io/luxon/docs/identifiers.html) | ||
* [Quick tour](https://moment.github.io/luxon/docs/manual/tour.html) | ||
* [For Moment users](https://moment.github.io/luxon/docs/manual/moment.html) | ||
* [Why does Luxon exist?](https://moment.github.io/luxon/docs/manual/why.html) | ||
* [General documentation](https://moment.github.io/luxon/#/?id=luxon) | ||
* [API docs](https://moment.github.io/luxon/api-docs/index.html) | ||
* [Quick tour](https://moment.github.io/luxon/#/tour) | ||
* [For Moment users](https://moment.github.io/luxon/#/moment) | ||
* [Why does Luxon exist?](https://moment.github.io/luxon/#/why) | ||
* [A quick demo](https://moment.github.io/luxon/demo/global.html) | ||
@@ -34,11 +38,11 @@ | ||
See [contributing](contributing.md). | ||
See [contributing](CONTRIBUTING.md). | ||
![Phasers to stun][phasers-image] | ||
[license-image]: http://img.shields.io/badge/license-MIT-blue.svg | ||
[license]: license.md | ||
[license-image]: https://img.shields.io/badge/license-MIT-blue.svg | ||
[license]: LICENSE.md | ||
[travis-url]: http://travis-ci.org/moment/luxon | ||
[travis-image]: https://api.travis-ci.org/moment/luxon.svg?branch=master | ||
[github-action-image]: https://github.com/moment/luxon/actions/workflows/test.yml/badge.svg | ||
[github-action-url]: https://github.com/moment/luxon/actions/workflows/test.yml | ||
@@ -48,11 +52,8 @@ [npm-url]: https://npmjs.org/package/luxon | ||
[doc-url]: https://moment.github.io/luxon/docs/ | ||
[doc-coverage-image]: https://moment.github.io/luxon/docs/badge.svg | ||
[test-coverage-url]: https://codecov.io/gh/moment/luxon | ||
[test-coverage-image]: https://codecov.io/gh/moment/luxon/branch/master/graph/badge.svg | ||
[contributing-url]: https://moment.github.io/luxon/docs/manual/contributing.html | ||
[contributing-url]: https://github.com/moment/luxon/blob/master/CONTRIBUTING.md | ||
[contributing-image]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg | ||
[phasers-image]: https://img.shields.io/badge/phasers-stun-brightgreen.svg |
@@ -12,5 +12,6 @@ import { InvalidArgumentError, InvalidDurationError, InvalidUnitError } from "./errors.js"; | ||
normalizeObject, | ||
roundTo | ||
roundTo, | ||
} from "./impl/util.js"; | ||
import Settings from "./settings.js"; | ||
import DateTime from "./datetime.js"; | ||
@@ -20,3 +21,3 @@ const INVALID = "Invalid Duration"; | ||
// unit conversion constants | ||
const lowOrderMatrix = { | ||
export const lowOrderMatrix = { | ||
weeks: { | ||
@@ -27,3 +28,3 @@ days: 7, | ||
seconds: 7 * 24 * 60 * 60, | ||
milliseconds: 7 * 24 * 60 * 60 * 1000 | ||
milliseconds: 7 * 24 * 60 * 60 * 1000, | ||
}, | ||
@@ -34,74 +35,71 @@ days: { | ||
seconds: 24 * 60 * 60, | ||
milliseconds: 24 * 60 * 60 * 1000 | ||
milliseconds: 24 * 60 * 60 * 1000, | ||
}, | ||
hours: { minutes: 60, seconds: 60 * 60, milliseconds: 60 * 60 * 1000 }, | ||
minutes: { seconds: 60, milliseconds: 60 * 1000 }, | ||
seconds: { milliseconds: 1000 } | ||
seconds: { milliseconds: 1000 }, | ||
}, | ||
casualMatrix = Object.assign( | ||
{ | ||
years: { | ||
quarters: 4, | ||
months: 12, | ||
weeks: 52, | ||
days: 365, | ||
hours: 365 * 24, | ||
minutes: 365 * 24 * 60, | ||
seconds: 365 * 24 * 60 * 60, | ||
milliseconds: 365 * 24 * 60 * 60 * 1000 | ||
}, | ||
quarters: { | ||
months: 3, | ||
weeks: 13, | ||
days: 91, | ||
hours: 91 * 24, | ||
minutes: 91 * 24 * 60, | ||
seconds: 91 * 24 * 60 * 60, | ||
milliseconds: 91 * 24 * 60 * 60 * 1000 | ||
}, | ||
months: { | ||
weeks: 4, | ||
days: 30, | ||
hours: 30 * 24, | ||
minutes: 30 * 24 * 60, | ||
seconds: 30 * 24 * 60 * 60, | ||
milliseconds: 30 * 24 * 60 * 60 * 1000 | ||
} | ||
casualMatrix = { | ||
years: { | ||
quarters: 4, | ||
months: 12, | ||
weeks: 52, | ||
days: 365, | ||
hours: 365 * 24, | ||
minutes: 365 * 24 * 60, | ||
seconds: 365 * 24 * 60 * 60, | ||
milliseconds: 365 * 24 * 60 * 60 * 1000, | ||
}, | ||
lowOrderMatrix | ||
), | ||
quarters: { | ||
months: 3, | ||
weeks: 13, | ||
days: 91, | ||
hours: 91 * 24, | ||
minutes: 91 * 24 * 60, | ||
seconds: 91 * 24 * 60 * 60, | ||
milliseconds: 91 * 24 * 60 * 60 * 1000, | ||
}, | ||
months: { | ||
weeks: 4, | ||
days: 30, | ||
hours: 30 * 24, | ||
minutes: 30 * 24 * 60, | ||
seconds: 30 * 24 * 60 * 60, | ||
milliseconds: 30 * 24 * 60 * 60 * 1000, | ||
}, | ||
...lowOrderMatrix, | ||
}, | ||
daysInYearAccurate = 146097.0 / 400, | ||
daysInMonthAccurate = 146097.0 / 4800, | ||
accurateMatrix = Object.assign( | ||
{ | ||
years: { | ||
quarters: 4, | ||
months: 12, | ||
weeks: daysInYearAccurate / 7, | ||
days: daysInYearAccurate, | ||
hours: daysInYearAccurate * 24, | ||
minutes: daysInYearAccurate * 24 * 60, | ||
seconds: daysInYearAccurate * 24 * 60 * 60, | ||
milliseconds: daysInYearAccurate * 24 * 60 * 60 * 1000 | ||
}, | ||
quarters: { | ||
months: 3, | ||
weeks: daysInYearAccurate / 28, | ||
days: daysInYearAccurate / 4, | ||
hours: (daysInYearAccurate * 24) / 4, | ||
minutes: (daysInYearAccurate * 24 * 60) / 4, | ||
seconds: (daysInYearAccurate * 24 * 60 * 60) / 4, | ||
milliseconds: (daysInYearAccurate * 24 * 60 * 60 * 1000) / 4 | ||
}, | ||
months: { | ||
weeks: daysInMonthAccurate / 7, | ||
days: daysInMonthAccurate, | ||
hours: daysInMonthAccurate * 24, | ||
minutes: daysInMonthAccurate * 24 * 60, | ||
seconds: daysInMonthAccurate * 24 * 60 * 60, | ||
milliseconds: daysInMonthAccurate * 24 * 60 * 60 * 1000 | ||
} | ||
accurateMatrix = { | ||
years: { | ||
quarters: 4, | ||
months: 12, | ||
weeks: daysInYearAccurate / 7, | ||
days: daysInYearAccurate, | ||
hours: daysInYearAccurate * 24, | ||
minutes: daysInYearAccurate * 24 * 60, | ||
seconds: daysInYearAccurate * 24 * 60 * 60, | ||
milliseconds: daysInYearAccurate * 24 * 60 * 60 * 1000, | ||
}, | ||
lowOrderMatrix | ||
); | ||
quarters: { | ||
months: 3, | ||
weeks: daysInYearAccurate / 28, | ||
days: daysInYearAccurate / 4, | ||
hours: (daysInYearAccurate * 24) / 4, | ||
minutes: (daysInYearAccurate * 24 * 60) / 4, | ||
seconds: (daysInYearAccurate * 24 * 60 * 60) / 4, | ||
milliseconds: (daysInYearAccurate * 24 * 60 * 60 * 1000) / 4, | ||
}, | ||
months: { | ||
weeks: daysInMonthAccurate / 7, | ||
days: daysInMonthAccurate, | ||
hours: daysInMonthAccurate * 24, | ||
minutes: daysInMonthAccurate * 24 * 60, | ||
seconds: daysInMonthAccurate * 24 * 60 * 60, | ||
milliseconds: daysInMonthAccurate * 24 * 60 * 60 * 1000, | ||
}, | ||
...lowOrderMatrix, | ||
}; | ||
@@ -118,3 +116,3 @@ // units ordered by size | ||
"seconds", | ||
"milliseconds" | ||
"milliseconds", | ||
]; | ||
@@ -128,5 +126,6 @@ | ||
const conf = { | ||
values: clear ? alts.values : Object.assign({}, dur.values, alts.values || {}), | ||
values: clear ? alts.values : { ...dur.values, ...(alts.values || {}) }, | ||
loc: dur.loc.clone(alts.loc), | ||
conversionAccuracy: alts.conversionAccuracy || dur.conversionAccuracy | ||
conversionAccuracy: alts.conversionAccuracy || dur.conversionAccuracy, | ||
matrix: alts.matrix || dur.matrix, | ||
}; | ||
@@ -136,4 +135,5 @@ return new Duration(conf); | ||
function antiTrunc(n) { | ||
return n < 0 ? Math.floor(n) : Math.ceil(n); | ||
// this is needed since in some test cases it would return 0.9999999999999999 instead of 1 | ||
function removePrecisionIssue(a) { | ||
return Math.trunc(a * 1e3) / 1e3; | ||
} | ||
@@ -145,8 +145,6 @@ | ||
raw = fromMap[fromUnit] / conv, | ||
sameSign = Math.sign(raw) === Math.sign(toMap[toUnit]), | ||
// ok, so this is wild, but see the matrix in the tests | ||
added = | ||
!sameSign && toMap[toUnit] !== 0 && Math.abs(raw) <= 1 ? antiTrunc(raw) : Math.trunc(raw); | ||
toMap[toUnit] += added; | ||
fromMap[fromUnit] -= added * conv; | ||
added = Math.floor(raw); | ||
toMap[toUnit] = removePrecisionIssue(toMap[toUnit] + added); | ||
fromMap[fromUnit] = removePrecisionIssue(fromMap[fromUnit] - added * conv); | ||
} | ||
@@ -168,4 +166,15 @@ | ||
// Remove all properties with a value of 0 from an object | ||
function removeZeroes(vals) { | ||
const newVals = {}; | ||
for (const [key, value] of Object.entries(vals)) { | ||
if (value !== 0) { | ||
newVals[key] = value; | ||
} | ||
} | ||
return newVals; | ||
} | ||
/** | ||
* A Duration object represents a period of time, like "2 months" or "1 day, 1 hour". Conceptually, it's just a map of units to their quantities, accompanied by some additional configuration and methods for creating, parsing, interrogating, transforming, and formatting them. They can be used on their own or in conjunction with other Luxon types; for example, you can use {@link DateTime.plus} to add a Duration object to a DateTime, producing another DateTime. | ||
* A Duration object represents a period of time, like "2 months" or "1 day, 1 hour". Conceptually, it's just a map of units to their quantities, accompanied by some additional configuration and methods for creating, parsing, interrogating, transforming, and formatting them. They can be used on their own or in conjunction with other Luxon types; for example, you can use {@link DateTime#plus} to add a Duration object to a DateTime, producing another DateTime. | ||
* | ||
@@ -175,6 +184,6 @@ * Here is a brief overview of commonly used methods and getters in Duration: | ||
* * **Creation** To create a Duration, use {@link Duration.fromMillis}, {@link Duration.fromObject}, or {@link Duration.fromISO}. | ||
* * **Unit values** See the {@link Duration.years}, {@link Duration.months}, {@link Duration.weeks}, {@link Duration.days}, {@link Duration.hours}, {@link Duration.minutes}, {@link Duration.seconds}, {@link Duration.milliseconds} accessors. | ||
* * **Configuration** See {@link Duration.locale} and {@link Duration.numberingSystem} accessors. | ||
* * **Transformation** To create new Durations out of old ones use {@link Duration.plus}, {@link Duration.minus}, {@link Duration.normalize}, {@link Duration.set}, {@link Duration.reconfigure}, {@link Duration.shiftTo}, and {@link Duration.negate}. | ||
* * **Output** To convert the Duration into other representations, see {@link Duration.as}, {@link Duration.toISO}, {@link Duration.toFormat}, and {@link Duration.toJSON} | ||
* * **Unit values** See the {@link Duration#years}, {@link Duration#months}, {@link Duration#weeks}, {@link Duration#days}, {@link Duration#hours}, {@link Duration#minutes}, {@link Duration#seconds}, {@link Duration#milliseconds} accessors. | ||
* * **Configuration** See {@link Duration#locale} and {@link Duration#numberingSystem} accessors. | ||
* * **Transformation** To create new Durations out of old ones use {@link Duration#plus}, {@link Duration#minus}, {@link Duration#normalize}, {@link Duration#set}, {@link Duration#reconfigure}, {@link Duration#shiftTo}, and {@link Duration#negate}. | ||
* * **Output** To convert the Duration into other representations, see {@link Duration#as}, {@link Duration#toISO}, {@link Duration#toFormat}, and {@link Duration#toJSON} | ||
* | ||
@@ -189,2 +198,8 @@ * There's are more methods documented below. In addition, for more information on subtler topics like internationalization and validity, see the external documentation. | ||
const accurate = config.conversionAccuracy === "longterm" || false; | ||
let matrix = accurate ? accurateMatrix : casualMatrix; | ||
if (config.matrix) { | ||
matrix = config.matrix; | ||
} | ||
/** | ||
@@ -209,3 +224,3 @@ * @access private | ||
*/ | ||
this.matrix = accurate ? accurateMatrix : casualMatrix; | ||
this.matrix = matrix; | ||
/** | ||
@@ -227,3 +242,3 @@ * @access private | ||
static fromMillis(count, opts) { | ||
return Duration.fromObject(Object.assign({ milliseconds: count }, opts)); | ||
return Duration.fromObject({ milliseconds: count }, opts); | ||
} | ||
@@ -244,8 +259,10 @@ | ||
* @param {number} obj.milliseconds | ||
* @param {string} [obj.locale='en-US'] - the locale to use | ||
* @param {string} obj.numberingSystem - the numbering system to use | ||
* @param {string} [obj.conversionAccuracy='casual'] - the conversion system to use | ||
* @param {Object} [opts=[]] - options for creating this Duration | ||
* @param {string} [opts.locale='en-US'] - the locale to use | ||
* @param {string} opts.numberingSystem - the numbering system to use | ||
* @param {string} [opts.conversionAccuracy='casual'] - the preset conversion system to use | ||
* @param {string} [opts.matrix=Object] - the custom conversion system to use | ||
* @return {Duration} | ||
*/ | ||
static fromObject(obj) { | ||
static fromObject(obj, opts = {}) { | ||
if (obj == null || typeof obj !== "object") { | ||
@@ -258,11 +275,8 @@ throw new InvalidArgumentError( | ||
} | ||
return new Duration({ | ||
values: normalizeObject(obj, Duration.normalizeUnit, [ | ||
"locale", | ||
"numberingSystem", | ||
"conversionAccuracy", | ||
"zone" // a bit of debt; it's super inconvenient internally not to be able to blindly pass this | ||
]), | ||
loc: Locale.fromObject(obj), | ||
conversionAccuracy: obj.conversionAccuracy | ||
values: normalizeObject(obj, Duration.normalizeUnit), | ||
loc: Locale.fromObject(opts), | ||
conversionAccuracy: opts.conversionAccuracy, | ||
matrix: opts.matrix, | ||
}); | ||
@@ -272,2 +286,26 @@ } | ||
/** | ||
* Create a Duration from DurationLike. | ||
* | ||
* @param {Object | number | Duration} durationLike | ||
* One of: | ||
* - object with keys like 'years' and 'hours'. | ||
* - number representing milliseconds | ||
* - Duration instance | ||
* @return {Duration} | ||
*/ | ||
static fromDurationLike(durationLike) { | ||
if (isNumber(durationLike)) { | ||
return Duration.fromMillis(durationLike); | ||
} else if (Duration.isDuration(durationLike)) { | ||
return durationLike; | ||
} else if (typeof durationLike === "object") { | ||
return Duration.fromObject(durationLike); | ||
} else { | ||
throw new InvalidArgumentError( | ||
`Unknown duration argument ${durationLike} of type ${typeof durationLike}` | ||
); | ||
} | ||
} | ||
/** | ||
* Create a Duration from an ISO 8601 duration string. | ||
@@ -278,3 +316,4 @@ * @param {string} text - text to parse | ||
* @param {string} opts.numberingSystem - the numbering system to use | ||
* @param {string} [opts.conversionAccuracy='casual'] - the conversion system to use | ||
* @param {string} [opts.conversionAccuracy='casual'] - the preset conversion system to use | ||
* @param {string} [opts.matrix=Object] - the preset conversion system to use | ||
* @see https://en.wikipedia.org/wiki/ISO_8601#Durations | ||
@@ -289,4 +328,3 @@ * @example Duration.fromISO('P3Y6M1W4DT12H30M5S').toObject() //=> { years: 3, months: 6, weeks: 1, days: 4, hours: 12, minutes: 30, seconds: 5 } | ||
if (parsed) { | ||
const obj = Object.assign(parsed, opts); | ||
return Duration.fromObject(obj); | ||
return Duration.fromObject(parsed, opts); | ||
} else { | ||
@@ -303,3 +341,4 @@ return Duration.invalid("unparsable", `the input "${text}" can't be parsed as ISO 8601`); | ||
* @param {string} opts.numberingSystem - the numbering system to use | ||
* @param {string} [opts.conversionAccuracy='casual'] - the conversion system to use | ||
* @param {string} [opts.conversionAccuracy='casual'] - the preset conversion system to use | ||
* @param {string} [opts.matrix=Object] - the conversion system to use | ||
* @see https://en.wikipedia.org/wiki/ISO_8601#Times | ||
@@ -316,4 +355,3 @@ * @example Duration.fromISOTime('11:22:33.444').toObject() //=> { hours: 11, minutes: 22, seconds: 33, milliseconds: 444 } | ||
if (parsed) { | ||
const obj = Object.assign(parsed, opts); | ||
return Duration.fromObject(obj); | ||
return Duration.fromObject(parsed, opts); | ||
} else { | ||
@@ -366,3 +404,3 @@ return Duration.invalid("unparsable", `the input "${text}" can't be parsed as ISO 8601`); | ||
millisecond: "milliseconds", | ||
milliseconds: "milliseconds" | ||
milliseconds: "milliseconds", | ||
}[unit ? unit.toLowerCase() : unit]; | ||
@@ -408,2 +446,3 @@ | ||
* * `d` for days | ||
* * `w` for weeks | ||
* * `M` for months | ||
@@ -413,3 +452,4 @@ * * `y` for years | ||
* * Add padding by repeating the token, e.g. "yy" pads the years to two digits, "hhhh" pads the hours out to four digits | ||
* * The duration will be converted to the set of units in the format string using {@link Duration.shiftTo} and the Durations's conversion accuracy setting. | ||
* * Tokens can be escaped by wrapping with single quotes. | ||
* * The duration will be converted to the set of units in the format string using {@link Duration#shiftTo} and the Durations's conversion accuracy setting. | ||
* @param {string} fmt - the format string | ||
@@ -425,5 +465,6 @@ * @param {Object} opts - options | ||
// reverse-compat since 1.2; we always round down now, never up, and we do it by default | ||
const fmtOpts = Object.assign({}, opts, { | ||
floor: opts.round !== false && opts.floor !== false | ||
}); | ||
const fmtOpts = { | ||
...opts, | ||
floor: opts.round !== false && opts.floor !== false, | ||
}; | ||
return this.isValid | ||
@@ -435,19 +476,40 @@ ? Formatter.create(this.loc, fmtOpts).formatDurationFromString(this, fmt) | ||
/** | ||
* Returns a string representation of a Duration with all units included. | ||
* To modify its behavior use the `listStyle` and any Intl.NumberFormat option, though `unitDisplay` is especially relevant. | ||
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat | ||
* @param opts - On option object to override the formatting. Accepts the same keys as the options parameter of the native `Int.NumberFormat` constructor, as well as `listStyle`. | ||
* @example | ||
* ```js | ||
* var dur = Duration.fromObject({ days: 1, hours: 5, minutes: 6 }) | ||
* dur.toHuman() //=> '1 day, 5 hours, 6 minutes' | ||
* dur.toHuman({ listStyle: "long" }) //=> '1 day, 5 hours, and 6 minutes' | ||
* dur.toHuman({ unitDisplay: "short" }) //=> '1 day, 5 hr, 6 min' | ||
* ``` | ||
*/ | ||
toHuman(opts = {}) { | ||
const l = orderedUnits | ||
.map((unit) => { | ||
const val = this.values[unit]; | ||
if (isUndefined(val)) { | ||
return null; | ||
} | ||
return this.loc | ||
.numberFormatter({ style: "unit", unitDisplay: "long", ...opts, unit: unit.slice(0, -1) }) | ||
.format(val); | ||
}) | ||
.filter((n) => n); | ||
return this.loc | ||
.listFormatter({ type: "conjunction", style: opts.listStyle || "narrow", ...opts }) | ||
.format(l); | ||
} | ||
/** | ||
* Returns a JavaScript object with this Duration's values. | ||
* @param opts - options for generating the object | ||
* @param {boolean} [opts.includeConfig=false] - include configuration attributes in the output | ||
* @example Duration.fromObject({ years: 1, days: 6, seconds: 2 }).toObject() //=> { years: 1, days: 6, seconds: 2 } | ||
* @return {Object} | ||
*/ | ||
toObject(opts = {}) { | ||
toObject() { | ||
if (!this.isValid) return {}; | ||
const base = Object.assign({}, this.values); | ||
if (opts.includeConfig) { | ||
base.conversionAccuracy = this.conversionAccuracy; | ||
base.numberingSystem = this.loc.numberingSystem; | ||
base.locale = this.loc.locale; | ||
} | ||
return base; | ||
return { ...this.values }; | ||
} | ||
@@ -508,30 +570,13 @@ | ||
opts = Object.assign( | ||
{ | ||
suppressMilliseconds: false, | ||
suppressSeconds: false, | ||
includePrefix: false, | ||
format: "extended" | ||
}, | ||
opts | ||
); | ||
opts = { | ||
suppressMilliseconds: false, | ||
suppressSeconds: false, | ||
includePrefix: false, | ||
format: "extended", | ||
...opts, | ||
includeOffset: false, | ||
}; | ||
const value = this.shiftTo("hours", "minutes", "seconds", "milliseconds"); | ||
let fmt = opts.format === "basic" ? "hhmm" : "hh:mm"; | ||
if (!opts.suppressSeconds || value.seconds !== 0 || value.milliseconds !== 0) { | ||
fmt += opts.format === "basic" ? "ss" : ":ss"; | ||
if (!opts.suppressMilliseconds || value.milliseconds !== 0) { | ||
fmt += ".SSS"; | ||
} | ||
} | ||
let str = value.toFormat(fmt); | ||
if (opts.includePrefix) { | ||
str = "T" + str; | ||
} | ||
return str; | ||
const dateTime = DateTime.fromMillis(millis, { zone: "UTC" }); | ||
return dateTime.toISOTime(opts); | ||
} | ||
@@ -560,3 +605,9 @@ | ||
toMillis() { | ||
return this.as("milliseconds"); | ||
let sum = this.values.milliseconds ?? 0; | ||
for (let unit of reverseUnits.slice(1)) { | ||
if (this.values?.[unit]) { | ||
sum += this.values[unit] * this.matrix[unit]["milliseconds"]; | ||
} | ||
} | ||
return sum; | ||
} | ||
@@ -580,3 +631,3 @@ | ||
const dur = friendlyDuration(duration), | ||
const dur = Duration.fromDurationLike(duration), | ||
result = {}; | ||
@@ -601,3 +652,3 @@ | ||
const dur = friendlyDuration(duration); | ||
const dur = Duration.fromDurationLike(duration); | ||
return this.plus(dur.negate()); | ||
@@ -609,4 +660,4 @@ } | ||
* @param {function} fn - The function to apply to each unit. Arity is 1 or 2: the value of the unit and, optionally, the unit name. Must return a number. | ||
* @example Duration.fromObject({ hours: 1, minutes: 30 }).mapUnit(x => x * 2) //=> { hours: 2, minutes: 60 } | ||
* @example Duration.fromObject({ hours: 1, minutes: 30 }).mapUnit((x, u) => u === "hour" ? x * 2 : x) //=> { hours: 2, minutes: 30 } | ||
* @example Duration.fromObject({ hours: 1, minutes: 30 }).mapUnits(x => x * 2) //=> { hours: 2, minutes: 60 } | ||
* @example Duration.fromObject({ hours: 1, minutes: 30 }).mapUnits((x, u) => u === "hours" ? x * 2 : x) //=> { hours: 2, minutes: 30 } | ||
* @return {Duration} | ||
@@ -645,3 +696,3 @@ */ | ||
const mixed = Object.assign(this.values, normalizeObject(values, Duration.normalizeUnit, [])); | ||
const mixed = { ...this.values, ...normalizeObject(values, Duration.normalizeUnit) }; | ||
return clone(this, { values: mixed }); | ||
@@ -655,10 +706,5 @@ } | ||
*/ | ||
reconfigure({ locale, numberingSystem, conversionAccuracy } = {}) { | ||
const loc = this.loc.clone({ locale, numberingSystem }), | ||
opts = { loc }; | ||
if (conversionAccuracy) { | ||
opts.conversionAccuracy = conversionAccuracy; | ||
} | ||
reconfigure({ locale, numberingSystem, conversionAccuracy, matrix } = {}) { | ||
const loc = this.loc.clone({ locale, numberingSystem }); | ||
const opts = { loc, matrix, conversionAccuracy }; | ||
return clone(this, opts); | ||
@@ -688,3 +734,17 @@ } | ||
const vals = this.toObject(); | ||
normalizeValues(this.matrix, vals); | ||
if (this.valueOf() >= 0) { | ||
normalizeValues(this.matrix, vals); | ||
return clone(this, { values: vals }, true); | ||
} | ||
return this.negate().normalize().negate(); | ||
} | ||
/** | ||
* Rescale units to its largest representation | ||
* @example Duration.fromObject({ milliseconds: 90000 }).rescale().toObject() //=> { minutes: 1, seconds: 30 } | ||
* @return {Duration} | ||
*/ | ||
rescale() { | ||
if (!this.isValid) return this; | ||
const vals = removeZeroes(this.normalize().shiftToAll().toObject()); | ||
return clone(this, { values: vals }, true); | ||
@@ -705,3 +765,3 @@ } | ||
units = units.map(u => Duration.normalizeUnit(u)); | ||
units = units.map((u) => Duration.normalizeUnit(u)); | ||
@@ -732,3 +792,3 @@ const built = {}, | ||
built[k] = i; | ||
accumulated[k] = own - i; // we'd like to absorb these fractions in another unit | ||
accumulated[k] = (own * 1000 - i * 1000) / 1000; | ||
@@ -760,2 +820,21 @@ // plus anything further down the chain that should be rolled up in to this | ||
/** | ||
* Shift this Duration to all available units. | ||
* Same as shiftTo("years", "months", "weeks", "days", "hours", "minutes", "seconds", "milliseconds") | ||
* @return {Duration} | ||
*/ | ||
shiftToAll() { | ||
if (!this.isValid) return this; | ||
return this.shiftTo( | ||
"years", | ||
"months", | ||
"weeks", | ||
"days", | ||
"hours", | ||
"minutes", | ||
"seconds", | ||
"milliseconds" | ||
); | ||
} | ||
/** | ||
* Return the negative of this Duration. | ||
@@ -769,3 +848,3 @@ * @example Duration.fromObject({ hours: 1, seconds: 30 }).negate().toObject() //=> { hours: -1, seconds: -30 } | ||
for (const k of Object.keys(this.values)) { | ||
negated[k] = -this.values[k]; | ||
negated[k] = this.values[k] === 0 ? 0 : -this.values[k]; | ||
} | ||
@@ -901,18 +980,1 @@ return clone(this, { values: negated }, true); | ||
} | ||
/** | ||
* @private | ||
*/ | ||
export function friendlyDuration(durationish) { | ||
if (isNumber(durationish)) { | ||
return Duration.fromMillis(durationish); | ||
} else if (Duration.isDuration(durationish)) { | ||
return durationish; | ||
} else if (typeof durationish === "object") { | ||
return Duration.fromObject(durationish); | ||
} else { | ||
throw new InvalidArgumentError( | ||
`Unknown duration argument ${durationish} of type ${typeof durationish}` | ||
); | ||
} | ||
} |
@@ -8,3 +8,3 @@ import { | ||
weeksInWeekYear, | ||
isInteger | ||
isInteger, | ||
} from "./util.js"; | ||
@@ -24,3 +24,10 @@ import Invalid from "./invalid.js"; | ||
function dayOfWeek(year, month, day) { | ||
const js = new Date(Date.UTC(year, month - 1, day)).getUTCDay(); | ||
const d = new Date(Date.UTC(year, month - 1, day)); | ||
if (year < 100 && year >= 0) { | ||
d.setUTCFullYear(d.getUTCFullYear() - 1900); | ||
} | ||
const js = d.getUTCDay(); | ||
return js === 0 ? 7 : js; | ||
@@ -35,3 +42,3 @@ } | ||
const table = isLeapYear(year) ? leapLadder : nonLeapLadder, | ||
month0 = table.findIndex(i => i < ordinal), | ||
month0 = table.findIndex((i) => i < ordinal), | ||
day = ordinal - table[month0]; | ||
@@ -63,3 +70,3 @@ return { month: month0 + 1, day }; | ||
return Object.assign({ weekYear, weekNumber, weekday }, timeObject(gregObj)); | ||
return { weekYear, weekNumber, weekday, ...timeObject(gregObj) }; | ||
} | ||
@@ -86,18 +93,15 @@ | ||
const { month, day } = uncomputeOrdinal(year, ordinal); | ||
return Object.assign({ year, month, day }, timeObject(weekData)); | ||
return { year, month, day, ...timeObject(weekData) }; | ||
} | ||
export function gregorianToOrdinal(gregData) { | ||
const { year, month, day } = gregData, | ||
ordinal = computeOrdinal(year, month, day); | ||
return Object.assign({ year, ordinal }, timeObject(gregData)); | ||
const { year, month, day } = gregData; | ||
const ordinal = computeOrdinal(year, month, day); | ||
return { year, ordinal, ...timeObject(gregData) }; | ||
} | ||
export function ordinalToGregorian(ordinalData) { | ||
const { year, ordinal } = ordinalData, | ||
{ month, day } = uncomputeOrdinal(year, ordinal); | ||
return Object.assign({ year, month, day }, timeObject(ordinalData)); | ||
const { year, ordinal } = ordinalData; | ||
const { month, day } = uncomputeOrdinal(year, ordinal); | ||
return { year, month, day, ...timeObject(ordinalData) }; | ||
} | ||
@@ -104,0 +108,0 @@ |
import Duration from "../duration.js"; | ||
function dayDiff(earlier, later) { | ||
const utcDayStart = dt => | ||
dt | ||
.toUTC(0, { keepLocalTime: true }) | ||
.startOf("day") | ||
.valueOf(), | ||
const utcDayStart = (dt) => dt.toUTC(0, { keepLocalTime: true }).startOf("day").valueOf(), | ||
ms = utcDayStart(later) - utcDayStart(earlier); | ||
@@ -16,3 +12,3 @@ return Math.floor(Duration.fromMillis(ms).as("days")); | ||
["years", (a, b) => b.year - a.year], | ||
["quarters", (a, b) => b.quarter - a.quarter], | ||
["quarters", (a, b) => b.quarter - a.quarter + (b.year - a.year) * 4], | ||
["months", (a, b) => b.month - a.month + (b.year - a.year) * 12], | ||
@@ -24,10 +20,19 @@ [ | ||
return (days - (days % 7)) / 7; | ||
} | ||
}, | ||
], | ||
["days", dayDiff] | ||
["days", dayDiff], | ||
]; | ||
const results = {}; | ||
const earlier = cursor; | ||
let lowestOrder, highWater; | ||
/* This loop tries to diff using larger units first. | ||
If we overshoot, we backtrack and try the next smaller unit. | ||
"cursor" starts out at the earlier timestamp and moves closer and closer to "later" | ||
as we use smaller and smaller units. | ||
highWater keeps track of where we would be if we added one more of the smallest unit, | ||
this is used later to potentially convert any difference smaller than the smallest higher order unit | ||
into a fraction of that smallest higher order unit | ||
*/ | ||
for (const [unit, differ] of differs) { | ||
@@ -37,13 +42,23 @@ if (units.indexOf(unit) >= 0) { | ||
let delta = differ(cursor, later); | ||
highWater = cursor.plus({ [unit]: delta }); | ||
results[unit] = differ(cursor, later); | ||
highWater = earlier.plus(results); | ||
if (highWater > later) { | ||
cursor = cursor.plus({ [unit]: delta - 1 }); | ||
delta -= 1; | ||
// we overshot the end point, backtrack cursor by 1 | ||
results[unit]--; | ||
cursor = earlier.plus(results); | ||
// if we are still overshooting now, we need to backtrack again | ||
// this happens in certain situations when diffing times in different zones, | ||
// because this calculation ignores time zones | ||
if (cursor > later) { | ||
// keep the "overshot by 1" around as highWater | ||
highWater = cursor; | ||
// backtrack cursor by 1 | ||
results[unit]--; | ||
cursor = earlier.plus(results); | ||
} | ||
} else { | ||
cursor = highWater; | ||
} | ||
results[unit] = delta; | ||
} | ||
@@ -55,3 +70,3 @@ } | ||
export default function(earlier, later, units, opts) { | ||
export default function (earlier, later, units, opts) { | ||
let [cursor, results, highWater, lowestOrder] = highOrderDiffs(earlier, later, units); | ||
@@ -62,3 +77,3 @@ | ||
const lowerOrderUnits = units.filter( | ||
u => ["hours", "minutes", "seconds", "milliseconds"].indexOf(u) >= 0 | ||
(u) => ["hours", "minutes", "seconds", "milliseconds"].indexOf(u) >= 0 | ||
); | ||
@@ -76,3 +91,3 @@ | ||
const duration = Duration.fromObject(Object.assign(results, opts)); | ||
const duration = Duration.fromObject(results, opts); | ||
@@ -79,0 +94,0 @@ if (lowerOrderUnits.length > 0) { |
@@ -22,3 +22,3 @@ const numberingSystems = { | ||
tibt: "[\u0F20-\u0F29]", | ||
latn: "\\d" | ||
latn: "\\d", | ||
}; | ||
@@ -45,6 +45,5 @@ | ||
thai: [3664, 3673], | ||
tibt: [3872, 3881] | ||
tibt: [3872, 3881], | ||
}; | ||
// eslint-disable-next-line | ||
const hanidecChars = numberingSystems.hanidec.replace(/[\[|\]]/g, "").split(""); | ||
@@ -51,0 +50,0 @@ |
@@ -24,3 +24,3 @@ import * as Formats from "./formats.js"; | ||
"November", | ||
"December" | ||
"December", | ||
]; | ||
@@ -40,3 +40,3 @@ | ||
"Nov", | ||
"Dec" | ||
"Dec", | ||
]; | ||
@@ -70,3 +70,3 @@ | ||
"Saturday", | ||
"Sunday" | ||
"Sunday", | ||
]; | ||
@@ -139,3 +139,3 @@ | ||
minutes: ["minute", "min."], | ||
seconds: ["second", "sec."] | ||
seconds: ["second", "sec."], | ||
}; | ||
@@ -167,4 +167,4 @@ | ||
: singular | ||
? units[unit][0] | ||
: unit; | ||
? units[unit][0] | ||
: unit; | ||
return isInPast ? `${fmtValue} ${fmtUnit} ago` : `in ${fmtValue} ${fmtUnit}`; | ||
@@ -186,3 +186,3 @@ } | ||
"timeZoneName", | ||
"hour12" | ||
"hourCycle", | ||
]), | ||
@@ -189,0 +189,0 @@ key = stringify(filtered), |
@@ -12,3 +12,3 @@ /** | ||
month: n, | ||
day: n | ||
day: n, | ||
}; | ||
@@ -19,3 +19,3 @@ | ||
month: s, | ||
day: n | ||
day: n, | ||
}; | ||
@@ -27,3 +27,3 @@ | ||
day: n, | ||
weekday: s | ||
weekday: s, | ||
}; | ||
@@ -34,3 +34,3 @@ | ||
month: l, | ||
day: n | ||
day: n, | ||
}; | ||
@@ -42,3 +42,3 @@ | ||
day: n, | ||
weekday: l | ||
weekday: l, | ||
}; | ||
@@ -48,3 +48,3 @@ | ||
hour: n, | ||
minute: n | ||
minute: n, | ||
}; | ||
@@ -55,3 +55,3 @@ | ||
minute: n, | ||
second: n | ||
second: n, | ||
}; | ||
@@ -63,3 +63,3 @@ | ||
second: n, | ||
timeZoneName: s | ||
timeZoneName: s, | ||
}; | ||
@@ -71,3 +71,3 @@ | ||
second: n, | ||
timeZoneName: l | ||
timeZoneName: l, | ||
}; | ||
@@ -78,8 +78,5 @@ | ||
minute: n, | ||
hour12: false | ||
hourCycle: "h23", | ||
}; | ||
/** | ||
* {@link toLocaleString}; format like '09:30:23', always 24-hour. | ||
*/ | ||
export const TIME_24_WITH_SECONDS = { | ||
@@ -89,8 +86,5 @@ hour: n, | ||
second: n, | ||
hour12: false | ||
hourCycle: "h23", | ||
}; | ||
/** | ||
* {@link toLocaleString}; format like '09:30:23 EDT', always 24-hour. | ||
*/ | ||
export const TIME_24_WITH_SHORT_OFFSET = { | ||
@@ -100,9 +94,6 @@ hour: n, | ||
second: n, | ||
hour12: false, | ||
timeZoneName: s | ||
hourCycle: "h23", | ||
timeZoneName: s, | ||
}; | ||
/** | ||
* {@link toLocaleString}; format like '09:30:23 Eastern Daylight Time', always 24-hour. | ||
*/ | ||
export const TIME_24_WITH_LONG_OFFSET = { | ||
@@ -112,9 +103,6 @@ hour: n, | ||
second: n, | ||
hour12: false, | ||
timeZoneName: l | ||
hourCycle: "h23", | ||
timeZoneName: l, | ||
}; | ||
/** | ||
* {@link toLocaleString}; format like '10/14/1983, 9:30 AM'. Only 12-hour if the locale is. | ||
*/ | ||
export const DATETIME_SHORT = { | ||
@@ -125,8 +113,5 @@ year: n, | ||
hour: n, | ||
minute: n | ||
minute: n, | ||
}; | ||
/** | ||
* {@link toLocaleString}; format like '10/14/1983, 9:30:33 AM'. Only 12-hour if the locale is. | ||
*/ | ||
export const DATETIME_SHORT_WITH_SECONDS = { | ||
@@ -138,3 +123,3 @@ year: n, | ||
minute: n, | ||
second: n | ||
second: n, | ||
}; | ||
@@ -147,3 +132,3 @@ | ||
hour: n, | ||
minute: n | ||
minute: n, | ||
}; | ||
@@ -157,3 +142,3 @@ | ||
minute: n, | ||
second: n | ||
second: n, | ||
}; | ||
@@ -167,3 +152,3 @@ | ||
hour: n, | ||
minute: n | ||
minute: n, | ||
}; | ||
@@ -177,3 +162,3 @@ | ||
minute: n, | ||
timeZoneName: s | ||
timeZoneName: s, | ||
}; | ||
@@ -188,3 +173,3 @@ | ||
second: n, | ||
timeZoneName: s | ||
timeZoneName: s, | ||
}; | ||
@@ -199,3 +184,3 @@ | ||
minute: n, | ||
timeZoneName: l | ||
timeZoneName: l, | ||
}; | ||
@@ -211,3 +196,3 @@ | ||
second: n, | ||
timeZoneName: l | ||
timeZoneName: l, | ||
}; |
import * as English from "./english.js"; | ||
import * as Formats from "./formats.js"; | ||
import { hasFormatToParts, padStart } from "./util.js"; | ||
import { padStart } from "./util.js"; | ||
@@ -37,3 +37,3 @@ function stringifyTokens(splits, tokenToString) { | ||
FFF: Formats.DATETIME_FULL_WITH_SECONDS, | ||
FFFF: Formats.DATETIME_HUGE_WITH_SECONDS | ||
FFFF: Formats.DATETIME_HUGE_WITH_SECONDS, | ||
}; | ||
@@ -51,2 +51,5 @@ | ||
static parseFormat(fmt) { | ||
// white-space is always considered a literal in user-provided formats | ||
// the " " token has a special meaning (see unitForToken) | ||
let current = null, | ||
@@ -60,3 +63,3 @@ currentFull = "", | ||
if (currentFull.length > 0) { | ||
splits.push({ literal: bracketed, val: currentFull }); | ||
splits.push({ literal: bracketed || /^\s+$/.test(currentFull), val: currentFull }); | ||
} | ||
@@ -72,3 +75,3 @@ current = null; | ||
if (currentFull.length > 0) { | ||
splits.push({ literal: false, val: currentFull }); | ||
splits.push({ literal: /^\s+$/.test(currentFull), val: currentFull }); | ||
} | ||
@@ -81,3 +84,3 @@ currentFull = c; | ||
if (currentFull.length > 0) { | ||
splits.push({ literal: bracketed, val: currentFull }); | ||
splits.push({ literal: bracketed || /^\s+$/.test(currentFull), val: currentFull }); | ||
} | ||
@@ -102,21 +105,27 @@ | ||
} | ||
const df = this.systemLoc.dtFormatter(dt, Object.assign({}, this.opts, opts)); | ||
const df = this.systemLoc.dtFormatter(dt, { ...this.opts, ...opts }); | ||
return df.format(); | ||
} | ||
formatDateTime(dt, opts = {}) { | ||
const df = this.loc.dtFormatter(dt, Object.assign({}, this.opts, opts)); | ||
return df.format(); | ||
dtFormatter(dt, opts = {}) { | ||
return this.loc.dtFormatter(dt, { ...this.opts, ...opts }); | ||
} | ||
formatDateTimeParts(dt, opts = {}) { | ||
const df = this.loc.dtFormatter(dt, Object.assign({}, this.opts, opts)); | ||
return df.formatToParts(); | ||
formatDateTime(dt, opts) { | ||
return this.dtFormatter(dt, opts).format(); | ||
} | ||
resolvedOptions(dt, opts = {}) { | ||
const df = this.loc.dtFormatter(dt, Object.assign({}, this.opts, opts)); | ||
return df.resolvedOptions(); | ||
formatDateTimeParts(dt, opts) { | ||
return this.dtFormatter(dt, opts).formatToParts(); | ||
} | ||
formatInterval(interval, opts) { | ||
const df = this.dtFormatter(interval.start, opts); | ||
return df.dtf.formatRange(interval.start.toJSDate(), interval.end.toJSDate()); | ||
} | ||
resolvedOptions(dt, opts) { | ||
return this.dtFormatter(dt, opts).resolvedOptions(); | ||
} | ||
num(n, p = 0) { | ||
@@ -128,3 +137,3 @@ // we get some perf out of doing this here, annoyingly | ||
const opts = Object.assign({}, this.opts); | ||
const opts = { ...this.opts }; | ||
@@ -140,6 +149,5 @@ if (p > 0) { | ||
const knownEnglish = this.loc.listingMode() === "en", | ||
useDateTimeFormatter = | ||
this.loc.outputCalendar && this.loc.outputCalendar !== "gregory" && hasFormatToParts(), | ||
useDateTimeFormatter = this.loc.outputCalendar && this.loc.outputCalendar !== "gregory", | ||
string = (opts, extract) => this.loc.extract(dt, opts, extract), | ||
formatOffset = opts => { | ||
formatOffset = (opts) => { | ||
if (dt.isOffsetFixed && dt.offset === 0 && opts.allowZ) { | ||
@@ -154,3 +162,3 @@ return "Z"; | ||
? English.meridiemForDateTime(dt) | ||
: string({ hour: "numeric", hour12: true }, "dayperiod"), | ||
: string({ hour: "numeric", hourCycle: "h12" }, "dayperiod"), | ||
month = (length, standalone) => | ||
@@ -167,3 +175,3 @@ knownEnglish | ||
), | ||
maybeMacro = token => { | ||
maybeMacro = (token) => { | ||
const formatOpts = Formatter.macroTokenToFormatOpts(token); | ||
@@ -176,6 +184,6 @@ if (formatOpts) { | ||
}, | ||
era = length => | ||
era = (length) => | ||
knownEnglish ? English.eraForDateTime(dt, length) : string({ era: length }, "era"), | ||
tokenToString = token => { | ||
// Where possible: http://cldr.unicode.org/translation/date-time-1/date-time#TOC-Standalone-vs.-Format-Styles | ||
tokenToString = (token) => { | ||
// Where possible: https://cldr.unicode.org/translation/date-time/date-time-symbols | ||
switch (token) { | ||
@@ -194,2 +202,7 @@ // ms | ||
return this.num(dt.second, 2); | ||
// fractional seconds | ||
case "uu": | ||
return this.num(Math.floor(dt.millisecond / 10), 2); | ||
case "uuu": | ||
return this.num(Math.floor(dt.millisecond / 100)); | ||
// minutes | ||
@@ -362,3 +375,3 @@ case "m": | ||
formatDurationFromString(dur, fmt) { | ||
const tokenToField = token => { | ||
const tokenToField = (token) => { | ||
switch (token[0]) { | ||
@@ -375,2 +388,4 @@ case "S": | ||
return "day"; | ||
case "w": | ||
return "week"; | ||
case "M": | ||
@@ -384,3 +399,3 @@ return "month"; | ||
}, | ||
tokenToString = lildur => token => { | ||
tokenToString = (lildur) => (token) => { | ||
const mapped = tokenToField(token); | ||
@@ -398,5 +413,5 @@ if (mapped) { | ||
), | ||
collapsed = dur.shiftTo(...realTokens.map(tokenToField).filter(t => t)); | ||
collapsed = dur.shiftTo(...realTokens.map(tokenToField).filter((t) => t)); | ||
return stringifyTokens(tokens, tokenToString(collapsed)); | ||
} | ||
} |
@@ -1,8 +0,20 @@ | ||
import { hasFormatToParts, hasIntl, padStart, roundTo, hasRelative } from "./util.js"; | ||
import { padStart, roundTo, hasRelative, formatOffset } from "./util.js"; | ||
import * as English from "./english.js"; | ||
import Settings from "../settings.js"; | ||
import DateTime from "../datetime.js"; | ||
import Formatter from "./formatter.js"; | ||
import IANAZone from "../zones/IANAZone.js"; | ||
// todo - remap caching | ||
let intlLFCache = {}; | ||
function getCachedLF(locString, opts = {}) { | ||
const key = JSON.stringify([locString, opts]); | ||
let dtf = intlLFCache[key]; | ||
if (!dtf) { | ||
dtf = new Intl.ListFormat(locString, opts); | ||
intlLFCache[key] = dtf; | ||
} | ||
return dtf; | ||
} | ||
let intlDTCache = {}; | ||
@@ -46,9 +58,4 @@ function getCachedDTF(locString, opts = {}) { | ||
return sysLocaleCache; | ||
} else if (hasIntl()) { | ||
const computedSys = new Intl.DateTimeFormat().resolvedOptions().locale; | ||
// node sometimes defaults to "und". Override that because that is dumb | ||
sysLocaleCache = !computedSys || computedSys === "und" ? "en-US" : computedSys; | ||
return sysLocaleCache; | ||
} else { | ||
sysLocaleCache = "en-US"; | ||
sysLocaleCache = new Intl.DateTimeFormat().resolvedOptions().locale; | ||
return sysLocaleCache; | ||
@@ -67,2 +74,10 @@ } | ||
// private subtags and unicode subtags have ordering requirements, | ||
// and we're not properly parsing this, so just strip out the | ||
// private ones if they exist. | ||
const xIndex = localeStr.indexOf("-x-"); | ||
if (xIndex !== -1) { | ||
localeStr = localeStr.substring(0, xIndex); | ||
} | ||
const uIndex = localeStr.indexOf("-u-"); | ||
@@ -73,12 +88,14 @@ if (uIndex === -1) { | ||
let options; | ||
const smaller = localeStr.substring(0, uIndex); | ||
let selectedStr; | ||
try { | ||
options = getCachedDTF(localeStr).resolvedOptions(); | ||
selectedStr = localeStr; | ||
} catch (e) { | ||
const smaller = localeStr.substring(0, uIndex); | ||
options = getCachedDTF(smaller).resolvedOptions(); | ||
selectedStr = smaller; | ||
} | ||
const { numberingSystem, calendar } = options; | ||
// return the smaller one so that we can append the calendar and numbering overrides to it | ||
return [smaller, numberingSystem, calendar]; | ||
return [selectedStr, numberingSystem, calendar]; | ||
} | ||
@@ -88,19 +105,17 @@ } | ||
function intlConfigString(localeStr, numberingSystem, outputCalendar) { | ||
if (hasIntl()) { | ||
if (outputCalendar || numberingSystem) { | ||
if (outputCalendar || numberingSystem) { | ||
if (!localeStr.includes("-u-")) { | ||
localeStr += "-u"; | ||
} | ||
if (outputCalendar) { | ||
localeStr += `-ca-${outputCalendar}`; | ||
} | ||
if (outputCalendar) { | ||
localeStr += `-ca-${outputCalendar}`; | ||
} | ||
if (numberingSystem) { | ||
localeStr += `-nu-${numberingSystem}`; | ||
} | ||
return localeStr; | ||
} else { | ||
return localeStr; | ||
if (numberingSystem) { | ||
localeStr += `-nu-${numberingSystem}`; | ||
} | ||
return localeStr; | ||
} else { | ||
return []; | ||
return localeStr; | ||
} | ||
@@ -112,3 +127,3 @@ } | ||
for (let i = 1; i <= 12; i++) { | ||
const dt = DateTime.utc(2016, i, 1); | ||
const dt = DateTime.utc(2009, i, 1); | ||
ms.push(f(dt)); | ||
@@ -128,4 +143,4 @@ } | ||
function listStuff(loc, length, defaultOK, englishFn, intlFn) { | ||
const mode = loc.listingMode(defaultOK); | ||
function listStuff(loc, length, englishFn, intlFn) { | ||
const mode = loc.listingMode(); | ||
@@ -149,3 +164,3 @@ if (mode === "error") { | ||
loc.locale.startsWith("en") || | ||
(hasIntl() && new Intl.DateTimeFormat(loc.intl).resolvedOptions().numberingSystem === "latn") | ||
new Intl.DateTimeFormat(loc.intl).resolvedOptions().numberingSystem === "latn" | ||
); | ||
@@ -164,4 +179,6 @@ } | ||
if (!forceSimple && hasIntl()) { | ||
const intlOpts = { useGrouping: false }; | ||
const { padTo, floor, ...otherOpts } = opts; | ||
if (!forceSimple || Object.keys(otherOpts).length > 0) { | ||
const intlOpts = { useGrouping: false, ...opts }; | ||
if (opts.padTo > 0) intlOpts.minimumIntegerDigits = opts.padTo; | ||
@@ -191,6 +208,9 @@ this.inf = getCachedINF(intl, intlOpts); | ||
this.opts = opts; | ||
this.hasIntl = hasIntl(); | ||
this.originalZone = undefined; | ||
let z; | ||
if (dt.zone.universal && this.hasIntl) { | ||
let z = undefined; | ||
if (this.opts.timeZone) { | ||
// Don't apply any workarounds if a timeZone is explicitly provided in opts | ||
this.dt = dt; | ||
} else if (dt.zone.type === "fixed") { | ||
// UTC-8 or Etc/UTC-8 are not part of tzdata, only Etc/GMT+8 and the like. | ||
@@ -204,67 +224,64 @@ // That is why fixed-offset TZ is set to that unless it is: | ||
const offsetZ = gmtOffset >= 0 ? `Etc/GMT+${gmtOffset}` : `Etc/GMT${gmtOffset}`; | ||
const isOffsetZoneSupported = IANAZone.isValidZone(offsetZ); | ||
if (dt.offset !== 0 && isOffsetZoneSupported) { | ||
if (dt.offset !== 0 && IANAZone.create(offsetZ).valid) { | ||
z = offsetZ; | ||
this.dt = dt; | ||
} else { | ||
// Not all fixed-offset zones like Etc/+4:30 are present in tzdata. | ||
// So we have to make do. Two cases: | ||
// 1. The format options tell us to show the zone. We can't do that, so the best | ||
// we can do is format the date in UTC. | ||
// 2. The format options don't tell us to show the zone. Then we can adjust them | ||
// the time and tell the formatter to show it to us in UTC, so that the time is right | ||
// and the bad zone doesn't show up. | ||
// Not all fixed-offset zones like Etc/+4:30 are present in tzdata so | ||
// we manually apply the offset and substitute the zone as needed. | ||
z = "UTC"; | ||
if (opts.timeZoneName) { | ||
this.dt = dt; | ||
} else { | ||
this.dt = dt.offset === 0 ? dt : DateTime.fromMillis(dt.ts + dt.offset * 60 * 1000); | ||
} | ||
this.dt = dt.offset === 0 ? dt : dt.setZone("UTC").plus({ minutes: dt.offset }); | ||
this.originalZone = dt.zone; | ||
} | ||
} else if (dt.zone.type === "local") { | ||
} else if (dt.zone.type === "system") { | ||
this.dt = dt; | ||
} else { | ||
} else if (dt.zone.type === "iana") { | ||
this.dt = dt; | ||
z = dt.zone.name; | ||
} else { | ||
// Custom zones can have any offset / offsetName so we just manually | ||
// apply the offset and substitute the zone as needed. | ||
z = "UTC"; | ||
this.dt = dt.setZone("UTC").plus({ minutes: dt.offset }); | ||
this.originalZone = dt.zone; | ||
} | ||
if (this.hasIntl) { | ||
const intlOpts = Object.assign({}, this.opts); | ||
if (z) { | ||
intlOpts.timeZone = z; | ||
} | ||
this.dtf = getCachedDTF(intl, intlOpts); | ||
} | ||
const intlOpts = { ...this.opts }; | ||
intlOpts.timeZone = intlOpts.timeZone || z; | ||
this.dtf = getCachedDTF(intl, intlOpts); | ||
} | ||
format() { | ||
if (this.hasIntl) { | ||
return this.dtf.format(this.dt.toJSDate()); | ||
} else { | ||
const tokenFormat = English.formatString(this.opts), | ||
loc = Locale.create("en-US"); | ||
return Formatter.create(loc).formatDateTimeFromString(this.dt, tokenFormat); | ||
if (this.originalZone) { | ||
// If we have to substitute in the actual zone name, we have to use | ||
// formatToParts so that the timezone can be replaced. | ||
return this.formatToParts() | ||
.map(({ value }) => value) | ||
.join(""); | ||
} | ||
return this.dtf.format(this.dt.toJSDate()); | ||
} | ||
formatToParts() { | ||
if (this.hasIntl && hasFormatToParts()) { | ||
return this.dtf.formatToParts(this.dt.toJSDate()); | ||
} else { | ||
// This is kind of a cop out. We actually could do this for English. However, we couldn't do it for intl strings | ||
// and IMO it's too weird to have an uncanny valley like that | ||
return []; | ||
const parts = this.dtf.formatToParts(this.dt.toJSDate()); | ||
if (this.originalZone) { | ||
return parts.map((part) => { | ||
if (part.type === "timeZoneName") { | ||
const offsetName = this.originalZone.offsetName(this.dt.ts, { | ||
locale: this.dt.locale, | ||
format: this.opts.timeZoneName, | ||
}); | ||
return { | ||
...part, | ||
value: offsetName, | ||
}; | ||
} else { | ||
return part; | ||
} | ||
}); | ||
} | ||
return parts; | ||
} | ||
resolvedOptions() { | ||
if (this.hasIntl) { | ||
return this.dtf.resolvedOptions(); | ||
} else { | ||
return { | ||
locale: "en-US", | ||
numberingSystem: "latn", | ||
outputCalendar: "gregory" | ||
}; | ||
} | ||
return this.dtf.resolvedOptions(); | ||
} | ||
@@ -278,3 +295,3 @@ } | ||
constructor(intl, isEnglish, opts) { | ||
this.opts = Object.assign({ style: "long" }, opts); | ||
this.opts = { style: "long", ...opts }; | ||
if (!isEnglish && hasRelative()) { | ||
@@ -312,7 +329,7 @@ this.rtf = getCachedRTF(intl, opts); | ||
static create(locale, numberingSystem, outputCalendar, defaultToEN = false) { | ||
const specifiedLocale = locale || Settings.defaultLocale, | ||
// the system locale is useful for human readable strings but annoying for parsing/formatting known formats | ||
localeR = specifiedLocale || (defaultToEN ? "en-US" : systemLocale()), | ||
numberingSystemR = numberingSystem || Settings.defaultNumberingSystem, | ||
outputCalendarR = outputCalendar || Settings.defaultOutputCalendar; | ||
const specifiedLocale = locale || Settings.defaultLocale; | ||
// the system locale is useful for human readable strings but annoying for parsing/formatting known formats | ||
const localeR = specifiedLocale || (defaultToEN ? "en-US" : systemLocale()); | ||
const numberingSystemR = numberingSystem || Settings.defaultNumberingSystem; | ||
const outputCalendarR = outputCalendar || Settings.defaultOutputCalendar; | ||
return new Locale(localeR, numberingSystemR, outputCalendarR, specifiedLocale); | ||
@@ -357,17 +374,8 @@ } | ||
listingMode(defaultOK = true) { | ||
const intl = hasIntl(), | ||
hasFTP = intl && hasFormatToParts(), | ||
isActuallyEn = this.isEnglish(), | ||
hasNoWeirdness = | ||
(this.numberingSystem === null || this.numberingSystem === "latn") && | ||
(this.outputCalendar === null || this.outputCalendar === "gregory"); | ||
if (!hasFTP && !(isActuallyEn && hasNoWeirdness) && !defaultOK) { | ||
return "error"; | ||
} else if (!hasFTP || (isActuallyEn && hasNoWeirdness)) { | ||
return "en"; | ||
} else { | ||
return "intl"; | ||
} | ||
listingMode() { | ||
const isActuallyEn = this.isEnglish(); | ||
const hasNoWeirdness = | ||
(this.numberingSystem === null || this.numberingSystem === "latn") && | ||
(this.outputCalendar === null || this.outputCalendar === "gregory"); | ||
return isActuallyEn && hasNoWeirdness ? "en" : "intl"; | ||
} | ||
@@ -389,15 +397,15 @@ | ||
redefaultToEN(alts = {}) { | ||
return this.clone(Object.assign({}, alts, { defaultToEN: true })); | ||
return this.clone({ ...alts, defaultToEN: true }); | ||
} | ||
redefaultToSystem(alts = {}) { | ||
return this.clone(Object.assign({}, alts, { defaultToEN: false })); | ||
return this.clone({ ...alts, defaultToEN: false }); | ||
} | ||
months(length, format = false, defaultOK = true) { | ||
return listStuff(this, length, defaultOK, English.months, () => { | ||
months(length, format = false) { | ||
return listStuff(this, length, English.months, () => { | ||
const intl = format ? { month: length, day: "numeric" } : { month: length }, | ||
formatStr = format ? "format" : "standalone"; | ||
if (!this.monthsCache[formatStr][length]) { | ||
this.monthsCache[formatStr][length] = mapMonths(dt => this.extract(dt, intl, "month")); | ||
this.monthsCache[formatStr][length] = mapMonths((dt) => this.extract(dt, intl, "month")); | ||
} | ||
@@ -408,4 +416,4 @@ return this.monthsCache[formatStr][length]; | ||
weekdays(length, format = false, defaultOK = true) { | ||
return listStuff(this, length, defaultOK, English.weekdays, () => { | ||
weekdays(length, format = false) { | ||
return listStuff(this, length, English.weekdays, () => { | ||
const intl = format | ||
@@ -416,3 +424,3 @@ ? { weekday: length, year: "numeric", month: "long", day: "numeric" } | ||
if (!this.weekdaysCache[formatStr][length]) { | ||
this.weekdaysCache[formatStr][length] = mapWeekdays(dt => | ||
this.weekdaysCache[formatStr][length] = mapWeekdays((dt) => | ||
this.extract(dt, intl, "weekday") | ||
@@ -425,7 +433,6 @@ ); | ||
meridiems(defaultOK = true) { | ||
meridiems() { | ||
return listStuff( | ||
this, | ||
undefined, | ||
defaultOK, | ||
() => English.meridiems, | ||
@@ -436,5 +443,5 @@ () => { | ||
if (!this.meridiemCache) { | ||
const intl = { hour: "numeric", hour12: true }; | ||
const intl = { hour: "numeric", hourCycle: "h12" }; | ||
this.meridiemCache = [DateTime.utc(2016, 11, 13, 9), DateTime.utc(2016, 11, 13, 19)].map( | ||
dt => this.extract(dt, intl, "dayperiod") | ||
(dt) => this.extract(dt, intl, "dayperiod") | ||
); | ||
@@ -448,4 +455,4 @@ } | ||
eras(length, defaultOK = true) { | ||
return listStuff(this, length, defaultOK, English.eras, () => { | ||
eras(length) { | ||
return listStuff(this, length, English.eras, () => { | ||
const intl = { era: length }; | ||
@@ -456,3 +463,3 @@ | ||
if (!this.eraCache[length]) { | ||
this.eraCache[length] = [DateTime.utc(-40, 1, 1), DateTime.utc(2017, 1, 1)].map(dt => | ||
this.eraCache[length] = [DateTime.utc(-40, 1, 1), DateTime.utc(2017, 1, 1)].map((dt) => | ||
this.extract(dt, intl, "era") | ||
@@ -469,3 +476,3 @@ ); | ||
results = df.formatToParts(), | ||
matching = results.find(m => m.type.toLowerCase() === field); | ||
matching = results.find((m) => m.type.toLowerCase() === field); | ||
return matching ? matching.value : null; | ||
@@ -488,2 +495,6 @@ } | ||
listFormatter(opts = {}) { | ||
return getCachedLF(this.intl, opts); | ||
} | ||
isEnglish() { | ||
@@ -493,3 +504,3 @@ return ( | ||
this.locale.toLowerCase() === "en-us" || | ||
(hasIntl() && new Intl.DateTimeFormat(this.intl).resolvedOptions().locale.startsWith("en-us")) | ||
new Intl.DateTimeFormat(this.intl).resolvedOptions().locale.startsWith("en-us") | ||
); | ||
@@ -496,0 +507,0 @@ } |
@@ -6,4 +6,4 @@ import { | ||
parseMillis, | ||
ianaRegex, | ||
isUndefined | ||
isUndefined, | ||
parseFloating, | ||
} from "./util.js"; | ||
@@ -24,2 +24,4 @@ import * as English from "./english.js"; | ||
const ianaRegex = /[A-Za-z_+-]{1,256}(?::?\/[A-Za-z0-9_+-]{1,256}(?:\/[A-Za-z0-9_+-]{1,256})?)?/; | ||
function combineRegexes(...regexes) { | ||
@@ -31,3 +33,3 @@ const full = regexes.reduce((f, r) => f + r.source, ""); | ||
function combineExtractors(...extractors) { | ||
return m => | ||
return (m) => | ||
extractors | ||
@@ -37,3 +39,3 @@ .reduce( | ||
const [val, zone, next] = ex(m, cursor); | ||
return [Object.assign(mergedVals, val), mergedZone || zone, next]; | ||
return [{ ...mergedVals, ...val }, zone || mergedZone, next]; | ||
}, | ||
@@ -72,16 +74,17 @@ [{}, null, 1] | ||
// ISO and SQL parsing | ||
const offsetRegex = /(?:(Z)|([+-]\d\d)(?::?(\d\d))?)/, | ||
isoTimeBaseRegex = /(\d\d)(?::?(\d\d)(?::?(\d\d)(?:[.,](\d{1,30}))?)?)?/, | ||
isoTimeRegex = RegExp(`${isoTimeBaseRegex.source}${offsetRegex.source}?`), | ||
isoTimeExtensionRegex = RegExp(`(?:T${isoTimeRegex.source})?`), | ||
isoYmdRegex = /([+-]\d{6}|\d{4})(?:-?(\d\d)(?:-?(\d\d))?)?/, | ||
isoWeekRegex = /(\d{4})-?W(\d\d)(?:-?(\d))?/, | ||
isoOrdinalRegex = /(\d{4})-?(\d{3})/, | ||
extractISOWeekData = simpleParse("weekYear", "weekNumber", "weekDay"), | ||
extractISOOrdinalData = simpleParse("year", "ordinal"), | ||
sqlYmdRegex = /(\d{4})-(\d\d)-(\d\d)/, // dumbed-down version of the ISO one | ||
sqlTimeRegex = RegExp( | ||
`${isoTimeBaseRegex.source} ?(?:${offsetRegex.source}|(${ianaRegex.source}))?` | ||
), | ||
sqlTimeExtensionRegex = RegExp(`(?: ${sqlTimeRegex.source})?`); | ||
const offsetRegex = /(?:(Z)|([+-]\d\d)(?::?(\d\d))?)/; | ||
const isoExtendedZone = `(?:${offsetRegex.source}?(?:\\[(${ianaRegex.source})\\])?)?`; | ||
const isoTimeBaseRegex = /(\d\d)(?::?(\d\d)(?::?(\d\d)(?:[.,](\d{1,30}))?)?)?/; | ||
const isoTimeRegex = RegExp(`${isoTimeBaseRegex.source}${isoExtendedZone}`); | ||
const isoTimeExtensionRegex = RegExp(`(?:T${isoTimeRegex.source})?`); | ||
const isoYmdRegex = /([+-]\d{6}|\d{4})(?:-?(\d\d)(?:-?(\d\d))?)?/; | ||
const isoWeekRegex = /(\d{4})-?W(\d\d)(?:-?(\d))?/; | ||
const isoOrdinalRegex = /(\d{4})-?(\d{3})/; | ||
const extractISOWeekData = simpleParse("weekYear", "weekNumber", "weekDay"); | ||
const extractISOOrdinalData = simpleParse("year", "ordinal"); | ||
const sqlYmdRegex = /(\d{4})-(\d\d)-(\d\d)/; // dumbed-down version of the ISO one | ||
const sqlTimeRegex = RegExp( | ||
`${isoTimeBaseRegex.source} ?(?:${offsetRegex.source}|(${ianaRegex.source}))?` | ||
); | ||
const sqlTimeExtensionRegex = RegExp(`(?: ${sqlTimeRegex.source})?`); | ||
@@ -97,3 +100,3 @@ function int(match, pos, fallback) { | ||
month: int(match, cursor + 1, 1), | ||
day: int(match, cursor + 2, 1) | ||
day: int(match, cursor + 2, 1), | ||
}; | ||
@@ -109,3 +112,3 @@ | ||
seconds: int(match, cursor + 2, 0), | ||
milliseconds: parseMillis(match[cursor + 3]) | ||
milliseconds: parseMillis(match[cursor + 3]), | ||
}; | ||
@@ -134,16 +137,8 @@ | ||
const isoDuration = /^-?P(?:(?:(-?\d{1,9})Y)?(?:(-?\d{1,9})M)?(?:(-?\d{1,9})W)?(?:(-?\d{1,9})D)?(?:T(?:(-?\d{1,9})H)?(?:(-?\d{1,9})M)?(?:(-?\d{1,20})(?:[.,](-?\d{1,9}))?S)?)?)$/; | ||
const isoDuration = | ||
/^-?P(?:(?:(-?\d{1,20}(?:\.\d{1,20})?)Y)?(?:(-?\d{1,20}(?:\.\d{1,20})?)M)?(?:(-?\d{1,20}(?:\.\d{1,20})?)W)?(?:(-?\d{1,20}(?:\.\d{1,20})?)D)?(?:T(?:(-?\d{1,20}(?:\.\d{1,20})?)H)?(?:(-?\d{1,20}(?:\.\d{1,20})?)M)?(?:(-?\d{1,20})(?:[.,](-?\d{1,20}))?S)?)?)$/; | ||
function extractISODuration(match) { | ||
const [ | ||
s, | ||
yearStr, | ||
monthStr, | ||
weekStr, | ||
dayStr, | ||
hourStr, | ||
minuteStr, | ||
secondStr, | ||
millisecondsStr | ||
] = match; | ||
const [s, yearStr, monthStr, weekStr, dayStr, hourStr, minuteStr, secondStr, millisecondsStr] = | ||
match; | ||
@@ -158,11 +153,11 @@ const hasNegativePrefix = s[0] === "-"; | ||
{ | ||
years: maybeNegate(parseInteger(yearStr)), | ||
months: maybeNegate(parseInteger(monthStr)), | ||
weeks: maybeNegate(parseInteger(weekStr)), | ||
days: maybeNegate(parseInteger(dayStr)), | ||
hours: maybeNegate(parseInteger(hourStr)), | ||
minutes: maybeNegate(parseInteger(minuteStr)), | ||
seconds: maybeNegate(parseInteger(secondStr), secondStr === "-0"), | ||
milliseconds: maybeNegate(parseMillis(millisecondsStr), negativeSeconds) | ||
} | ||
years: maybeNegate(parseFloating(yearStr)), | ||
months: maybeNegate(parseFloating(monthStr)), | ||
weeks: maybeNegate(parseFloating(weekStr)), | ||
days: maybeNegate(parseFloating(dayStr)), | ||
hours: maybeNegate(parseFloating(hourStr)), | ||
minutes: maybeNegate(parseFloating(minuteStr)), | ||
seconds: maybeNegate(parseFloating(secondStr), secondStr === "-0"), | ||
milliseconds: maybeNegate(parseMillis(millisecondsStr), negativeSeconds), | ||
}, | ||
]; | ||
@@ -183,3 +178,3 @@ } | ||
PDT: -7 * 60, | ||
PST: -8 * 60 | ||
PST: -8 * 60, | ||
}; | ||
@@ -193,3 +188,3 @@ | ||
hour: parseInteger(hourStr), | ||
minute: parseInteger(minuteStr) | ||
minute: parseInteger(minuteStr), | ||
}; | ||
@@ -209,3 +204,4 @@ | ||
// RFC 2822/5322 | ||
const rfc2822 = /^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|(?:([+-]\d\d)(\d\d)))$/; | ||
const rfc2822 = | ||
/^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|(?:([+-]\d\d)(\d\d)))$/; | ||
@@ -225,3 +221,3 @@ function extractRFC2822(match) { | ||
offHourStr, | ||
offMinuteStr | ||
offMinuteStr, | ||
] = match, | ||
@@ -252,5 +248,8 @@ result = fromStrings(weekdayStr, yearStr, monthStr, dayStr, hourStr, minuteStr, secondStr); | ||
const rfc1123 = /^(Mon|Tue|Wed|Thu|Fri|Sat|Sun), (\d\d) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) (\d{4}) (\d\d):(\d\d):(\d\d) GMT$/, | ||
rfc850 = /^(Monday|Tuesday|Wedsday|Thursday|Friday|Saturday|Sunday), (\d\d)-(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-(\d\d) (\d\d):(\d\d):(\d\d) GMT$/, | ||
ascii = /^(Mon|Tue|Wed|Thu|Fri|Sat|Sun) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) ( \d|\d\d) (\d\d):(\d\d):(\d\d) (\d{4})$/; | ||
const rfc1123 = | ||
/^(Mon|Tue|Wed|Thu|Fri|Sat|Sun), (\d\d) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) (\d{4}) (\d\d):(\d\d):(\d\d) GMT$/, | ||
rfc850 = | ||
/^(Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), (\d\d)-(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-(\d\d) (\d\d):(\d\d):(\d\d) GMT$/, | ||
ascii = | ||
/^(Mon|Tue|Wed|Thu|Fri|Sat|Sun) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) ( \d|\d\d) (\d\d):(\d\d):(\d\d) (\d{4})$/; | ||
@@ -277,3 +276,4 @@ function extractRFC1123Or850(match) { | ||
extractISOTime, | ||
extractISOOffset | ||
extractISOOffset, | ||
extractIANAZone | ||
); | ||
@@ -283,3 +283,4 @@ const extractISOWeekTimeAndOffset = combineExtractors( | ||
extractISOTime, | ||
extractISOOffset | ||
extractISOOffset, | ||
extractIANAZone | ||
); | ||
@@ -289,7 +290,12 @@ const extractISOOrdinalDateAndTime = combineExtractors( | ||
extractISOTime, | ||
extractISOOffset | ||
extractISOOffset, | ||
extractIANAZone | ||
); | ||
const extractISOTimeAndOffset = combineExtractors(extractISOTime, extractISOOffset); | ||
const extractISOTimeAndOffset = combineExtractors( | ||
extractISOTime, | ||
extractISOOffset, | ||
extractIANAZone | ||
); | ||
/** | ||
/* | ||
* @private | ||
@@ -334,8 +340,2 @@ */ | ||
const extractISOYmdTimeOffsetAndIANAZone = combineExtractors( | ||
extractISOYmd, | ||
extractISOTime, | ||
extractISOOffset, | ||
extractIANAZone | ||
); | ||
const extractISOTimeOffsetAndIANAZone = combineExtractors( | ||
@@ -350,5 +350,5 @@ extractISOTime, | ||
s, | ||
[sqlYmdWithTimeExtensionRegex, extractISOYmdTimeOffsetAndIANAZone], | ||
[sqlYmdWithTimeExtensionRegex, extractISOYmdTimeAndOffset], | ||
[sqlTimeCombinedRegex, extractISOTimeOffsetAndIANAZone] | ||
); | ||
} |
@@ -11,3 +11,3 @@ import { parseMillis, isUndefined, untruncateYear, signedOffset, hasOwnProperty } from "./util.js"; | ||
function intUnit(regex, post = i => i) { | ||
function intUnit(regex, post = (i) => i) { | ||
return { regex, deser: ([s]) => post(parseDigits(s)) }; | ||
@@ -17,3 +17,3 @@ } | ||
const NBSP = String.fromCharCode(160); | ||
const spaceOrNBSP = `( |${NBSP})`; | ||
const spaceOrNBSP = `[ ${NBSP}]`; | ||
const spaceOrNBSPRegExp = new RegExp(spaceOrNBSP, "g"); | ||
@@ -41,3 +41,3 @@ | ||
deser: ([s]) => | ||
strings.findIndex(i => stripInsensitivities(s) === stripInsensitivities(i)) + startIndex | ||
strings.findIndex((i) => stripInsensitivities(s) === stripInsensitivities(i)) + startIndex, | ||
}; | ||
@@ -56,6 +56,9 @@ } | ||
function escapeToken(value) { | ||
// eslint-disable-next-line no-useless-escape | ||
return value.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&"); | ||
} | ||
/** | ||
* @param token | ||
* @param {Locale} loc | ||
*/ | ||
function unitForToken(token, loc) { | ||
@@ -73,4 +76,4 @@ const one = digitRegex(loc), | ||
fourToSix = digitRegex(loc, "{4,6}"), | ||
literal = t => ({ regex: RegExp(escapeToken(t.val)), deser: ([s]) => s, literal: true }), | ||
unitate = t => { | ||
literal = (t) => ({ regex: RegExp(escapeToken(t.val)), deser: ([s]) => s, literal: true }), | ||
unitate = (t) => { | ||
if (token.literal) { | ||
@@ -82,5 +85,5 @@ return literal(t); | ||
case "G": | ||
return oneOf(loc.eras("short", false), 0); | ||
return oneOf(loc.eras("short"), 0); | ||
case "GG": | ||
return oneOf(loc.eras("long", false), 0); | ||
return oneOf(loc.eras("long"), 0); | ||
// years | ||
@@ -103,5 +106,5 @@ case "y": | ||
case "MMM": | ||
return oneOf(loc.months("short", true, false), 1); | ||
return oneOf(loc.months("short", true), 1); | ||
case "MMMM": | ||
return oneOf(loc.months("long", true, false), 1); | ||
return oneOf(loc.months("long", true), 1); | ||
case "L": | ||
@@ -112,5 +115,5 @@ return intUnit(oneOrTwo); | ||
case "LLL": | ||
return oneOf(loc.months("short", false, false), 1); | ||
return oneOf(loc.months("short", false), 1); | ||
case "LLLL": | ||
return oneOf(loc.months("long", false, false), 1); | ||
return oneOf(loc.months("long", false), 1); | ||
// dates | ||
@@ -153,2 +156,6 @@ case "d": | ||
return simple(oneToNine); | ||
case "uu": | ||
return simple(oneOrTwo); | ||
case "uuu": | ||
return intUnit(one); | ||
// meridiem | ||
@@ -172,9 +179,9 @@ case "a": | ||
case "EEE": | ||
return oneOf(loc.weekdays("short", false, false), 1); | ||
return oneOf(loc.weekdays("short", false), 1); | ||
case "EEEE": | ||
return oneOf(loc.weekdays("long", false, false), 1); | ||
return oneOf(loc.weekdays("long", false), 1); | ||
case "ccc": | ||
return oneOf(loc.weekdays("short", true, false), 1); | ||
return oneOf(loc.weekdays("short", true), 1); | ||
case "cccc": | ||
return oneOf(loc.weekdays("long", true, false), 1); | ||
return oneOf(loc.weekdays("long", true), 1); | ||
// offset/zone | ||
@@ -190,2 +197,6 @@ case "Z": | ||
return simple(/[a-z_+-/]{1,256}?/i); | ||
// this special-case "token" represents a place where a macro-token expanded into a white-space literal | ||
// in this case we accept any non-newline white-space | ||
case " ": | ||
return simple(/[^\S\n\r]/); | ||
default: | ||
@@ -197,3 +208,3 @@ return literal(t); | ||
const unit = unitate(token) || { | ||
invalidReason: MISSING_FTP | ||
invalidReason: MISSING_FTP, | ||
}; | ||
@@ -209,3 +220,3 @@ | ||
"2-digit": "yy", | ||
numeric: "yyyyy" | ||
numeric: "yyyyy", | ||
}, | ||
@@ -216,35 +227,44 @@ month: { | ||
short: "MMM", | ||
long: "MMMM" | ||
long: "MMMM", | ||
}, | ||
day: { | ||
numeric: "d", | ||
"2-digit": "dd" | ||
"2-digit": "dd", | ||
}, | ||
weekday: { | ||
short: "EEE", | ||
long: "EEEE" | ||
long: "EEEE", | ||
}, | ||
dayperiod: "a", | ||
dayPeriod: "a", | ||
hour: { | ||
hour12: { | ||
numeric: "h", | ||
"2-digit": "hh" | ||
"2-digit": "hh", | ||
}, | ||
hour24: { | ||
numeric: "H", | ||
"2-digit": "HH", | ||
}, | ||
minute: { | ||
numeric: "m", | ||
"2-digit": "mm" | ||
"2-digit": "mm", | ||
}, | ||
second: { | ||
numeric: "s", | ||
"2-digit": "ss" | ||
} | ||
"2-digit": "ss", | ||
}, | ||
timeZoneName: { | ||
long: "ZZZZZ", | ||
short: "ZZZ", | ||
}, | ||
}; | ||
function tokenForPart(part, locale, formatOpts) { | ||
function tokenForPart(part, formatOpts, resolvedOpts) { | ||
const { type, value } = part; | ||
if (type === "literal") { | ||
const isSpace = /^\s+$/.test(value); | ||
return { | ||
literal: true, | ||
val: value | ||
literal: !isSpace, | ||
val: isSpace ? " " : value, | ||
}; | ||
@@ -255,3 +275,22 @@ } | ||
let val = partTypeStyleToTokenVal[type]; | ||
// The user might have explicitly specified hour12 or hourCycle | ||
// if so, respect their decision | ||
// if not, refer back to the resolvedOpts, which are based on the locale | ||
let actualType = type; | ||
if (type === "hour") { | ||
if (formatOpts.hour12 != null) { | ||
actualType = formatOpts.hour12 ? "hour12" : "hour24"; | ||
} else if (formatOpts.hourCycle != null) { | ||
if (formatOpts.hourCycle === "h11" || formatOpts.hourCycle === "h12") { | ||
actualType = "hour12"; | ||
} else { | ||
actualType = "hour24"; | ||
} | ||
} else { | ||
// tokens only differentiate between 24 hours or not, | ||
// so we do not need to check hourCycle here, which is less supported anyways | ||
actualType = resolvedOpts.hour12 ? "hour12" : "hour24"; | ||
} | ||
} | ||
let val = partTypeStyleToTokenVal[actualType]; | ||
if (typeof val === "object") { | ||
@@ -264,3 +303,3 @@ val = val[style]; | ||
literal: false, | ||
val | ||
val, | ||
}; | ||
@@ -273,3 +312,3 @@ } | ||
function buildRegex(units) { | ||
const re = units.map(u => u.regex).reduce((f, r) => `${f}(${r.source})`, ""); | ||
const re = units.map((u) => u.regex).reduce((f, r) => `${f}(${r.source})`, ""); | ||
return [`^${re}$`, units]; | ||
@@ -301,3 +340,3 @@ } | ||
function dateTimeFromMatches(matches) { | ||
const toField = token => { | ||
const toField = (token) => { | ||
switch (token) { | ||
@@ -336,11 +375,15 @@ case "S": | ||
let zone; | ||
if (!isUndefined(matches.Z)) { | ||
zone = new FixedOffsetZone(matches.Z); | ||
} else if (!isUndefined(matches.z)) { | ||
let zone = null; | ||
let specificOffset; | ||
if (!isUndefined(matches.z)) { | ||
zone = IANAZone.create(matches.z); | ||
} else { | ||
zone = null; | ||
} | ||
if (!isUndefined(matches.Z)) { | ||
if (!zone) { | ||
zone = new FixedOffsetZone(matches.Z); | ||
} | ||
specificOffset = matches.Z; | ||
} | ||
if (!isUndefined(matches.q)) { | ||
@@ -375,3 +418,3 @@ matches.M = (matches.q - 1) * 3 + 1; | ||
return [vals, zone]; | ||
return [vals, zone, specificOffset]; | ||
} | ||
@@ -395,21 +438,13 @@ | ||
const formatOpts = Formatter.macroTokenToFormatOpts(token.val); | ||
const tokens = formatOptsToTokens(formatOpts, locale); | ||
if (!formatOpts) { | ||
if (tokens == null || tokens.includes(undefined)) { | ||
return token; | ||
} | ||
const formatter = Formatter.create(locale, formatOpts); | ||
const parts = formatter.formatDateTimeParts(getDummyDateTime()); | ||
const tokens = parts.map(p => tokenForPart(p, locale, formatOpts)); | ||
if (tokens.includes(undefined)) { | ||
return token; | ||
} | ||
return tokens; | ||
} | ||
function expandMacroTokens(tokens, locale) { | ||
return Array.prototype.concat(...tokens.map(t => maybeExpandMacroToken(t, locale))); | ||
export function expandMacroTokens(tokens, locale) { | ||
return Array.prototype.concat(...tokens.map((t) => maybeExpandMacroToken(t, locale))); | ||
} | ||
@@ -423,4 +458,4 @@ | ||
const tokens = expandMacroTokens(Formatter.parseFormat(format), locale), | ||
units = tokens.map(t => unitForToken(t, locale)), | ||
disqualifyingUnit = units.find(t => t.invalidReason); | ||
units = tokens.map((t) => unitForToken(t, locale)), | ||
disqualifyingUnit = units.find((t) => t.invalidReason); | ||
@@ -433,3 +468,5 @@ if (disqualifyingUnit) { | ||
[rawMatches, matches] = match(input, regex, handlers), | ||
[result, zone] = matches ? dateTimeFromMatches(matches) : [null, null]; | ||
[result, zone, specificOffset] = matches | ||
? dateTimeFromMatches(matches) | ||
: [null, null, undefined]; | ||
if (hasOwnProperty(matches, "a") && hasOwnProperty(matches, "H")) { | ||
@@ -440,3 +477,3 @@ throw new ConflictingSpecificationError( | ||
} | ||
return { input, tokens, regex, rawMatches, matches, result, zone }; | ||
return { input, tokens, regex, rawMatches, matches, result, zone, specificOffset }; | ||
} | ||
@@ -446,4 +483,16 @@ } | ||
export function parseFromTokens(locale, input, format) { | ||
const { result, zone, invalidReason } = explainFromTokens(locale, input, format); | ||
return [result, zone, invalidReason]; | ||
const { result, zone, specificOffset, invalidReason } = explainFromTokens(locale, input, format); | ||
return [result, zone, specificOffset, invalidReason]; | ||
} | ||
export function formatOptsToTokens(formatOpts, locale) { | ||
if (!formatOpts) { | ||
return null; | ||
} | ||
const formatter = Formatter.create(locale, formatOpts); | ||
const df = formatter.dtFormatter(getDummyDateTime()); | ||
const parts = df.formatToParts(); | ||
const resolvedOpts = df.resolvedOptions(); | ||
return parts.map((p) => tokenForPart(p, formatOpts, resolvedOpts)); | ||
} |
@@ -8,2 +8,3 @@ /* | ||
import { InvalidArgumentError } from "../errors.js"; | ||
import Settings from "../settings.js"; | ||
@@ -38,14 +39,2 @@ /** | ||
export function hasIntl() { | ||
try { | ||
return typeof Intl !== "undefined" && Intl.DateTimeFormat; | ||
} catch (e) { | ||
return false; | ||
} | ||
} | ||
export function hasFormatToParts() { | ||
return !isUndefined(Intl.DateTimeFormat.prototype.formatToParts); | ||
} | ||
export function hasRelative() { | ||
@@ -104,13 +93,10 @@ try { | ||
export function padStart(input, n = 2) { | ||
const minus = input < 0 ? "-" : ""; | ||
const target = minus ? input * -1 : input; | ||
let result; | ||
if (target.toString().length < n) { | ||
result = ("0".repeat(n) + target).slice(-n); | ||
const isNeg = input < 0; | ||
let padded; | ||
if (isNeg) { | ||
padded = "-" + ("" + -input).padStart(n, "0"); | ||
} else { | ||
result = target.toString(); | ||
padded = ("" + input).padStart(n, "0"); | ||
} | ||
return `${minus}${result}`; | ||
return padded; | ||
} | ||
@@ -126,2 +112,10 @@ | ||
export function parseFloating(string) { | ||
if (isUndefined(string) || string === null || string === "") { | ||
return undefined; | ||
} else { | ||
return parseFloat(string); | ||
} | ||
} | ||
export function parseMillis(fraction) { | ||
@@ -164,3 +158,3 @@ // Return undefined (instead of 0) in these cases, where fraction is not set | ||
// covert a calendar object to a local timestamp (epoch, but with the offset baked in) | ||
// convert a calendar object to a local timestamp (epoch, but with the offset baked in) | ||
export function objToLocalTS(obj) { | ||
@@ -180,3 +174,6 @@ let d = Date.UTC( | ||
d = new Date(d); | ||
d.setUTCFullYear(d.getUTCFullYear() - 1900); | ||
// set the month and day again, this is necessary because year 2000 is a leap year, but year 100 is not | ||
// so if obj.year is in 99, but obj.day makes it roll over into year 100, | ||
// the calculations done by Date.UTC are using year 2000 - which is incorrect | ||
d.setUTCFullYear(obj.year, obj.month - 1, obj.day); | ||
} | ||
@@ -201,3 +198,3 @@ return +d; | ||
return year; | ||
} else return year > 60 ? 1900 + year : 2000 + year; | ||
} else return year > Settings.twoDigitCutoffYear ? 1900 + year : 2000 + year; | ||
} | ||
@@ -210,3 +207,3 @@ | ||
intlOpts = { | ||
hour12: false, | ||
hourCycle: "h23", | ||
year: "numeric", | ||
@@ -216,3 +213,3 @@ month: "2-digit", | ||
hour: "2-digit", | ||
minute: "2-digit" | ||
minute: "2-digit", | ||
}; | ||
@@ -224,20 +221,8 @@ | ||
const modified = Object.assign({ timeZoneName: offsetFormat }, intlOpts), | ||
intl = hasIntl(); | ||
const modified = { timeZoneName: offsetFormat, ...intlOpts }; | ||
if (intl && hasFormatToParts()) { | ||
const parsed = new Intl.DateTimeFormat(locale, modified) | ||
.formatToParts(date) | ||
.find(m => m.type.toLowerCase() === "timezonename"); | ||
return parsed ? parsed.value : null; | ||
} else if (intl) { | ||
// this probably doesn't work for all locales | ||
const without = new Intl.DateTimeFormat(locale, intlOpts).format(date), | ||
included = new Intl.DateTimeFormat(locale, modified).format(date), | ||
diffed = included.substring(without.length), | ||
trimmed = diffed.replace(/^[, \u200e]+/, ""); | ||
return trimmed; | ||
} else { | ||
return null; | ||
} | ||
const parsed = new Intl.DateTimeFormat(locale, modified) | ||
.formatToParts(date) | ||
.find((m) => m.type.toLowerCase() === "timezonename"); | ||
return parsed ? parsed.value : null; | ||
} | ||
@@ -268,7 +253,6 @@ | ||
export function normalizeObject(obj, normalizer, nonUnitKeys) { | ||
export function normalizeObject(obj, normalizer) { | ||
const normalized = {}; | ||
for (const u in obj) { | ||
if (hasOwnProperty(obj, u)) { | ||
if (nonUnitKeys.indexOf(u) >= 0) continue; | ||
const v = obj[u]; | ||
@@ -302,3 +286,1 @@ if (v === undefined || v === null) continue; | ||
} | ||
export const ianaRegex = /[A-Za-z_+-]{1,256}(:?\/[A-Za-z_+-]{1,256}(\/[A-Za-z_+-]{1,256})?)?/; |
@@ -11,2 +11,3 @@ /** | ||
import { isUndefined, isString, isNumber } from "./util.js"; | ||
import SystemZone from "../zones/systemZone.js"; | ||
@@ -21,12 +22,9 @@ export function normalizeZone(input, defaultZone) { | ||
const lowered = input.toLowerCase(); | ||
if (lowered === "local") return defaultZone; | ||
if (lowered === "default") return defaultZone; | ||
else if (lowered === "local" || lowered === "system") return SystemZone.instance; | ||
else if (lowered === "utc" || lowered === "gmt") return FixedOffsetZone.utcInstance; | ||
else if ((offset = IANAZone.parseGMTOffset(input)) != null) { | ||
// handle Etc/GMT-4, which V8 chokes on | ||
return FixedOffsetZone.instance(offset); | ||
} else if (IANAZone.isValidSpecifier(lowered)) return IANAZone.create(input); | ||
else return FixedOffsetZone.parseSpecifier(lowered) || new InvalidZone(input); | ||
else return FixedOffsetZone.parseSpecifier(lowered) || IANAZone.create(input); | ||
} else if (isNumber(input)) { | ||
return FixedOffsetZone.instance(input); | ||
} else if (typeof input === "object" && input.offset && typeof input.offset === "number") { | ||
} else if (typeof input === "object" && "offset" in input && typeof input.offset === "function") { | ||
// This is dumb, but the instanceof check above doesn't seem to really work | ||
@@ -33,0 +31,0 @@ // so we're duck checking it |
@@ -7,3 +7,3 @@ import DateTime from "./datetime.js"; | ||
import { hasFormatToParts, hasIntl, hasRelative } from "./impl/util.js"; | ||
import { hasRelative } from "./impl/util.js"; | ||
@@ -20,7 +20,5 @@ /** | ||
static hasDST(zone = Settings.defaultZone) { | ||
const proto = DateTime.now() | ||
.setZone(zone) | ||
.set({ month: 12 }); | ||
const proto = DateTime.now().setZone(zone).set({ month: 12 }); | ||
return !zone.universal && proto.offset !== proto.set({ month: 6 }).offset; | ||
return !zone.isUniversal && proto.offset !== proto.set({ month: 6 }).offset; | ||
} | ||
@@ -34,3 +32,3 @@ | ||
static isValidIANAZone(zone) { | ||
return IANAZone.isValidSpecifier(zone) && IANAZone.isValidZone(zone); | ||
return IANAZone.isValidZone(zone); | ||
} | ||
@@ -45,3 +43,3 @@ | ||
* * If `input` is a string that doesn't refer to a known time zone, a Zone | ||
* instance with {@link Zone.isValid} == false is returned. | ||
* instance with {@link Zone#isValid} == false is returned. | ||
* * If `input is a number, a Zone instance with the specified fixed offset | ||
@@ -72,3 +70,3 @@ * in minutes is returned. | ||
* @example Info.months('long', { outputCalendar: 'islamic' })[0] //=> 'Rabiʻ I' | ||
* @return {[string]} | ||
* @return {Array} | ||
*/ | ||
@@ -86,3 +84,3 @@ static months( | ||
* changes the string. | ||
* See {@link months} | ||
* See {@link Info#months} | ||
* @param {string} [length='long'] - the length of the month representation, such as "numeric", "2-digit", "narrow", "short", "long" | ||
@@ -94,3 +92,3 @@ * @param {Object} opts - options | ||
* @param {string} [opts.outputCalendar='gregory'] - the calendar | ||
* @return {[string]} | ||
* @return {Array} | ||
*/ | ||
@@ -116,3 +114,3 @@ static monthsFormat( | ||
* @example Info.weekdays('short', { locale: 'ar' })[0] //=> 'الاثنين' | ||
* @return {[string]} | ||
* @return {Array} | ||
*/ | ||
@@ -127,4 +125,4 @@ static weekdays(length = "long", { locale = null, numberingSystem = null, locObj = null } = {}) { | ||
* changes the string. | ||
* See {@link weekdays} | ||
* @param {string} [length='long'] - the length of the weekday representation, such as "narrow", "short", "long". | ||
* See {@link Info#weekdays} | ||
* @param {string} [length='long'] - the length of the month representation, such as "narrow", "short", "long". | ||
* @param {Object} opts - options | ||
@@ -134,3 +132,3 @@ * @param {string} [opts.locale=null] - the locale code | ||
* @param {string} [opts.locObj=null] - an existing locale object to use | ||
* @return {[string]} | ||
* @return {Array} | ||
*/ | ||
@@ -150,3 +148,3 @@ static weekdaysFormat( | ||
* @example Info.meridiems({ locale: 'my' }) //=> [ 'နံနက်', 'ညနေ' ] | ||
* @return {[string]} | ||
* @return {Array} | ||
*/ | ||
@@ -165,3 +163,3 @@ static meridiems({ locale = null } = {}) { | ||
* @example Info.eras('long', { locale: 'fr' }) //=> [ 'avant Jésus-Christ', 'après Jésus-Christ' ] | ||
* @return {[string]} | ||
* @return {Array} | ||
*/ | ||
@@ -174,33 +172,11 @@ static eras(length = "short", { locale = null } = {}) { | ||
* Return the set of available features in this environment. | ||
* Some features of Luxon are not available in all environments. For example, on older browsers, timezone support is not available. Use this function to figure out if that's the case. | ||
* Some features of Luxon are not available in all environments. For example, on older browsers, relative time formatting support is not available. Use this function to figure out if that's the case. | ||
* Keys: | ||
* * `zones`: whether this environment supports IANA timezones | ||
* * `intlTokens`: whether this environment supports internationalized token-based formatting/parsing | ||
* * `intl`: whether this environment supports general internationalization | ||
* * `relative`: whether this environment supports relative time formatting | ||
* @example Info.features() //=> { intl: true, intlTokens: false, zones: true, relative: false } | ||
* @example Info.features() //=> { relative: false } | ||
* @return {Object} | ||
*/ | ||
static features() { | ||
let intl = false, | ||
intlTokens = false, | ||
zones = false, | ||
relative = false; | ||
if (hasIntl()) { | ||
intl = true; | ||
intlTokens = hasFormatToParts(); | ||
relative = hasRelative(); | ||
try { | ||
zones = | ||
new Intl.DateTimeFormat("en", { timeZone: "America/New_York" }).resolvedOptions() | ||
.timeZone === "America/New_York"; | ||
} catch (e) { | ||
zones = false; | ||
} | ||
} | ||
return { intl, intlTokens, zones, relative }; | ||
return { relative: hasRelative() }; | ||
} | ||
} |
import DateTime, { friendlyDateTime } from "./datetime.js"; | ||
import Duration, { friendlyDuration } from "./duration.js"; | ||
import Duration from "./duration.js"; | ||
import Settings from "./settings.js"; | ||
import { InvalidArgumentError, InvalidIntervalError } from "./errors.js"; | ||
import Invalid from "./impl/invalid.js"; | ||
import Formatter from "./impl/formatter.js"; | ||
import * as Formats from "./impl/formats.js"; | ||
@@ -30,8 +32,8 @@ const INVALID = "Invalid Interval"; | ||
* | ||
* * **Creation** To create an Interval, use {@link fromDateTimes}, {@link after}, {@link before}, or {@link fromISO}. | ||
* * **Accessors** Use {@link start} and {@link end} to get the start and end. | ||
* * **Interrogation** To analyze the Interval, use {@link count}, {@link length}, {@link hasSame}, {@link contains}, {@link isAfter}, or {@link isBefore}. | ||
* * **Transformation** To create other Intervals out of this one, use {@link set}, {@link splitAt}, {@link splitBy}, {@link divideEqually}, {@link merge}, {@link xor}, {@link union}, {@link intersection}, or {@link difference}. | ||
* * **Comparison** To compare this Interval to another one, use {@link equals}, {@link overlaps}, {@link abutsStart}, {@link abutsEnd}, {@link engulfs}. | ||
* * **Output** To convert the Interval into other representations, see {@link toString}, {@link toISO}, {@link toISODate}, {@link toISOTime}, {@link toFormat}, and {@link toDuration}. | ||
* * **Creation** To create an Interval, use {@link Interval.fromDateTimes}, {@link Interval.after}, {@link Interval.before}, or {@link Interval.fromISO}. | ||
* * **Accessors** Use {@link Interval#start} and {@link Interval#end} to get the start and end. | ||
* * **Interrogation** To analyze the Interval, use {@link Interval#count}, {@link Interval#length}, {@link Interval#hasSame}, {@link Interval#contains}, {@link Interval#isAfter}, or {@link Interval#isBefore}. | ||
* * **Transformation** To create other Intervals out of this one, use {@link Interval#set}, {@link Interval#splitAt}, {@link Interval#splitBy}, {@link Interval#divideEqually}, {@link Interval.merge}, {@link Interval.xor}, {@link Interval#union}, {@link Interval#intersection}, or {@link Interval#difference}. | ||
* * **Comparison** To compare this Interval to another one, use {@link Interval#equals}, {@link Interval#overlaps}, {@link Interval#abutsStart}, {@link Interval#abutsEnd}, {@link Interval#engulfs} | ||
* * **Output** To convert the Interval into other representations, see {@link Interval#toString}, {@link Interval#toLocaleString}, {@link Interval#toISO}, {@link Interval#toISODate}, {@link Interval#toISOTime}, {@link Interval#toFormat}, and {@link Interval#toDuration}. | ||
*/ | ||
@@ -96,3 +98,3 @@ export default class Interval { | ||
start: builtStart, | ||
end: builtEnd | ||
end: builtEnd, | ||
}); | ||
@@ -111,3 +113,3 @@ } else { | ||
static after(start, duration) { | ||
const dur = friendlyDuration(duration), | ||
const dur = Duration.fromDurationLike(duration), | ||
dt = friendlyDateTime(start); | ||
@@ -124,3 +126,3 @@ return Interval.fromDateTimes(dt, dt.plus(dur)); | ||
static before(end, duration) { | ||
const dur = friendlyDuration(duration), | ||
const dur = Duration.fromDurationLike(duration), | ||
dt = friendlyDateTime(end); | ||
@@ -134,3 +136,3 @@ return Interval.fromDateTimes(dt.minus(dur), dt); | ||
* @param {string} text - the ISO string to parse | ||
* @param {Object} [opts] - options to pass {@link DateTime.fromISO} and optionally {@link Duration.fromISO} | ||
* @param {Object} [opts] - options to pass {@link DateTime#fromISO} and optionally {@link Duration#fromISO} | ||
* @see https://en.wikipedia.org/wiki/ISO_8601#Time_intervals | ||
@@ -237,3 +239,3 @@ * @return {Interval} | ||
* Returns the count of minutes, hours, days, months, or years included in the Interval, even in part. | ||
* Unlike {@link length} this counts sections of the calendar, not periods of time, e.g. specifying 'day' | ||
* Unlike {@link Interval#length} this counts sections of the calendar, not periods of time, e.g. specifying 'day' | ||
* asks 'what dates are included in this interval?', not 'how many days long is this interval?' | ||
@@ -247,3 +249,3 @@ * @param {string} [unit='milliseconds'] - the unit of time to count. | ||
end = this.end.startOf(unit); | ||
return Math.floor(end.diff(start, unit).get(unit)) + 1; | ||
return Math.floor(end.diff(start, unit).get(unit)) + (end.valueOf() !== this.end.valueOf()); | ||
} | ||
@@ -312,4 +314,4 @@ | ||
* Split this Interval at each of the specified DateTimes | ||
* @param {...[DateTime]} dateTimes - the unit of time to count. | ||
* @return {[Interval]} | ||
* @param {...DateTime} dateTimes - the unit of time to count. | ||
* @return {Array} | ||
*/ | ||
@@ -320,3 +322,3 @@ splitAt(...dateTimes) { | ||
.map(friendlyDateTime) | ||
.filter(d => this.contains(d)) | ||
.filter((d) => this.contains(d)) | ||
.sort(), | ||
@@ -342,6 +344,6 @@ results = []; | ||
* @param {Duration|Object|number} duration - The length of each resulting interval. | ||
* @return {[Interval]} | ||
* @return {Array} | ||
*/ | ||
splitBy(duration) { | ||
const dur = friendlyDuration(duration); | ||
const dur = Duration.fromDurationLike(duration); | ||
@@ -358,3 +360,3 @@ if (!this.isValid || !dur.isValid || dur.as("milliseconds") === 0) { | ||
while (s < this.e) { | ||
const added = this.start.plus(dur.mapUnits(x => x * idx)); | ||
const added = this.start.plus(dur.mapUnits((x) => x * idx)); | ||
next = +added > +this.e ? this.e : added; | ||
@@ -372,3 +374,3 @@ results.push(Interval.fromDateTimes(s, next)); | ||
* @param {number} numberOfParts - The number of Intervals to divide the Interval into. | ||
* @return {[Interval]} | ||
* @return {Array} | ||
*/ | ||
@@ -467,18 +469,20 @@ divideEqually(numberOfParts) { | ||
* Combines overlapping and adjacent Intervals. | ||
* @param {[Interval]} intervals | ||
* @return {[Interval]} | ||
* @param {Array} intervals | ||
* @return {Array} | ||
*/ | ||
static merge(intervals) { | ||
const [found, final] = intervals.sort((a, b) => a.s - b.s).reduce( | ||
([sofar, current], item) => { | ||
if (!current) { | ||
return [sofar, item]; | ||
} else if (current.overlaps(item) || current.abutsStart(item)) { | ||
return [sofar, current.union(item)]; | ||
} else { | ||
return [sofar.concat([current]), item]; | ||
} | ||
}, | ||
[[], null] | ||
); | ||
const [found, final] = intervals | ||
.sort((a, b) => a.s - b.s) | ||
.reduce( | ||
([sofar, current], item) => { | ||
if (!current) { | ||
return [sofar, item]; | ||
} else if (current.overlaps(item) || current.abutsStart(item)) { | ||
return [sofar, current.union(item)]; | ||
} else { | ||
return [sofar.concat([current]), item]; | ||
} | ||
}, | ||
[[], null] | ||
); | ||
if (final) { | ||
@@ -492,4 +496,4 @@ found.push(final); | ||
* Return an array of Intervals representing the spans of time that only appear in one of the specified Intervals. | ||
* @param {[Interval]} intervals | ||
* @return {[Interval]} | ||
* @param {Array} intervals | ||
* @return {Array} | ||
*/ | ||
@@ -500,3 +504,6 @@ static xor(intervals) { | ||
const results = [], | ||
ends = intervals.map(i => [{ time: i.s, type: "s" }, { time: i.e, type: "e" }]), | ||
ends = intervals.map((i) => [ | ||
{ time: i.s, type: "s" }, | ||
{ time: i.e, type: "e" }, | ||
]), | ||
flattened = Array.prototype.concat(...ends), | ||
@@ -525,8 +532,8 @@ arr = flattened.sort((a, b) => a.time - b.time); | ||
* @param {...Interval} intervals | ||
* @return {[Interval]} | ||
* @return {Array} | ||
*/ | ||
difference(...intervals) { | ||
return Interval.xor([this].concat(intervals)) | ||
.map(i => this.intersection(i)) | ||
.filter(i => i && !i.isEmpty()); | ||
.map((i) => this.intersection(i)) | ||
.filter((i) => i && !i.isEmpty()); | ||
} | ||
@@ -544,5 +551,29 @@ | ||
/** | ||
* Returns a localized string representing this Interval. Accepts the same options as the | ||
* Intl.DateTimeFormat constructor and any presets defined by Luxon, such as | ||
* {@link DateTime.DATE_FULL} or {@link DateTime.TIME_SIMPLE}. The exact behavior of this method | ||
* is browser-specific, but in general it will return an appropriate representation of the | ||
* Interval in the assigned locale. Defaults to the system's locale if no locale has been | ||
* specified. | ||
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat | ||
* @param {Object} [formatOpts=DateTime.DATE_SHORT] - Either a DateTime preset or | ||
* Intl.DateTimeFormat constructor options. | ||
* @param {Object} opts - Options to override the configuration of the start DateTime. | ||
* @example Interval.fromISO('2022-11-07T09:00Z/2022-11-08T09:00Z').toLocaleString(); //=> 11/7/2022 – 11/8/2022 | ||
* @example Interval.fromISO('2022-11-07T09:00Z/2022-11-08T09:00Z').toLocaleString(DateTime.DATE_FULL); //=> November 7 – 8, 2022 | ||
* @example Interval.fromISO('2022-11-07T09:00Z/2022-11-08T09:00Z').toLocaleString(DateTime.DATE_FULL, { locale: 'fr-FR' }); //=> 7–8 novembre 2022 | ||
* @example Interval.fromISO('2022-11-07T17:00Z/2022-11-07T19:00Z').toLocaleString(DateTime.TIME_SIMPLE); //=> 6:00 – 8:00 PM | ||
* @example Interval.fromISO('2022-11-07T17:00Z/2022-11-07T19:00Z').toLocaleString({ weekday: 'short', month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' }); //=> Mon, Nov 07, 6:00 – 8:00 p | ||
* @return {string} | ||
*/ | ||
toLocaleString(formatOpts = Formats.DATE_SHORT, opts = {}) { | ||
return this.isValid | ||
? Formatter.create(this.s.loc.clone(opts), formatOpts).formatInterval(this) | ||
: INVALID; | ||
} | ||
/** | ||
* Returns an ISO 8601-compliant string representation of this Interval. | ||
* @see https://en.wikipedia.org/wiki/ISO_8601#Time_intervals | ||
* @param {Object} opts - The same options as {@link DateTime.toISO} | ||
* @param {Object} opts - The same options as {@link DateTime#toISO} | ||
* @return {string} | ||
@@ -570,3 +601,3 @@ */ | ||
* @see https://en.wikipedia.org/wiki/ISO_8601#Time_intervals | ||
* @param {Object} opts - The same options as {@link DateTime.toISO} | ||
* @param {Object} opts - The same options as {@link DateTime#toISO} | ||
* @return {string} | ||
@@ -580,6 +611,10 @@ */ | ||
/** | ||
* Returns a string representation of this Interval formatted according to the specified format string. | ||
* @param {string} dateFormat - the format string. This string formats the start and end time. See {@link DateTime.toFormat} for details. | ||
* @param {Object} opts - options | ||
* @param {string} [opts.separator = ' – '] - a separator to place between the start and end representations | ||
* Returns a string representation of this Interval formatted according to the specified format | ||
* string. **You may not want this.** See {@link Interval#toLocaleString} for a more flexible | ||
* formatting tool. | ||
* @param {string} dateFormat - The format string. This string formats the start and end time. | ||
* See {@link DateTime#toFormat} for details. | ||
* @param {Object} opts - Options. | ||
* @param {string} [opts.separator = ' – '] - A separator to place between the start and end | ||
* representations. | ||
* @return {string} | ||
@@ -586,0 +621,0 @@ */ |
@@ -9,6 +9,6 @@ import DateTime from "./datetime.js"; | ||
import InvalidZone from "./zones/invalidZone.js"; | ||
import LocalZone from "./zones/localZone.js"; | ||
import SystemZone from "./zones/systemZone.js"; | ||
import Settings from "./settings.js"; | ||
const VERSION = "1.28.1"; | ||
const VERSION = "3.4.0"; | ||
@@ -25,4 +25,4 @@ export { | ||
InvalidZone, | ||
LocalZone, | ||
Settings | ||
SystemZone, | ||
Settings, | ||
}; |
@@ -1,2 +0,2 @@ | ||
import LocalZone from "./zones/localZone.js"; | ||
import SystemZone from "./zones/systemZone.js"; | ||
import IANAZone from "./zones/IANAZone.js"; | ||
@@ -8,7 +8,8 @@ import Locale from "./impl/locale.js"; | ||
let now = () => Date.now(), | ||
defaultZone = null, // not setting this directly to LocalZone.instance bc loading order issues | ||
defaultZone = "system", | ||
defaultLocale = null, | ||
defaultNumberingSystem = null, | ||
defaultOutputCalendar = null, | ||
throwOnInvalid = false; | ||
twoDigitCutoffYear = 60, | ||
throwOnInvalid; | ||
@@ -39,27 +40,17 @@ /** | ||
/** | ||
* Get the default time zone to create DateTimes in. | ||
* @type {string} | ||
*/ | ||
static get defaultZoneName() { | ||
return Settings.defaultZone.name; | ||
} | ||
/** | ||
* Set the default time zone to create DateTimes in. Does not affect existing instances. | ||
* Use the value "system" to reset this value to the system's time zone. | ||
* @type {string} | ||
*/ | ||
static set defaultZoneName(z) { | ||
if (!z) { | ||
defaultZone = null; | ||
} else { | ||
defaultZone = normalizeZone(z); | ||
} | ||
static set defaultZone(zone) { | ||
defaultZone = zone; | ||
} | ||
/** | ||
* Get the default time zone object to create DateTimes in. Does not affect existing instances. | ||
* Get the default time zone object currently used to create DateTimes. Does not affect existing instances. | ||
* The default value is the system's time zone (the one set on the machine that runs this code). | ||
* @type {Zone} | ||
*/ | ||
static get defaultZone() { | ||
return defaultZone || LocalZone.instance; | ||
return normalizeZone(defaultZone, SystemZone.instance); | ||
} | ||
@@ -116,2 +107,22 @@ | ||
/** | ||
* Get the cutoff year after which a string encoding a year as two digits is interpreted to occur in the current century. | ||
* @type {number} | ||
*/ | ||
static get twoDigitCutoffYear() { | ||
return twoDigitCutoffYear; | ||
} | ||
/** | ||
* Set the cutoff year after which a string encoding a year as two digits is interpreted to occur in the current century. | ||
* @type {number} | ||
* @example Settings.twoDigitCutoffYear = 0 // cut-off year is 0, so all 'yy' are interpreted as current century | ||
* @example Settings.twoDigitCutoffYear = 50 // '49' -> 1949; '50' -> 2050 | ||
* @example Settings.twoDigitCutoffYear = 1950 // interpreted as 50 | ||
* @example Settings.twoDigitCutoffYear = 2050 // ALSO interpreted as 50 | ||
*/ | ||
static set twoDigitCutoffYear(cutoffYear) { | ||
twoDigitCutoffYear = cutoffYear % 100; | ||
} | ||
/** | ||
* Get whether Luxon will throw when it encounters invalid DateTimes, Durations, or Intervals | ||
@@ -118,0 +129,0 @@ * @type {boolean} |
@@ -1,2 +0,1 @@ | ||
/* eslint no-unused-vars: "off" */ | ||
import { ZoneIsAbstractError } from "./errors.js"; | ||
@@ -26,2 +25,6 @@ | ||
get ianaName() { | ||
return this.name; | ||
} | ||
/** | ||
@@ -32,3 +35,3 @@ * Returns whether the offset is known to be fixed for the whole year. | ||
*/ | ||
get universal() { | ||
get isUniversal() { | ||
throw new ZoneIsAbstractError(); | ||
@@ -35,0 +38,0 @@ } |
@@ -65,2 +65,10 @@ import { formatOffset, signedOffset } from "../impl/util.js"; | ||
get ianaName() { | ||
if (this.fixed === 0) { | ||
return "Etc/UTC"; | ||
} else { | ||
return `Etc/GMT${formatOffset(-this.fixed, "narrow")}`; | ||
} | ||
} | ||
/** @override **/ | ||
@@ -77,3 +85,3 @@ offsetName() { | ||
/** @override **/ | ||
get universal() { | ||
get isUniversal() { | ||
return true; | ||
@@ -80,0 +88,0 @@ } |
@@ -1,6 +0,4 @@ | ||
import { formatOffset, parseZoneInfo, isUndefined, ianaRegex, objToLocalTS } from "../impl/util.js"; | ||
import { formatOffset, parseZoneInfo, isUndefined, objToLocalTS } from "../impl/util.js"; | ||
import Zone from "../zone.js"; | ||
const matchingRegex = RegExp(`^${ianaRegex.source}$`); | ||
let dtfCache = {}; | ||
@@ -17,3 +15,4 @@ function makeDTF(zone) { | ||
minute: "2-digit", | ||
second: "2-digit" | ||
second: "2-digit", | ||
era: "short", | ||
}); | ||
@@ -28,5 +27,6 @@ } | ||
day: 2, | ||
hour: 3, | ||
minute: 4, | ||
second: 5 | ||
era: 3, | ||
hour: 4, | ||
minute: 5, | ||
second: 6, | ||
}; | ||
@@ -36,15 +36,17 @@ | ||
const formatted = dtf.format(date).replace(/\u200E/g, ""), | ||
parsed = /(\d+)\/(\d+)\/(\d+),? (\d+):(\d+):(\d+)/.exec(formatted), | ||
[, fMonth, fDay, fYear, fHour, fMinute, fSecond] = parsed; | ||
return [fYear, fMonth, fDay, fHour, fMinute, fSecond]; | ||
parsed = /(\d+)\/(\d+)\/(\d+) (AD|BC),? (\d+):(\d+):(\d+)/.exec(formatted), | ||
[, fMonth, fDay, fYear, fadOrBc, fHour, fMinute, fSecond] = parsed; | ||
return [fYear, fMonth, fDay, fadOrBc, fHour, fMinute, fSecond]; | ||
} | ||
function partsOffset(dtf, date) { | ||
const formatted = dtf.formatToParts(date), | ||
filled = []; | ||
const formatted = dtf.formatToParts(date); | ||
const filled = []; | ||
for (let i = 0; i < formatted.length; i++) { | ||
const { type, value } = formatted[i], | ||
pos = typeToPos[type]; | ||
const { type, value } = formatted[i]; | ||
const pos = typeToPos[type]; | ||
if (!isUndefined(pos)) { | ||
if (type === "era") { | ||
filled[pos] = value; | ||
} else if (!isUndefined(pos)) { | ||
filled[pos] = parseInt(value, 10); | ||
@@ -86,8 +88,8 @@ } | ||
* @example IANAZone.isValidSpecifier("America/New_York") //=> true | ||
* @example IANAZone.isValidSpecifier("Fantasia/Castle") //=> true | ||
* @example IANAZone.isValidSpecifier("Sport~~blorp") //=> false | ||
* @deprecated This method returns false for some valid IANA names. Use isValidZone instead. | ||
* @return {boolean} | ||
*/ | ||
static isValidSpecifier(s) { | ||
return !!(s && s.match(matchingRegex)); | ||
return this.isValidZone(s); | ||
} | ||
@@ -104,2 +106,5 @@ | ||
static isValidZone(zone) { | ||
if (!zone) { | ||
return false; | ||
} | ||
try { | ||
@@ -113,14 +118,2 @@ new Intl.DateTimeFormat("en-US", { timeZone: zone }).format(); | ||
// Etc/GMT+8 -> -480 | ||
/** @ignore */ | ||
static parseGMTOffset(specifier) { | ||
if (specifier) { | ||
const match = specifier.match(/^Etc\/GMT(0|[+-]\d{1,2})$/i); | ||
if (match) { | ||
return -60 * parseInt(match[1]); | ||
} | ||
} | ||
return null; | ||
} | ||
constructor(name) { | ||
@@ -145,3 +138,3 @@ super(); | ||
/** @override **/ | ||
get universal() { | ||
get isUniversal() { | ||
return false; | ||
@@ -166,9 +159,14 @@ } | ||
const dtf = makeDTF(this.name), | ||
[year, month, day, hour, minute, second] = dtf.formatToParts | ||
? partsOffset(dtf, date) | ||
: hackyOffset(dtf, date), | ||
// work around https://bugs.chromium.org/p/chromium/issues/detail?id=1025564&can=2&q=%2224%3A00%22%20datetimeformat | ||
adjustedHour = hour === 24 ? 0 : hour; | ||
const dtf = makeDTF(this.name); | ||
let [year, month, day, adOrBc, hour, minute, second] = dtf.formatToParts | ||
? partsOffset(dtf, date) | ||
: hackyOffset(dtf, date); | ||
if (adOrBc === "BC") { | ||
year = -Math.abs(year) + 1; | ||
} | ||
// because we're using hour12 and https://bugs.chromium.org/p/chromium/issues/detail?id=1025564&can=2&q=%2224%3A00%22%20datetimeformat | ||
const adjustedHour = hour === 24 ? 0 : hour; | ||
const asUTC = objToLocalTS({ | ||
@@ -181,3 +179,3 @@ year, | ||
second, | ||
millisecond: 0 | ||
millisecond: 0, | ||
}); | ||
@@ -184,0 +182,0 @@ |
@@ -25,3 +25,3 @@ import Zone from "../zone.js"; | ||
/** @override **/ | ||
get universal() { | ||
get isUniversal() { | ||
return false; | ||
@@ -28,0 +28,0 @@ } |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
4063463
19
41
42397
56