Move assets to priv, vendorise Luxon

This commit is contained in:
Mikko Ahlroth 2024-04-11 17:58:33 +03:00
parent 2d3c8493a8
commit 0af6e83cec
91 changed files with 48108 additions and 54 deletions

24
package-lock.json generated
View file

@ -1,24 +0,0 @@
{
"name": "gloss",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "gloss",
"version": "1.0.0",
"license": "AGPL-3.0-or-later",
"dependencies": {
"luxon": "^3.4.4"
}
},
"node_modules/luxon": {
"version": "3.4.4",
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz",
"integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==",
"engines": {
"node": ">=12"
}
}
}
}

View file

@ -1,28 +0,0 @@
{
"name": "gloss",
"version": "1.0.0",
"description": "Glossy blog generator",
"main": "\"\"",
"directories": {
"test": "test"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://gitlab.com/Nicd/gloss.git"
},
"keywords": [
"blog"
],
"author": "Mikko Ahlroth <mikko@ahlroth.fi>",
"license": "AGPL-3.0-or-later",
"bugs": {
"url": "https://gitlab.com/Nicd/gloss/issues"
},
"homepage": "https://gitlab.com/Nicd/gloss#readme",
"dependencies": {
"luxon": "^3.4.4"
}
}

7
priv/vendor/luxon/LICENSE.md vendored Normal file
View file

@ -0,0 +1,7 @@
Copyright 2019 JS Foundation and other contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

55
priv/vendor/luxon/README.md vendored Normal file
View file

@ -0,0 +1,55 @@
# Luxon
[![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]
Luxon is a library for working with dates and times in JavaScript.
```js
DateTime.now().setZone("America/New_York").minus({ weeks: 1 }).endOf("day").toISO();
```
## Upgrading to 3.0
[Guide](https://moment.github.io/luxon/#upgrading)
## Features
* DateTime, Duration, and Interval types.
* Immutable, chainable, unambiguous API.
* Parsing and formatting for common and custom formats.
* Native time zone and Intl support (no locale or tz files).
## Download/install
[Download/install instructions](https://moment.github.io/luxon/#/install)
## Documentation
* [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)
## Development
See [contributing](CONTRIBUTING.md).
![Phasers to stun][phasers-image]
[license-image]: https://img.shields.io/badge/license-MIT-blue.svg
[license]: LICENSE.md
[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
[npm-url]: https://npmjs.org/package/luxon
[npm-version-image]: https://badge.fury.io/js/luxon.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://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

8311
priv/vendor/luxon/build/amd/luxon.js vendored Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

7695
priv/vendor/luxon/build/es6/luxon.js vendored Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

8314
priv/vendor/luxon/build/global/luxon.js vendored Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

7384
priv/vendor/luxon/build/node/luxon.js vendored Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

87
priv/vendor/luxon/package.json vendored Normal file
View file

@ -0,0 +1,87 @@
{
"name": "luxon",
"version": "3.4.4",
"description": "Immutable date wrapper",
"author": "Isaac Cambron",
"keywords": [
"date",
"immutable"
],
"repository": "https://github.com/moment/luxon",
"exports": {
".": {
"import": "./src/luxon.js",
"require": "./build/node/luxon.js"
},
"./package.json": "./package.json"
},
"scripts": {
"build": "babel-node tasks/buildAll.js",
"build-node": "babel-node tasks/buildNode.js",
"build-global": "babel-node tasks/buildGlobal.js",
"jest": "jest",
"test": "jest --coverage",
"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",
"prepack": "babel-node tasks/buildAll.js",
"prepare": "husky install",
"show-site": "http-server build"
},
"lint-staged": {
"*.{js,json}": [
"prettier --write"
]
},
"devDependencies": {
"@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": "latest",
"documentation": "latest",
"fs-extra": "^6.0.1",
"http-server": "^14.1.1",
"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"
},
"main": "build/node/luxon.js",
"module": "src/luxon.js",
"browser": "build/cjs-browser/luxon.js",
"jsdelivr": "build/global/luxon.min.js",
"unpkg": "build/global/luxon.min.js",
"engines": {
"node": ">=12"
},
"files": [
"build/node/luxon.js",
"build/node/luxon.js.map",
"build/cjs-browser/luxon.js",
"build/cjs-browser/luxon.js.map",
"build/amd/luxon.js",
"build/amd/luxon.js.map",
"build/global/luxon.js",
"build/global/luxon.js.map",
"build/global/luxon.min.js",
"build/global/luxon.min.js.map",
"build/es6/luxon.js",
"build/es6/luxon.js.map",
"src"
],
"license": "MIT",
"sideEffects": false
}

2422
priv/vendor/luxon/src/datetime.js vendored Normal file

File diff suppressed because it is too large Load diff

990
priv/vendor/luxon/src/duration.js vendored Normal file
View file

@ -0,0 +1,990 @@
import { InvalidArgumentError, InvalidDurationError, InvalidUnitError } from "./errors.js";
import Formatter from "./impl/formatter.js";
import Invalid from "./impl/invalid.js";
import Locale from "./impl/locale.js";
import { parseISODuration, parseISOTimeOnly } from "./impl/regexParser.js";
import {
asNumber,
hasOwnProperty,
isNumber,
isUndefined,
normalizeObject,
roundTo,
} from "./impl/util.js";
import Settings from "./settings.js";
import DateTime from "./datetime.js";
const INVALID = "Invalid Duration";
// unit conversion constants
export const lowOrderMatrix = {
weeks: {
days: 7,
hours: 7 * 24,
minutes: 7 * 24 * 60,
seconds: 7 * 24 * 60 * 60,
milliseconds: 7 * 24 * 60 * 60 * 1000,
},
days: {
hours: 24,
minutes: 24 * 60,
seconds: 24 * 60 * 60,
milliseconds: 24 * 60 * 60 * 1000,
},
hours: { minutes: 60, seconds: 60 * 60, milliseconds: 60 * 60 * 1000 },
minutes: { seconds: 60, milliseconds: 60 * 1000 },
seconds: { milliseconds: 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,
},
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 = {
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,
},
...lowOrderMatrix,
};
// units ordered by size
const orderedUnits = [
"years",
"quarters",
"months",
"weeks",
"days",
"hours",
"minutes",
"seconds",
"milliseconds",
];
const reverseUnits = orderedUnits.slice(0).reverse();
// clone really means "create another instance just like this one, but with these changes"
function clone(dur, alts, clear = false) {
// deep merge for vals
const conf = {
values: clear ? alts.values : { ...dur.values, ...(alts.values || {}) },
loc: dur.loc.clone(alts.loc),
conversionAccuracy: alts.conversionAccuracy || dur.conversionAccuracy,
matrix: alts.matrix || dur.matrix,
};
return new Duration(conf);
}
function durationToMillis(matrix, vals) {
let sum = vals.milliseconds ?? 0;
for (const unit of reverseUnits.slice(1)) {
if (vals[unit]) {
sum += vals[unit] * matrix[unit]["milliseconds"];
}
}
return sum;
}
// NB: mutates parameters
function normalizeValues(matrix, vals) {
// the logic below assumes the overall value of the duration is positive
// if this is not the case, factor is used to make it so
const factor = durationToMillis(matrix, vals) < 0 ? -1 : 1;
orderedUnits.reduceRight((previous, current) => {
if (!isUndefined(vals[current])) {
if (previous) {
const previousVal = vals[previous] * factor;
const conv = matrix[current][previous];
// if (previousVal < 0):
// lower order unit is negative (e.g. { years: 2, days: -2 })
// normalize this by reducing the higher order unit by the appropriate amount
// and increasing the lower order unit
// this can never make the higher order unit negative, because this function only operates
// on positive durations, so the amount of time represented by the lower order unit cannot
// be larger than the higher order unit
// else:
// lower order unit is positive (e.g. { years: 2, days: 450 } or { years: -2, days: 450 })
// in this case we attempt to convert as much as possible from the lower order unit into
// the higher order one
//
// Math.floor takes care of both of these cases, rounding away from 0
// if previousVal < 0 it makes the absolute value larger
// if previousVal >= it makes the absolute value smaller
const rollUp = Math.floor(previousVal / conv);
vals[current] += rollUp * factor;
vals[previous] -= rollUp * conv * factor;
}
return current;
} else {
return previous;
}
}, null);
// try to convert any decimals into smaller units if possible
// for example for { years: 2.5, days: 0, seconds: 0 } we want to get { years: 2, days: 182, hours: 12 }
orderedUnits.reduce((previous, current) => {
if (!isUndefined(vals[current])) {
if (previous) {
const fraction = vals[previous] % 1;
vals[previous] -= fraction;
vals[current] += fraction * matrix[previous][current];
}
return current;
} else {
return previous;
}
}, null);
}
// 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.
*
* 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}
*
* There's are more methods documented below. In addition, for more information on subtler topics like internationalization and validity, see the external documentation.
*/
export default class Duration {
/**
* @private
*/
constructor(config) {
const accurate = config.conversionAccuracy === "longterm" || false;
let matrix = accurate ? accurateMatrix : casualMatrix;
if (config.matrix) {
matrix = config.matrix;
}
/**
* @access private
*/
this.values = config.values;
/**
* @access private
*/
this.loc = config.loc || Locale.create();
/**
* @access private
*/
this.conversionAccuracy = accurate ? "longterm" : "casual";
/**
* @access private
*/
this.invalid = config.invalid || null;
/**
* @access private
*/
this.matrix = matrix;
/**
* @access private
*/
this.isLuxonDuration = true;
}
/**
* Create Duration from a number of milliseconds.
* @param {number} count of milliseconds
* @param {Object} opts - options for parsing
* @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 conversion system to use
* @return {Duration}
*/
static fromMillis(count, opts) {
return Duration.fromObject({ milliseconds: count }, opts);
}
/**
* Create a Duration from a JavaScript object with keys like 'years' and 'hours'.
* If this object is empty then a zero milliseconds duration is returned.
* @param {Object} obj - the object to create the DateTime from
* @param {number} obj.years
* @param {number} obj.quarters
* @param {number} obj.months
* @param {number} obj.weeks
* @param {number} obj.days
* @param {number} obj.hours
* @param {number} obj.minutes
* @param {number} obj.seconds
* @param {number} obj.milliseconds
* @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, opts = {}) {
if (obj == null || typeof obj !== "object") {
throw new InvalidArgumentError(
`Duration.fromObject: argument expected to be an object, got ${
obj === null ? "null" : typeof obj
}`
);
}
return new Duration({
values: normalizeObject(obj, Duration.normalizeUnit),
loc: Locale.fromObject(opts),
conversionAccuracy: opts.conversionAccuracy,
matrix: opts.matrix,
});
}
/**
* 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.
* @param {string} text - text to parse
* @param {Object} opts - options for parsing
* @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 preset conversion system to use
* @see https://en.wikipedia.org/wiki/ISO_8601#Durations
* @example Duration.fromISO('P3Y6M1W4DT12H30M5S').toObject() //=> { years: 3, months: 6, weeks: 1, days: 4, hours: 12, minutes: 30, seconds: 5 }
* @example Duration.fromISO('PT23H').toObject() //=> { hours: 23 }
* @example Duration.fromISO('P5Y3M').toObject() //=> { years: 5, months: 3 }
* @return {Duration}
*/
static fromISO(text, opts) {
const [parsed] = parseISODuration(text);
if (parsed) {
return Duration.fromObject(parsed, opts);
} else {
return Duration.invalid("unparsable", `the input "${text}" can't be parsed as ISO 8601`);
}
}
/**
* Create a Duration from an ISO 8601 time string.
* @param {string} text - text to parse
* @param {Object} opts - options for parsing
* @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 conversion system to use
* @see https://en.wikipedia.org/wiki/ISO_8601#Times
* @example Duration.fromISOTime('11:22:33.444').toObject() //=> { hours: 11, minutes: 22, seconds: 33, milliseconds: 444 }
* @example Duration.fromISOTime('11:00').toObject() //=> { hours: 11, minutes: 0, seconds: 0 }
* @example Duration.fromISOTime('T11:00').toObject() //=> { hours: 11, minutes: 0, seconds: 0 }
* @example Duration.fromISOTime('1100').toObject() //=> { hours: 11, minutes: 0, seconds: 0 }
* @example Duration.fromISOTime('T1100').toObject() //=> { hours: 11, minutes: 0, seconds: 0 }
* @return {Duration}
*/
static fromISOTime(text, opts) {
const [parsed] = parseISOTimeOnly(text);
if (parsed) {
return Duration.fromObject(parsed, opts);
} else {
return Duration.invalid("unparsable", `the input "${text}" can't be parsed as ISO 8601`);
}
}
/**
* Create an invalid Duration.
* @param {string} reason - simple string of why this datetime is invalid. Should not contain parameters or anything else data-dependent
* @param {string} [explanation=null] - longer explanation, may include parameters and other useful debugging information
* @return {Duration}
*/
static invalid(reason, explanation = null) {
if (!reason) {
throw new InvalidArgumentError("need to specify a reason the Duration is invalid");
}
const invalid = reason instanceof Invalid ? reason : new Invalid(reason, explanation);
if (Settings.throwOnInvalid) {
throw new InvalidDurationError(invalid);
} else {
return new Duration({ invalid });
}
}
/**
* @private
*/
static normalizeUnit(unit) {
const normalized = {
year: "years",
years: "years",
quarter: "quarters",
quarters: "quarters",
month: "months",
months: "months",
week: "weeks",
weeks: "weeks",
day: "days",
days: "days",
hour: "hours",
hours: "hours",
minute: "minutes",
minutes: "minutes",
second: "seconds",
seconds: "seconds",
millisecond: "milliseconds",
milliseconds: "milliseconds",
}[unit ? unit.toLowerCase() : unit];
if (!normalized) throw new InvalidUnitError(unit);
return normalized;
}
/**
* Check if an object is a Duration. Works across context boundaries
* @param {object} o
* @return {boolean}
*/
static isDuration(o) {
return (o && o.isLuxonDuration) || false;
}
/**
* Get the locale of a Duration, such 'en-GB'
* @type {string}
*/
get locale() {
return this.isValid ? this.loc.locale : null;
}
/**
* Get the numbering system of a Duration, such 'beng'. The numbering system is used when formatting the Duration
*
* @type {string}
*/
get numberingSystem() {
return this.isValid ? this.loc.numberingSystem : null;
}
/**
* Returns a string representation of this Duration formatted according to the specified format string. You may use these tokens:
* * `S` for milliseconds
* * `s` for seconds
* * `m` for minutes
* * `h` for hours
* * `d` for days
* * `w` for weeks
* * `M` for months
* * `y` for years
* Notes:
* * Add padding by repeating the token, e.g. "yy" pads the years to two digits, "hhhh" pads the hours out to four digits
* * 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
* @param {Object} opts - options
* @param {boolean} [opts.floor=true] - floor numerical values
* @example Duration.fromObject({ years: 1, days: 6, seconds: 2 }).toFormat("y d s") //=> "1 6 2"
* @example Duration.fromObject({ years: 1, days: 6, seconds: 2 }).toFormat("yy dd sss") //=> "01 06 002"
* @example Duration.fromObject({ years: 1, days: 6, seconds: 2 }).toFormat("M S") //=> "12 518402000"
* @return {string}
*/
toFormat(fmt, opts = {}) {
// reverse-compat since 1.2; we always round down now, never up, and we do it by default
const fmtOpts = {
...opts,
floor: opts.round !== false && opts.floor !== false,
};
return this.isValid
? Formatter.create(this.loc, fmtOpts).formatDurationFromString(this, fmt)
: INVALID;
}
/**
* Returns a string representation of a Duration with all units included.
* To modify its behavior, use `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/NumberFormat#options
* @param {Object} opts - Formatting options. Accepts the same keys as the options parameter of the native `Intl.NumberFormat` constructor, as well as `listStyle`.
* @param {string} [opts.listStyle='narrow'] - How to format the merged list. Corresponds to the `style` property of the options parameter of the native `Intl.ListFormat` constructor.
* @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 = {}) {
if (!this.isValid) return INVALID;
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.
* @example Duration.fromObject({ years: 1, days: 6, seconds: 2 }).toObject() //=> { years: 1, days: 6, seconds: 2 }
* @return {Object}
*/
toObject() {
if (!this.isValid) return {};
return { ...this.values };
}
/**
* Returns an ISO 8601-compliant string representation of this Duration.
* @see https://en.wikipedia.org/wiki/ISO_8601#Durations
* @example Duration.fromObject({ years: 3, seconds: 45 }).toISO() //=> 'P3YT45S'
* @example Duration.fromObject({ months: 4, seconds: 45 }).toISO() //=> 'P4MT45S'
* @example Duration.fromObject({ months: 5 }).toISO() //=> 'P5M'
* @example Duration.fromObject({ minutes: 5 }).toISO() //=> 'PT5M'
* @example Duration.fromObject({ milliseconds: 6 }).toISO() //=> 'PT0.006S'
* @return {string}
*/
toISO() {
// we could use the formatter, but this is an easier way to get the minimum string
if (!this.isValid) return null;
let s = "P";
if (this.years !== 0) s += this.years + "Y";
if (this.months !== 0 || this.quarters !== 0) s += this.months + this.quarters * 3 + "M";
if (this.weeks !== 0) s += this.weeks + "W";
if (this.days !== 0) s += this.days + "D";
if (this.hours !== 0 || this.minutes !== 0 || this.seconds !== 0 || this.milliseconds !== 0)
s += "T";
if (this.hours !== 0) s += this.hours + "H";
if (this.minutes !== 0) s += this.minutes + "M";
if (this.seconds !== 0 || this.milliseconds !== 0)
// this will handle "floating point madness" by removing extra decimal places
// https://stackoverflow.com/questions/588004/is-floating-point-math-broken
s += roundTo(this.seconds + this.milliseconds / 1000, 3) + "S";
if (s === "P") s += "T0S";
return s;
}
/**
* Returns an ISO 8601-compliant string representation of this Duration, formatted as a time of day.
* Note that this will return null if the duration is invalid, negative, or equal to or greater than 24 hours.
* @see https://en.wikipedia.org/wiki/ISO_8601#Times
* @param {Object} opts - options
* @param {boolean} [opts.suppressMilliseconds=false] - exclude milliseconds from the format if they're 0
* @param {boolean} [opts.suppressSeconds=false] - exclude seconds from the format if they're 0
* @param {boolean} [opts.includePrefix=false] - include the `T` prefix
* @param {string} [opts.format='extended'] - choose between the basic and extended format
* @example Duration.fromObject({ hours: 11 }).toISOTime() //=> '11:00:00.000'
* @example Duration.fromObject({ hours: 11 }).toISOTime({ suppressMilliseconds: true }) //=> '11:00:00'
* @example Duration.fromObject({ hours: 11 }).toISOTime({ suppressSeconds: true }) //=> '11:00'
* @example Duration.fromObject({ hours: 11 }).toISOTime({ includePrefix: true }) //=> 'T11:00:00.000'
* @example Duration.fromObject({ hours: 11 }).toISOTime({ format: 'basic' }) //=> '110000.000'
* @return {string}
*/
toISOTime(opts = {}) {
if (!this.isValid) return null;
const millis = this.toMillis();
if (millis < 0 || millis >= 86400000) return null;
opts = {
suppressMilliseconds: false,
suppressSeconds: false,
includePrefix: false,
format: "extended",
...opts,
includeOffset: false,
};
const dateTime = DateTime.fromMillis(millis, { zone: "UTC" });
return dateTime.toISOTime(opts);
}
/**
* Returns an ISO 8601 representation of this Duration appropriate for use in JSON.
* @return {string}
*/
toJSON() {
return this.toISO();
}
/**
* Returns an ISO 8601 representation of this Duration appropriate for use in debugging.
* @return {string}
*/
toString() {
return this.toISO();
}
/**
* Returns a string representation of this Duration appropriate for the REPL.
* @return {string}
*/
[Symbol.for("nodejs.util.inspect.custom")]() {
if (this.isValid) {
return `Duration { values: ${JSON.stringify(this.values)} }`;
} else {
return `Duration { Invalid, reason: ${this.invalidReason} }`;
}
}
/**
* Returns an milliseconds value of this Duration.
* @return {number}
*/
toMillis() {
if (!this.isValid) return NaN;
return durationToMillis(this.matrix, this.values);
}
/**
* Returns an milliseconds value of this Duration. Alias of {@link toMillis}
* @return {number}
*/
valueOf() {
return this.toMillis();
}
/**
* Make this Duration longer by the specified amount. Return a newly-constructed Duration.
* @param {Duration|Object|number} duration - The amount to add. Either a Luxon Duration, a number of milliseconds, the object argument to Duration.fromObject()
* @return {Duration}
*/
plus(duration) {
if (!this.isValid) return this;
const dur = Duration.fromDurationLike(duration),
result = {};
for (const k of orderedUnits) {
if (hasOwnProperty(dur.values, k) || hasOwnProperty(this.values, k)) {
result[k] = dur.get(k) + this.get(k);
}
}
return clone(this, { values: result }, true);
}
/**
* Make this Duration shorter by the specified amount. Return a newly-constructed Duration.
* @param {Duration|Object|number} duration - The amount to subtract. Either a Luxon Duration, a number of milliseconds, the object argument to Duration.fromObject()
* @return {Duration}
*/
minus(duration) {
if (!this.isValid) return this;
const dur = Duration.fromDurationLike(duration);
return this.plus(dur.negate());
}
/**
* Scale this Duration by the specified amount. Return a newly-constructed Duration.
* @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 }).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}
*/
mapUnits(fn) {
if (!this.isValid) return this;
const result = {};
for (const k of Object.keys(this.values)) {
result[k] = asNumber(fn(this.values[k], k));
}
return clone(this, { values: result }, true);
}
/**
* Get the value of unit.
* @param {string} unit - a unit such as 'minute' or 'day'
* @example Duration.fromObject({years: 2, days: 3}).get('years') //=> 2
* @example Duration.fromObject({years: 2, days: 3}).get('months') //=> 0
* @example Duration.fromObject({years: 2, days: 3}).get('days') //=> 3
* @return {number}
*/
get(unit) {
return this[Duration.normalizeUnit(unit)];
}
/**
* "Set" the values of specified units. Return a newly-constructed Duration.
* @param {Object} values - a mapping of units to numbers
* @example dur.set({ years: 2017 })
* @example dur.set({ hours: 8, minutes: 30 })
* @return {Duration}
*/
set(values) {
if (!this.isValid) return this;
const mixed = { ...this.values, ...normalizeObject(values, Duration.normalizeUnit) };
return clone(this, { values: mixed });
}
/**
* "Set" the locale and/or numberingSystem. Returns a newly-constructed Duration.
* @example dur.reconfigure({ locale: 'en-GB' })
* @return {Duration}
*/
reconfigure({ locale, numberingSystem, conversionAccuracy, matrix } = {}) {
const loc = this.loc.clone({ locale, numberingSystem });
const opts = { loc, matrix, conversionAccuracy };
return clone(this, opts);
}
/**
* Return the length of the duration in the specified unit.
* @param {string} unit - a unit such as 'minutes' or 'days'
* @example Duration.fromObject({years: 1}).as('days') //=> 365
* @example Duration.fromObject({years: 1}).as('months') //=> 12
* @example Duration.fromObject({hours: 60}).as('days') //=> 2.5
* @return {number}
*/
as(unit) {
return this.isValid ? this.shiftTo(unit).get(unit) : NaN;
}
/**
* Reduce this Duration to its canonical representation in its current units.
* Assuming the overall value of the Duration is positive, this means:
* - excessive values for lower-order units are converted to higher-order units (if possible, see first and second example)
* - negative lower-order units are converted to higher order units (there must be such a higher order unit, otherwise
* the overall value would be negative, see third example)
* - fractional values for higher-order units are converted to lower-order units (if possible, see fourth example)
*
* If the overall value is negative, the result of this method is equivalent to `this.negate().normalize().negate()`.
* @example Duration.fromObject({ years: 2, days: 5000 }).normalize().toObject() //=> { years: 15, days: 255 }
* @example Duration.fromObject({ days: 5000 }).normalize().toObject() //=> { days: 5000 }
* @example Duration.fromObject({ hours: 12, minutes: -45 }).normalize().toObject() //=> { hours: 11, minutes: 15 }
* @example Duration.fromObject({ years: 2.5, days: 0, hours: 0 }).normalize().toObject() //=> { years: 2, days: 182, hours: 12 }
* @return {Duration}
*/
normalize() {
if (!this.isValid) return this;
const vals = this.toObject();
normalizeValues(this.matrix, vals);
return clone(this, { values: vals }, true);
}
/**
* 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);
}
/**
* Convert this Duration into its representation in a different set of units.
* @example Duration.fromObject({ hours: 1, seconds: 30 }).shiftTo('minutes', 'milliseconds').toObject() //=> { minutes: 60, milliseconds: 30000 }
* @return {Duration}
*/
shiftTo(...units) {
if (!this.isValid) return this;
if (units.length === 0) {
return this;
}
units = units.map((u) => Duration.normalizeUnit(u));
const built = {},
accumulated = {},
vals = this.toObject();
let lastUnit;
for (const k of orderedUnits) {
if (units.indexOf(k) >= 0) {
lastUnit = k;
let own = 0;
// anything we haven't boiled down yet should get boiled to this unit
for (const ak in accumulated) {
own += this.matrix[ak][k] * accumulated[ak];
accumulated[ak] = 0;
}
// plus anything that's already in this unit
if (isNumber(vals[k])) {
own += vals[k];
}
// only keep the integer part for now in the hopes of putting any decimal part
// into a smaller unit later
const i = Math.trunc(own);
built[k] = i;
accumulated[k] = (own * 1000 - i * 1000) / 1000;
// otherwise, keep it in the wings to boil it later
} else if (isNumber(vals[k])) {
accumulated[k] = vals[k];
}
}
// anything leftover becomes the decimal for the last unit
// lastUnit must be defined since units is not empty
for (const key in accumulated) {
if (accumulated[key] !== 0) {
built[lastUnit] +=
key === lastUnit ? accumulated[key] : accumulated[key] / this.matrix[lastUnit][key];
}
}
normalizeValues(this.matrix, built);
return clone(this, { values: built }, true);
}
/**
* 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.
* @example Duration.fromObject({ hours: 1, seconds: 30 }).negate().toObject() //=> { hours: -1, seconds: -30 }
* @return {Duration}
*/
negate() {
if (!this.isValid) return this;
const negated = {};
for (const k of Object.keys(this.values)) {
negated[k] = this.values[k] === 0 ? 0 : -this.values[k];
}
return clone(this, { values: negated }, true);
}
/**
* Get the years.
* @type {number}
*/
get years() {
return this.isValid ? this.values.years || 0 : NaN;
}
/**
* Get the quarters.
* @type {number}
*/
get quarters() {
return this.isValid ? this.values.quarters || 0 : NaN;
}
/**
* Get the months.
* @type {number}
*/
get months() {
return this.isValid ? this.values.months || 0 : NaN;
}
/**
* Get the weeks
* @type {number}
*/
get weeks() {
return this.isValid ? this.values.weeks || 0 : NaN;
}
/**
* Get the days.
* @type {number}
*/
get days() {
return this.isValid ? this.values.days || 0 : NaN;
}
/**
* Get the hours.
* @type {number}
*/
get hours() {
return this.isValid ? this.values.hours || 0 : NaN;
}
/**
* Get the minutes.
* @type {number}
*/
get minutes() {
return this.isValid ? this.values.minutes || 0 : NaN;
}
/**
* Get the seconds.
* @return {number}
*/
get seconds() {
return this.isValid ? this.values.seconds || 0 : NaN;
}
/**
* Get the milliseconds.
* @return {number}
*/
get milliseconds() {
return this.isValid ? this.values.milliseconds || 0 : NaN;
}
/**
* Returns whether the Duration is invalid. Invalid durations are returned by diff operations
* on invalid DateTimes or Intervals.
* @return {boolean}
*/
get isValid() {
return this.invalid === null;
}
/**
* Returns an error code if this Duration became invalid, or null if the Duration is valid
* @return {string}
*/
get invalidReason() {
return this.invalid ? this.invalid.reason : null;
}
/**
* Returns an explanation of why this Duration became invalid, or null if the Duration is valid
* @type {string}
*/
get invalidExplanation() {
return this.invalid ? this.invalid.explanation : null;
}
/**
* Equality check
* Two Durations are equal iff they have the same units and the same values for each unit.
* @param {Duration} other
* @return {boolean}
*/
equals(other) {
if (!this.isValid || !other.isValid) {
return false;
}
if (!this.loc.equals(other.loc)) {
return false;
}
function eq(v1, v2) {
// Consider 0 and undefined as equal
if (v1 === undefined || v1 === 0) return v2 === undefined || v2 === 0;
return v1 === v2;
}
for (const u of orderedUnits) {
if (!eq(this.values[u], other.values[u])) {
return false;
}
}
return true;
}
}

61
priv/vendor/luxon/src/errors.js vendored Normal file
View file

@ -0,0 +1,61 @@
// these aren't really private, but nor are they really useful to document
/**
* @private
*/
class LuxonError extends Error {}
/**
* @private
*/
export class InvalidDateTimeError extends LuxonError {
constructor(reason) {
super(`Invalid DateTime: ${reason.toMessage()}`);
}
}
/**
* @private
*/
export class InvalidIntervalError extends LuxonError {
constructor(reason) {
super(`Invalid Interval: ${reason.toMessage()}`);
}
}
/**
* @private
*/
export class InvalidDurationError extends LuxonError {
constructor(reason) {
super(`Invalid Duration: ${reason.toMessage()}`);
}
}
/**
* @private
*/
export class ConflictingSpecificationError extends LuxonError {}
/**
* @private
*/
export class InvalidUnitError extends LuxonError {
constructor(unit) {
super(`Invalid unit ${unit}`);
}
}
/**
* @private
*/
export class InvalidArgumentError extends LuxonError {}
/**
* @private
*/
export class ZoneIsAbstractError extends LuxonError {
constructor() {
super("Zone is an abstract class");
}
}

View file

@ -0,0 +1,206 @@
import {
integerBetween,
isLeapYear,
timeObject,
daysInYear,
daysInMonth,
weeksInWeekYear,
isInteger,
isUndefined,
} from "./util.js";
import Invalid from "./invalid.js";
import { ConflictingSpecificationError } from "../errors.js";
const nonLeapLadder = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334],
leapLadder = [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335];
function unitOutOfRange(unit, value) {
return new Invalid(
"unit out of range",
`you specified ${value} (of type ${typeof value}) as a ${unit}, which is invalid`
);
}
export function dayOfWeek(year, month, day) {
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;
}
function computeOrdinal(year, month, day) {
return day + (isLeapYear(year) ? leapLadder : nonLeapLadder)[month - 1];
}
function uncomputeOrdinal(year, ordinal) {
const table = isLeapYear(year) ? leapLadder : nonLeapLadder,
month0 = table.findIndex((i) => i < ordinal),
day = ordinal - table[month0];
return { month: month0 + 1, day };
}
export function isoWeekdayToLocal(isoWeekday, startOfWeek) {
return ((isoWeekday - startOfWeek + 7) % 7) + 1;
}
/**
* @private
*/
export function gregorianToWeek(gregObj, minDaysInFirstWeek = 4, startOfWeek = 1) {
const { year, month, day } = gregObj,
ordinal = computeOrdinal(year, month, day),
weekday = isoWeekdayToLocal(dayOfWeek(year, month, day), startOfWeek);
let weekNumber = Math.floor((ordinal - weekday + 14 - minDaysInFirstWeek) / 7),
weekYear;
if (weekNumber < 1) {
weekYear = year - 1;
weekNumber = weeksInWeekYear(weekYear, minDaysInFirstWeek, startOfWeek);
} else if (weekNumber > weeksInWeekYear(year, minDaysInFirstWeek, startOfWeek)) {
weekYear = year + 1;
weekNumber = 1;
} else {
weekYear = year;
}
return { weekYear, weekNumber, weekday, ...timeObject(gregObj) };
}
export function weekToGregorian(weekData, minDaysInFirstWeek = 4, startOfWeek = 1) {
const { weekYear, weekNumber, weekday } = weekData,
weekdayOfJan4 = isoWeekdayToLocal(dayOfWeek(weekYear, 1, minDaysInFirstWeek), startOfWeek),
yearInDays = daysInYear(weekYear);
let ordinal = weekNumber * 7 + weekday - weekdayOfJan4 - 7 + minDaysInFirstWeek,
year;
if (ordinal < 1) {
year = weekYear - 1;
ordinal += daysInYear(year);
} else if (ordinal > yearInDays) {
year = weekYear + 1;
ordinal -= daysInYear(weekYear);
} else {
year = weekYear;
}
const { month, day } = uncomputeOrdinal(year, ordinal);
return { year, month, day, ...timeObject(weekData) };
}
export function gregorianToOrdinal(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;
const { month, day } = uncomputeOrdinal(year, ordinal);
return { year, month, day, ...timeObject(ordinalData) };
}
/**
* Check if local week units like localWeekday are used in obj.
* If so, validates that they are not mixed with ISO week units and then copies them to the normal week unit properties.
* Modifies obj in-place!
* @param obj the object values
*/
export function usesLocalWeekValues(obj, loc) {
const hasLocaleWeekData =
!isUndefined(obj.localWeekday) ||
!isUndefined(obj.localWeekNumber) ||
!isUndefined(obj.localWeekYear);
if (hasLocaleWeekData) {
const hasIsoWeekData =
!isUndefined(obj.weekday) || !isUndefined(obj.weekNumber) || !isUndefined(obj.weekYear);
if (hasIsoWeekData) {
throw new ConflictingSpecificationError(
"Cannot mix locale-based week fields with ISO-based week fields"
);
}
if (!isUndefined(obj.localWeekday)) obj.weekday = obj.localWeekday;
if (!isUndefined(obj.localWeekNumber)) obj.weekNumber = obj.localWeekNumber;
if (!isUndefined(obj.localWeekYear)) obj.weekYear = obj.localWeekYear;
delete obj.localWeekday;
delete obj.localWeekNumber;
delete obj.localWeekYear;
return {
minDaysInFirstWeek: loc.getMinDaysInFirstWeek(),
startOfWeek: loc.getStartOfWeek(),
};
} else {
return { minDaysInFirstWeek: 4, startOfWeek: 1 };
}
}
export function hasInvalidWeekData(obj, minDaysInFirstWeek = 4, startOfWeek = 1) {
const validYear = isInteger(obj.weekYear),
validWeek = integerBetween(
obj.weekNumber,
1,
weeksInWeekYear(obj.weekYear, minDaysInFirstWeek, startOfWeek)
),
validWeekday = integerBetween(obj.weekday, 1, 7);
if (!validYear) {
return unitOutOfRange("weekYear", obj.weekYear);
} else if (!validWeek) {
return unitOutOfRange("week", obj.weekNumber);
} else if (!validWeekday) {
return unitOutOfRange("weekday", obj.weekday);
} else return false;
}
export function hasInvalidOrdinalData(obj) {
const validYear = isInteger(obj.year),
validOrdinal = integerBetween(obj.ordinal, 1, daysInYear(obj.year));
if (!validYear) {
return unitOutOfRange("year", obj.year);
} else if (!validOrdinal) {
return unitOutOfRange("ordinal", obj.ordinal);
} else return false;
}
export function hasInvalidGregorianData(obj) {
const validYear = isInteger(obj.year),
validMonth = integerBetween(obj.month, 1, 12),
validDay = integerBetween(obj.day, 1, daysInMonth(obj.year, obj.month));
if (!validYear) {
return unitOutOfRange("year", obj.year);
} else if (!validMonth) {
return unitOutOfRange("month", obj.month);
} else if (!validDay) {
return unitOutOfRange("day", obj.day);
} else return false;
}
export function hasInvalidTimeData(obj) {
const { hour, minute, second, millisecond } = obj;
const validHour =
integerBetween(hour, 0, 23) ||
(hour === 24 && minute === 0 && second === 0 && millisecond === 0),
validMinute = integerBetween(minute, 0, 59),
validSecond = integerBetween(second, 0, 59),
validMillisecond = integerBetween(millisecond, 0, 999);
if (!validHour) {
return unitOutOfRange("hour", hour);
} else if (!validMinute) {
return unitOutOfRange("minute", minute);
} else if (!validSecond) {
return unitOutOfRange("second", second);
} else if (!validMillisecond) {
return unitOutOfRange("millisecond", millisecond);
} else return false;
}

95
priv/vendor/luxon/src/impl/diff.js vendored Normal file
View file

@ -0,0 +1,95 @@
import Duration from "../duration.js";
function dayDiff(earlier, later) {
const utcDayStart = (dt) => dt.toUTC(0, { keepLocalTime: true }).startOf("day").valueOf(),
ms = utcDayStart(later) - utcDayStart(earlier);
return Math.floor(Duration.fromMillis(ms).as("days"));
}
function highOrderDiffs(cursor, later, units) {
const differs = [
["years", (a, b) => b.year - a.year],
["quarters", (a, b) => b.quarter - a.quarter + (b.year - a.year) * 4],
["months", (a, b) => b.month - a.month + (b.year - a.year) * 12],
[
"weeks",
(a, b) => {
const days = dayDiff(a, b);
return (days - (days % 7)) / 7;
},
],
["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) {
if (units.indexOf(unit) >= 0) {
lowestOrder = unit;
results[unit] = differ(cursor, later);
highWater = earlier.plus(results);
if (highWater > later) {
// 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;
}
}
}
return [cursor, results, highWater, lowestOrder];
}
export default function (earlier, later, units, opts) {
let [cursor, results, highWater, lowestOrder] = highOrderDiffs(earlier, later, units);
const remainingMillis = later - cursor;
const lowerOrderUnits = units.filter(
(u) => ["hours", "minutes", "seconds", "milliseconds"].indexOf(u) >= 0
);
if (lowerOrderUnits.length === 0) {
if (highWater < later) {
highWater = cursor.plus({ [lowestOrder]: 1 });
}
if (highWater !== cursor) {
results[lowestOrder] = (results[lowestOrder] || 0) + remainingMillis / (highWater - cursor);
}
}
const duration = Duration.fromObject(results, opts);
if (lowerOrderUnits.length > 0) {
return Duration.fromMillis(remainingMillis, opts)
.shiftTo(...lowerOrderUnits)
.plus(duration);
} else {
return duration;
}
}

75
priv/vendor/luxon/src/impl/digits.js vendored Normal file
View file

@ -0,0 +1,75 @@
const numberingSystems = {
arab: "[\u0660-\u0669]",
arabext: "[\u06F0-\u06F9]",
bali: "[\u1B50-\u1B59]",
beng: "[\u09E6-\u09EF]",
deva: "[\u0966-\u096F]",
fullwide: "[\uFF10-\uFF19]",
gujr: "[\u0AE6-\u0AEF]",
hanidec: "[|一|二|三|四|五|六|七|八|九]",
khmr: "[\u17E0-\u17E9]",
knda: "[\u0CE6-\u0CEF]",
laoo: "[\u0ED0-\u0ED9]",
limb: "[\u1946-\u194F]",
mlym: "[\u0D66-\u0D6F]",
mong: "[\u1810-\u1819]",
mymr: "[\u1040-\u1049]",
orya: "[\u0B66-\u0B6F]",
tamldec: "[\u0BE6-\u0BEF]",
telu: "[\u0C66-\u0C6F]",
thai: "[\u0E50-\u0E59]",
tibt: "[\u0F20-\u0F29]",
latn: "\\d",
};
const numberingSystemsUTF16 = {
arab: [1632, 1641],
arabext: [1776, 1785],
bali: [6992, 7001],
beng: [2534, 2543],
deva: [2406, 2415],
fullwide: [65296, 65303],
gujr: [2790, 2799],
khmr: [6112, 6121],
knda: [3302, 3311],
laoo: [3792, 3801],
limb: [6470, 6479],
mlym: [3430, 3439],
mong: [6160, 6169],
mymr: [4160, 4169],
orya: [2918, 2927],
tamldec: [3046, 3055],
telu: [3174, 3183],
thai: [3664, 3673],
tibt: [3872, 3881],
};
const hanidecChars = numberingSystems.hanidec.replace(/[\[|\]]/g, "").split("");
export function parseDigits(str) {
let value = parseInt(str, 10);
if (isNaN(value)) {
value = "";
for (let i = 0; i < str.length; i++) {
const code = str.charCodeAt(i);
if (str[i].search(numberingSystems.hanidec) !== -1) {
value += hanidecChars.indexOf(str[i]);
} else {
for (const key in numberingSystemsUTF16) {
const [min, max] = numberingSystemsUTF16[key];
if (code >= min && code <= max) {
value += code - min;
}
}
}
}
return parseInt(value, 10);
} else {
return value;
}
}
export function digitRegex({ numberingSystem }, append = "") {
return new RegExp(`${numberingSystems[numberingSystem || "latn"]}${append}`);
}

233
priv/vendor/luxon/src/impl/english.js vendored Normal file
View file

@ -0,0 +1,233 @@
import * as Formats from "./formats.js";
import { pick } from "./util.js";
function stringify(obj) {
return JSON.stringify(obj, Object.keys(obj).sort());
}
/**
* @private
*/
export const monthsLong = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
];
export const monthsShort = [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec",
];
export const monthsNarrow = ["J", "F", "M", "A", "M", "J", "J", "A", "S", "O", "N", "D"];
export function months(length) {
switch (length) {
case "narrow":
return [...monthsNarrow];
case "short":
return [...monthsShort];
case "long":
return [...monthsLong];
case "numeric":
return ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"];
case "2-digit":
return ["01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"];
default:
return null;
}
}
export const weekdaysLong = [
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday",
];
export const weekdaysShort = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
export const weekdaysNarrow = ["M", "T", "W", "T", "F", "S", "S"];
export function weekdays(length) {
switch (length) {
case "narrow":
return [...weekdaysNarrow];
case "short":
return [...weekdaysShort];
case "long":
return [...weekdaysLong];
case "numeric":
return ["1", "2", "3", "4", "5", "6", "7"];
default:
return null;
}
}
export const meridiems = ["AM", "PM"];
export const erasLong = ["Before Christ", "Anno Domini"];
export const erasShort = ["BC", "AD"];
export const erasNarrow = ["B", "A"];
export function eras(length) {
switch (length) {
case "narrow":
return [...erasNarrow];
case "short":
return [...erasShort];
case "long":
return [...erasLong];
default:
return null;
}
}
export function meridiemForDateTime(dt) {
return meridiems[dt.hour < 12 ? 0 : 1];
}
export function weekdayForDateTime(dt, length) {
return weekdays(length)[dt.weekday - 1];
}
export function monthForDateTime(dt, length) {
return months(length)[dt.month - 1];
}
export function eraForDateTime(dt, length) {
return eras(length)[dt.year < 0 ? 0 : 1];
}
export function formatRelativeTime(unit, count, numeric = "always", narrow = false) {
const units = {
years: ["year", "yr."],
quarters: ["quarter", "qtr."],
months: ["month", "mo."],
weeks: ["week", "wk."],
days: ["day", "day", "days"],
hours: ["hour", "hr."],
minutes: ["minute", "min."],
seconds: ["second", "sec."],
};
const lastable = ["hours", "minutes", "seconds"].indexOf(unit) === -1;
if (numeric === "auto" && lastable) {
const isDay = unit === "days";
switch (count) {
case 1:
return isDay ? "tomorrow" : `next ${units[unit][0]}`;
case -1:
return isDay ? "yesterday" : `last ${units[unit][0]}`;
case 0:
return isDay ? "today" : `this ${units[unit][0]}`;
default: // fall through
}
}
const isInPast = Object.is(count, -0) || count < 0,
fmtValue = Math.abs(count),
singular = fmtValue === 1,
lilUnits = units[unit],
fmtUnit = narrow
? singular
? lilUnits[1]
: lilUnits[2] || lilUnits[1]
: singular
? units[unit][0]
: unit;
return isInPast ? `${fmtValue} ${fmtUnit} ago` : `in ${fmtValue} ${fmtUnit}`;
}
export function formatString(knownFormat) {
// these all have the offsets removed because we don't have access to them
// without all the intl stuff this is backfilling
const filtered = pick(knownFormat, [
"weekday",
"era",
"year",
"month",
"day",
"hour",
"minute",
"second",
"timeZoneName",
"hourCycle",
]),
key = stringify(filtered),
dateTimeHuge = "EEEE, LLLL d, yyyy, h:mm a";
switch (key) {
case stringify(Formats.DATE_SHORT):
return "M/d/yyyy";
case stringify(Formats.DATE_MED):
return "LLL d, yyyy";
case stringify(Formats.DATE_MED_WITH_WEEKDAY):
return "EEE, LLL d, yyyy";
case stringify(Formats.DATE_FULL):
return "LLLL d, yyyy";
case stringify(Formats.DATE_HUGE):
return "EEEE, LLLL d, yyyy";
case stringify(Formats.TIME_SIMPLE):
return "h:mm a";
case stringify(Formats.TIME_WITH_SECONDS):
return "h:mm:ss a";
case stringify(Formats.TIME_WITH_SHORT_OFFSET):
return "h:mm a";
case stringify(Formats.TIME_WITH_LONG_OFFSET):
return "h:mm a";
case stringify(Formats.TIME_24_SIMPLE):
return "HH:mm";
case stringify(Formats.TIME_24_WITH_SECONDS):
return "HH:mm:ss";
case stringify(Formats.TIME_24_WITH_SHORT_OFFSET):
return "HH:mm";
case stringify(Formats.TIME_24_WITH_LONG_OFFSET):
return "HH:mm";
case stringify(Formats.DATETIME_SHORT):
return "M/d/yyyy, h:mm a";
case stringify(Formats.DATETIME_MED):
return "LLL d, yyyy, h:mm a";
case stringify(Formats.DATETIME_FULL):
return "LLLL d, yyyy, h:mm a";
case stringify(Formats.DATETIME_HUGE):
return dateTimeHuge;
case stringify(Formats.DATETIME_SHORT_WITH_SECONDS):
return "M/d/yyyy, h:mm:ss a";
case stringify(Formats.DATETIME_MED_WITH_SECONDS):
return "LLL d, yyyy, h:mm:ss a";
case stringify(Formats.DATETIME_MED_WITH_WEEKDAY):
return "EEE, d LLL yyyy, h:mm a";
case stringify(Formats.DATETIME_FULL_WITH_SECONDS):
return "LLLL d, yyyy, h:mm:ss a";
case stringify(Formats.DATETIME_HUGE_WITH_SECONDS):
return "EEEE, LLLL d, yyyy, h:mm:ss a";
default:
return dateTimeHuge;
}
}

176
priv/vendor/luxon/src/impl/formats.js vendored Normal file
View file

@ -0,0 +1,176 @@
/**
* @private
*/
const n = "numeric",
s = "short",
l = "long";
export const DATE_SHORT = {
year: n,
month: n,
day: n,
};
export const DATE_MED = {
year: n,
month: s,
day: n,
};
export const DATE_MED_WITH_WEEKDAY = {
year: n,
month: s,
day: n,
weekday: s,
};
export const DATE_FULL = {
year: n,
month: l,
day: n,
};
export const DATE_HUGE = {
year: n,
month: l,
day: n,
weekday: l,
};
export const TIME_SIMPLE = {
hour: n,
minute: n,
};
export const TIME_WITH_SECONDS = {
hour: n,
minute: n,
second: n,
};
export const TIME_WITH_SHORT_OFFSET = {
hour: n,
minute: n,
second: n,
timeZoneName: s,
};
export const TIME_WITH_LONG_OFFSET = {
hour: n,
minute: n,
second: n,
timeZoneName: l,
};
export const TIME_24_SIMPLE = {
hour: n,
minute: n,
hourCycle: "h23",
};
export const TIME_24_WITH_SECONDS = {
hour: n,
minute: n,
second: n,
hourCycle: "h23",
};
export const TIME_24_WITH_SHORT_OFFSET = {
hour: n,
minute: n,
second: n,
hourCycle: "h23",
timeZoneName: s,
};
export const TIME_24_WITH_LONG_OFFSET = {
hour: n,
minute: n,
second: n,
hourCycle: "h23",
timeZoneName: l,
};
export const DATETIME_SHORT = {
year: n,
month: n,
day: n,
hour: n,
minute: n,
};
export const DATETIME_SHORT_WITH_SECONDS = {
year: n,
month: n,
day: n,
hour: n,
minute: n,
second: n,
};
export const DATETIME_MED = {
year: n,
month: s,
day: n,
hour: n,
minute: n,
};
export const DATETIME_MED_WITH_SECONDS = {
year: n,
month: s,
day: n,
hour: n,
minute: n,
second: n,
};
export const DATETIME_MED_WITH_WEEKDAY = {
year: n,
month: s,
day: n,
weekday: s,
hour: n,
minute: n,
};
export const DATETIME_FULL = {
year: n,
month: l,
day: n,
hour: n,
minute: n,
timeZoneName: s,
};
export const DATETIME_FULL_WITH_SECONDS = {
year: n,
month: l,
day: n,
hour: n,
minute: n,
second: n,
timeZoneName: s,
};
export const DATETIME_HUGE = {
year: n,
month: l,
day: n,
weekday: l,
hour: n,
minute: n,
timeZoneName: l,
};
export const DATETIME_HUGE_WITH_SECONDS = {
year: n,
month: l,
day: n,
weekday: l,
hour: n,
minute: n,
second: n,
timeZoneName: l,
};

409
priv/vendor/luxon/src/impl/formatter.js vendored Normal file
View file

@ -0,0 +1,409 @@
import * as English from "./english.js";
import * as Formats from "./formats.js";
import { padStart } from "./util.js";
function stringifyTokens(splits, tokenToString) {
let s = "";
for (const token of splits) {
if (token.literal) {
s += token.val;
} else {
s += tokenToString(token.val);
}
}
return s;
}
const macroTokenToFormatOpts = {
D: Formats.DATE_SHORT,
DD: Formats.DATE_MED,
DDD: Formats.DATE_FULL,
DDDD: Formats.DATE_HUGE,
t: Formats.TIME_SIMPLE,
tt: Formats.TIME_WITH_SECONDS,
ttt: Formats.TIME_WITH_SHORT_OFFSET,
tttt: Formats.TIME_WITH_LONG_OFFSET,
T: Formats.TIME_24_SIMPLE,
TT: Formats.TIME_24_WITH_SECONDS,
TTT: Formats.TIME_24_WITH_SHORT_OFFSET,
TTTT: Formats.TIME_24_WITH_LONG_OFFSET,
f: Formats.DATETIME_SHORT,
ff: Formats.DATETIME_MED,
fff: Formats.DATETIME_FULL,
ffff: Formats.DATETIME_HUGE,
F: Formats.DATETIME_SHORT_WITH_SECONDS,
FF: Formats.DATETIME_MED_WITH_SECONDS,
FFF: Formats.DATETIME_FULL_WITH_SECONDS,
FFFF: Formats.DATETIME_HUGE_WITH_SECONDS,
};
/**
* @private
*/
export default class Formatter {
static create(locale, opts = {}) {
return new Formatter(locale, opts);
}
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,
currentFull = "",
bracketed = false;
const splits = [];
for (let i = 0; i < fmt.length; i++) {
const c = fmt.charAt(i);
if (c === "'") {
if (currentFull.length > 0) {
splits.push({ literal: bracketed || /^\s+$/.test(currentFull), val: currentFull });
}
current = null;
currentFull = "";
bracketed = !bracketed;
} else if (bracketed) {
currentFull += c;
} else if (c === current) {
currentFull += c;
} else {
if (currentFull.length > 0) {
splits.push({ literal: /^\s+$/.test(currentFull), val: currentFull });
}
currentFull = c;
current = c;
}
}
if (currentFull.length > 0) {
splits.push({ literal: bracketed || /^\s+$/.test(currentFull), val: currentFull });
}
return splits;
}
static macroTokenToFormatOpts(token) {
return macroTokenToFormatOpts[token];
}
constructor(locale, formatOpts) {
this.opts = formatOpts;
this.loc = locale;
this.systemLoc = null;
}
formatWithSystemDefault(dt, opts) {
if (this.systemLoc === null) {
this.systemLoc = this.loc.redefaultToSystem();
}
const df = this.systemLoc.dtFormatter(dt, { ...this.opts, ...opts });
return df.format();
}
dtFormatter(dt, opts = {}) {
return this.loc.dtFormatter(dt, { ...this.opts, ...opts });
}
formatDateTime(dt, opts) {
return this.dtFormatter(dt, opts).format();
}
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) {
// we get some perf out of doing this here, annoyingly
if (this.opts.forceSimple) {
return padStart(n, p);
}
const opts = { ...this.opts };
if (p > 0) {
opts.padTo = p;
}
return this.loc.numberFormatter(opts).format(n);
}
formatDateTimeFromString(dt, fmt) {
const knownEnglish = this.loc.listingMode() === "en",
useDateTimeFormatter = this.loc.outputCalendar && this.loc.outputCalendar !== "gregory",
string = (opts, extract) => this.loc.extract(dt, opts, extract),
formatOffset = (opts) => {
if (dt.isOffsetFixed && dt.offset === 0 && opts.allowZ) {
return "Z";
}
return dt.isValid ? dt.zone.formatOffset(dt.ts, opts.format) : "";
},
meridiem = () =>
knownEnglish
? English.meridiemForDateTime(dt)
: string({ hour: "numeric", hourCycle: "h12" }, "dayperiod"),
month = (length, standalone) =>
knownEnglish
? English.monthForDateTime(dt, length)
: string(standalone ? { month: length } : { month: length, day: "numeric" }, "month"),
weekday = (length, standalone) =>
knownEnglish
? English.weekdayForDateTime(dt, length)
: string(
standalone ? { weekday: length } : { weekday: length, month: "long", day: "numeric" },
"weekday"
),
maybeMacro = (token) => {
const formatOpts = Formatter.macroTokenToFormatOpts(token);
if (formatOpts) {
return this.formatWithSystemDefault(dt, formatOpts);
} else {
return token;
}
},
era = (length) =>
knownEnglish ? English.eraForDateTime(dt, length) : string({ era: length }, "era"),
tokenToString = (token) => {
// Where possible: https://cldr.unicode.org/translation/date-time/date-time-symbols
switch (token) {
// ms
case "S":
return this.num(dt.millisecond);
case "u":
// falls through
case "SSS":
return this.num(dt.millisecond, 3);
// seconds
case "s":
return this.num(dt.second);
case "ss":
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
case "m":
return this.num(dt.minute);
case "mm":
return this.num(dt.minute, 2);
// hours
case "h":
return this.num(dt.hour % 12 === 0 ? 12 : dt.hour % 12);
case "hh":
return this.num(dt.hour % 12 === 0 ? 12 : dt.hour % 12, 2);
case "H":
return this.num(dt.hour);
case "HH":
return this.num(dt.hour, 2);
// offset
case "Z":
// like +6
return formatOffset({ format: "narrow", allowZ: this.opts.allowZ });
case "ZZ":
// like +06:00
return formatOffset({ format: "short", allowZ: this.opts.allowZ });
case "ZZZ":
// like +0600
return formatOffset({ format: "techie", allowZ: this.opts.allowZ });
case "ZZZZ":
// like EST
return dt.zone.offsetName(dt.ts, { format: "short", locale: this.loc.locale });
case "ZZZZZ":
// like Eastern Standard Time
return dt.zone.offsetName(dt.ts, { format: "long", locale: this.loc.locale });
// zone
case "z":
// like America/New_York
return dt.zoneName;
// meridiems
case "a":
return meridiem();
// dates
case "d":
return useDateTimeFormatter ? string({ day: "numeric" }, "day") : this.num(dt.day);
case "dd":
return useDateTimeFormatter ? string({ day: "2-digit" }, "day") : this.num(dt.day, 2);
// weekdays - standalone
case "c":
// like 1
return this.num(dt.weekday);
case "ccc":
// like 'Tues'
return weekday("short", true);
case "cccc":
// like 'Tuesday'
return weekday("long", true);
case "ccccc":
// like 'T'
return weekday("narrow", true);
// weekdays - format
case "E":
// like 1
return this.num(dt.weekday);
case "EEE":
// like 'Tues'
return weekday("short", false);
case "EEEE":
// like 'Tuesday'
return weekday("long", false);
case "EEEEE":
// like 'T'
return weekday("narrow", false);
// months - standalone
case "L":
// like 1
return useDateTimeFormatter
? string({ month: "numeric", day: "numeric" }, "month")
: this.num(dt.month);
case "LL":
// like 01, doesn't seem to work
return useDateTimeFormatter
? string({ month: "2-digit", day: "numeric" }, "month")
: this.num(dt.month, 2);
case "LLL":
// like Jan
return month("short", true);
case "LLLL":
// like January
return month("long", true);
case "LLLLL":
// like J
return month("narrow", true);
// months - format
case "M":
// like 1
return useDateTimeFormatter
? string({ month: "numeric" }, "month")
: this.num(dt.month);
case "MM":
// like 01
return useDateTimeFormatter
? string({ month: "2-digit" }, "month")
: this.num(dt.month, 2);
case "MMM":
// like Jan
return month("short", false);
case "MMMM":
// like January
return month("long", false);
case "MMMMM":
// like J
return month("narrow", false);
// years
case "y":
// like 2014
return useDateTimeFormatter ? string({ year: "numeric" }, "year") : this.num(dt.year);
case "yy":
// like 14
return useDateTimeFormatter
? string({ year: "2-digit" }, "year")
: this.num(dt.year.toString().slice(-2), 2);
case "yyyy":
// like 0012
return useDateTimeFormatter
? string({ year: "numeric" }, "year")
: this.num(dt.year, 4);
case "yyyyyy":
// like 000012
return useDateTimeFormatter
? string({ year: "numeric" }, "year")
: this.num(dt.year, 6);
// eras
case "G":
// like AD
return era("short");
case "GG":
// like Anno Domini
return era("long");
case "GGGGG":
return era("narrow");
case "kk":
return this.num(dt.weekYear.toString().slice(-2), 2);
case "kkkk":
return this.num(dt.weekYear, 4);
case "W":
return this.num(dt.weekNumber);
case "WW":
return this.num(dt.weekNumber, 2);
case "n":
return this.num(dt.localWeekNumber);
case "nn":
return this.num(dt.localWeekNumber, 2);
case "ii":
return this.num(dt.localWeekYear.toString().slice(-2), 2);
case "iiii":
return this.num(dt.localWeekYear, 4);
case "o":
return this.num(dt.ordinal);
case "ooo":
return this.num(dt.ordinal, 3);
case "q":
// like 1
return this.num(dt.quarter);
case "qq":
// like 01
return this.num(dt.quarter, 2);
case "X":
return this.num(Math.floor(dt.ts / 1000));
case "x":
return this.num(dt.ts);
default:
return maybeMacro(token);
}
};
return stringifyTokens(Formatter.parseFormat(fmt), tokenToString);
}
formatDurationFromString(dur, fmt) {
const tokenToField = (token) => {
switch (token[0]) {
case "S":
return "millisecond";
case "s":
return "second";
case "m":
return "minute";
case "h":
return "hour";
case "d":
return "day";
case "w":
return "week";
case "M":
return "month";
case "y":
return "year";
default:
return null;
}
},
tokenToString = (lildur) => (token) => {
const mapped = tokenToField(token);
if (mapped) {
return this.num(lildur.get(mapped), token.length);
} else {
return token;
}
},
tokens = Formatter.parseFormat(fmt),
realTokens = tokens.reduce(
(found, { literal, val }) => (literal ? found : found.concat(val)),
[]
),
collapsed = dur.shiftTo(...realTokens.map(tokenToField).filter((t) => t));
return stringifyTokens(tokens, tokenToString(collapsed));
}
}

14
priv/vendor/luxon/src/impl/invalid.js vendored Normal file
View file

@ -0,0 +1,14 @@
export default class Invalid {
constructor(reason, explanation) {
this.reason = reason;
this.explanation = explanation;
}
toMessage() {
if (this.explanation) {
return `${this.reason}: ${this.explanation}`;
} else {
return this.reason;
}
}
}

542
priv/vendor/luxon/src/impl/locale.js vendored Normal file
View file

@ -0,0 +1,542 @@
import { hasLocaleWeekInfo, hasRelative, padStart, roundTo, validateWeekSettings } from "./util.js";
import * as English from "./english.js";
import Settings from "../settings.js";
import DateTime from "../datetime.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 = {};
function getCachedDTF(locString, opts = {}) {
const key = JSON.stringify([locString, opts]);
let dtf = intlDTCache[key];
if (!dtf) {
dtf = new Intl.DateTimeFormat(locString, opts);
intlDTCache[key] = dtf;
}
return dtf;
}
let intlNumCache = {};
function getCachedINF(locString, opts = {}) {
const key = JSON.stringify([locString, opts]);
let inf = intlNumCache[key];
if (!inf) {
inf = new Intl.NumberFormat(locString, opts);
intlNumCache[key] = inf;
}
return inf;
}
let intlRelCache = {};
function getCachedRTF(locString, opts = {}) {
const { base, ...cacheKeyOpts } = opts; // exclude `base` from the options
const key = JSON.stringify([locString, cacheKeyOpts]);
let inf = intlRelCache[key];
if (!inf) {
inf = new Intl.RelativeTimeFormat(locString, opts);
intlRelCache[key] = inf;
}
return inf;
}
let sysLocaleCache = null;
function systemLocale() {
if (sysLocaleCache) {
return sysLocaleCache;
} else {
sysLocaleCache = new Intl.DateTimeFormat().resolvedOptions().locale;
return sysLocaleCache;
}
}
let weekInfoCache = {};
function getCachedWeekInfo(locString) {
let data = weekInfoCache[locString];
if (!data) {
const locale = new Intl.Locale(locString);
// browsers currently implement this as a property, but spec says it should be a getter function
data = "getWeekInfo" in locale ? locale.getWeekInfo() : locale.weekInfo;
weekInfoCache[locString] = data;
}
return data;
}
function parseLocaleString(localeStr) {
// I really want to avoid writing a BCP 47 parser
// see, e.g. https://github.com/wooorm/bcp-47
// Instead, we'll do this:
// a) if the string has no -u extensions, just leave it alone
// b) if it does, use Intl to resolve everything
// c) if Intl fails, try again without the -u
// 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-");
if (uIndex === -1) {
return [localeStr];
} else {
let options;
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 [selectedStr, numberingSystem, calendar];
}
}
function intlConfigString(localeStr, numberingSystem, outputCalendar) {
if (outputCalendar || numberingSystem) {
if (!localeStr.includes("-u-")) {
localeStr += "-u";
}
if (outputCalendar) {
localeStr += `-ca-${outputCalendar}`;
}
if (numberingSystem) {
localeStr += `-nu-${numberingSystem}`;
}
return localeStr;
} else {
return localeStr;
}
}
function mapMonths(f) {
const ms = [];
for (let i = 1; i <= 12; i++) {
const dt = DateTime.utc(2009, i, 1);
ms.push(f(dt));
}
return ms;
}
function mapWeekdays(f) {
const ms = [];
for (let i = 1; i <= 7; i++) {
const dt = DateTime.utc(2016, 11, 13 + i);
ms.push(f(dt));
}
return ms;
}
function listStuff(loc, length, englishFn, intlFn) {
const mode = loc.listingMode();
if (mode === "error") {
return null;
} else if (mode === "en") {
return englishFn(length);
} else {
return intlFn(length);
}
}
function supportsFastNumbers(loc) {
if (loc.numberingSystem && loc.numberingSystem !== "latn") {
return false;
} else {
return (
loc.numberingSystem === "latn" ||
!loc.locale ||
loc.locale.startsWith("en") ||
new Intl.DateTimeFormat(loc.intl).resolvedOptions().numberingSystem === "latn"
);
}
}
/**
* @private
*/
class PolyNumberFormatter {
constructor(intl, forceSimple, opts) {
this.padTo = opts.padTo || 0;
this.floor = opts.floor || 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;
this.inf = getCachedINF(intl, intlOpts);
}
}
format(i) {
if (this.inf) {
const fixed = this.floor ? Math.floor(i) : i;
return this.inf.format(fixed);
} else {
// to match the browser's numberformatter defaults
const fixed = this.floor ? Math.floor(i) : roundTo(i, 3);
return padStart(fixed, this.padTo);
}
}
}
/**
* @private
*/
class PolyDateFormatter {
constructor(dt, intl, opts) {
this.opts = opts;
this.originalZone = undefined;
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.
// That is why fixed-offset TZ is set to that unless it is:
// 1. Representing offset 0 when UTC is used to maintain previous behavior and does not become GMT.
// 2. Unsupported by the browser:
// - some do not support Etc/
// - < Etc/GMT-14, > Etc/GMT+12, and 30-minute or 45-minute offsets are not part of tzdata
const gmtOffset = -1 * (dt.offset / 60);
const offsetZ = gmtOffset >= 0 ? `Etc/GMT+${gmtOffset}` : `Etc/GMT${gmtOffset}`;
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 manually apply the offset and substitute the zone as needed.
z = "UTC";
this.dt = dt.offset === 0 ? dt : dt.setZone("UTC").plus({ minutes: dt.offset });
this.originalZone = dt.zone;
}
} else if (dt.zone.type === "system") {
this.dt = dt;
} 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;
}
const intlOpts = { ...this.opts };
intlOpts.timeZone = intlOpts.timeZone || z;
this.dtf = getCachedDTF(intl, intlOpts);
}
format() {
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() {
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() {
return this.dtf.resolvedOptions();
}
}
/**
* @private
*/
class PolyRelFormatter {
constructor(intl, isEnglish, opts) {
this.opts = { style: "long", ...opts };
if (!isEnglish && hasRelative()) {
this.rtf = getCachedRTF(intl, opts);
}
}
format(count, unit) {
if (this.rtf) {
return this.rtf.format(count, unit);
} else {
return English.formatRelativeTime(unit, count, this.opts.numeric, this.opts.style !== "long");
}
}
formatToParts(count, unit) {
if (this.rtf) {
return this.rtf.formatToParts(count, unit);
} else {
return [];
}
}
}
const fallbackWeekSettings = {
firstDay: 1,
minimalDays: 4,
weekend: [6, 7],
};
/**
* @private
*/
export default class Locale {
static fromOpts(opts) {
return Locale.create(
opts.locale,
opts.numberingSystem,
opts.outputCalendar,
opts.weekSettings,
opts.defaultToEN
);
}
static create(locale, numberingSystem, outputCalendar, weekSettings, defaultToEN = false) {
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;
const weekSettingsR = validateWeekSettings(weekSettings) || Settings.defaultWeekSettings;
return new Locale(localeR, numberingSystemR, outputCalendarR, weekSettingsR, specifiedLocale);
}
static resetCache() {
sysLocaleCache = null;
intlDTCache = {};
intlNumCache = {};
intlRelCache = {};
}
static fromObject({ locale, numberingSystem, outputCalendar, weekSettings } = {}) {
return Locale.create(locale, numberingSystem, outputCalendar, weekSettings);
}
constructor(locale, numbering, outputCalendar, weekSettings, specifiedLocale) {
const [parsedLocale, parsedNumberingSystem, parsedOutputCalendar] = parseLocaleString(locale);
this.locale = parsedLocale;
this.numberingSystem = numbering || parsedNumberingSystem || null;
this.outputCalendar = outputCalendar || parsedOutputCalendar || null;
this.weekSettings = weekSettings;
this.intl = intlConfigString(this.locale, this.numberingSystem, this.outputCalendar);
this.weekdaysCache = { format: {}, standalone: {} };
this.monthsCache = { format: {}, standalone: {} };
this.meridiemCache = null;
this.eraCache = {};
this.specifiedLocale = specifiedLocale;
this.fastNumbersCached = null;
}
get fastNumbers() {
if (this.fastNumbersCached == null) {
this.fastNumbersCached = supportsFastNumbers(this);
}
return this.fastNumbersCached;
}
listingMode() {
const isActuallyEn = this.isEnglish();
const hasNoWeirdness =
(this.numberingSystem === null || this.numberingSystem === "latn") &&
(this.outputCalendar === null || this.outputCalendar === "gregory");
return isActuallyEn && hasNoWeirdness ? "en" : "intl";
}
clone(alts) {
if (!alts || Object.getOwnPropertyNames(alts).length === 0) {
return this;
} else {
return Locale.create(
alts.locale || this.specifiedLocale,
alts.numberingSystem || this.numberingSystem,
alts.outputCalendar || this.outputCalendar,
validateWeekSettings(alts.weekSettings) || this.weekSettings,
alts.defaultToEN || false
);
}
}
redefaultToEN(alts = {}) {
return this.clone({ ...alts, defaultToEN: true });
}
redefaultToSystem(alts = {}) {
return this.clone({ ...alts, defaultToEN: false });
}
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"));
}
return this.monthsCache[formatStr][length];
});
}
weekdays(length, format = false) {
return listStuff(this, length, English.weekdays, () => {
const intl = format
? { weekday: length, year: "numeric", month: "long", day: "numeric" }
: { weekday: length },
formatStr = format ? "format" : "standalone";
if (!this.weekdaysCache[formatStr][length]) {
this.weekdaysCache[formatStr][length] = mapWeekdays((dt) =>
this.extract(dt, intl, "weekday")
);
}
return this.weekdaysCache[formatStr][length];
});
}
meridiems() {
return listStuff(
this,
undefined,
() => English.meridiems,
() => {
// In theory there could be aribitrary day periods. We're gonna assume there are exactly two
// for AM and PM. This is probably wrong, but it's makes parsing way easier.
if (!this.meridiemCache) {
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")
);
}
return this.meridiemCache;
}
);
}
eras(length) {
return listStuff(this, length, English.eras, () => {
const intl = { era: length };
// This is problematic. Different calendars are going to define eras totally differently. What I need is the minimum set of dates
// to definitely enumerate them.
if (!this.eraCache[length]) {
this.eraCache[length] = [DateTime.utc(-40, 1, 1), DateTime.utc(2017, 1, 1)].map((dt) =>
this.extract(dt, intl, "era")
);
}
return this.eraCache[length];
});
}
extract(dt, intlOpts, field) {
const df = this.dtFormatter(dt, intlOpts),
results = df.formatToParts(),
matching = results.find((m) => m.type.toLowerCase() === field);
return matching ? matching.value : null;
}
numberFormatter(opts = {}) {
// this forcesimple option is never used (the only caller short-circuits on it, but it seems safer to leave)
// (in contrast, the rest of the condition is used heavily)
return new PolyNumberFormatter(this.intl, opts.forceSimple || this.fastNumbers, opts);
}
dtFormatter(dt, intlOpts = {}) {
return new PolyDateFormatter(dt, this.intl, intlOpts);
}
relFormatter(opts = {}) {
return new PolyRelFormatter(this.intl, this.isEnglish(), opts);
}
listFormatter(opts = {}) {
return getCachedLF(this.intl, opts);
}
isEnglish() {
return (
this.locale === "en" ||
this.locale.toLowerCase() === "en-us" ||
new Intl.DateTimeFormat(this.intl).resolvedOptions().locale.startsWith("en-us")
);
}
getWeekSettings() {
if (this.weekSettings) {
return this.weekSettings;
} else if (!hasLocaleWeekInfo()) {
return fallbackWeekSettings;
} else {
return getCachedWeekInfo(this.locale);
}
}
getStartOfWeek() {
return this.getWeekSettings().firstDay;
}
getMinDaysInFirstWeek() {
return this.getWeekSettings().minimalDays;
}
getWeekendDays() {
return this.getWeekSettings().weekend;
}
equals(other) {
return (
this.locale === other.locale &&
this.numberingSystem === other.numberingSystem &&
this.outputCalendar === other.outputCalendar
);
}
}

View file

@ -0,0 +1,335 @@
import {
untruncateYear,
signedOffset,
parseInteger,
parseMillis,
isUndefined,
parseFloating,
} from "./util.js";
import * as English from "./english.js";
import FixedOffsetZone from "../zones/fixedOffsetZone.js";
import IANAZone from "../zones/IANAZone.js";
/*
* This file handles parsing for well-specified formats. Here's how it works:
* Two things go into parsing: a regex to match with and an extractor to take apart the groups in the match.
* An extractor is just a function that takes a regex match array and returns a { year: ..., month: ... } object
* parse() does the work of executing the regex and applying the extractor. It takes multiple regex/extractor pairs to try in sequence.
* Extractors can take a "cursor" representing the offset in the match to look at. This makes it easy to combine extractors.
* combineExtractors() does the work of combining them, keeping track of the cursor through multiple extractions.
* Some extractions are super dumb and simpleParse and fromStrings help DRY them.
*/
const ianaRegex = /[A-Za-z_+-]{1,256}(?::?\/[A-Za-z0-9_+-]{1,256}(?:\/[A-Za-z0-9_+-]{1,256})?)?/;
function combineRegexes(...regexes) {
const full = regexes.reduce((f, r) => f + r.source, "");
return RegExp(`^${full}$`);
}
function combineExtractors(...extractors) {
return (m) =>
extractors
.reduce(
([mergedVals, mergedZone, cursor], ex) => {
const [val, zone, next] = ex(m, cursor);
return [{ ...mergedVals, ...val }, zone || mergedZone, next];
},
[{}, null, 1]
)
.slice(0, 2);
}
function parse(s, ...patterns) {
if (s == null) {
return [null, null];
}
for (const [regex, extractor] of patterns) {
const m = regex.exec(s);
if (m) {
return extractor(m);
}
}
return [null, null];
}
function simpleParse(...keys) {
return (match, cursor) => {
const ret = {};
let i;
for (i = 0; i < keys.length; i++) {
ret[keys[i]] = parseInteger(match[cursor + i]);
}
return [ret, null, cursor + i];
};
}
// ISO and SQL parsing
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})?`);
function int(match, pos, fallback) {
const m = match[pos];
return isUndefined(m) ? fallback : parseInteger(m);
}
function extractISOYmd(match, cursor) {
const item = {
year: int(match, cursor),
month: int(match, cursor + 1, 1),
day: int(match, cursor + 2, 1),
};
return [item, null, cursor + 3];
}
function extractISOTime(match, cursor) {
const item = {
hours: int(match, cursor, 0),
minutes: int(match, cursor + 1, 0),
seconds: int(match, cursor + 2, 0),
milliseconds: parseMillis(match[cursor + 3]),
};
return [item, null, cursor + 4];
}
function extractISOOffset(match, cursor) {
const local = !match[cursor] && !match[cursor + 1],
fullOffset = signedOffset(match[cursor + 1], match[cursor + 2]),
zone = local ? null : FixedOffsetZone.instance(fullOffset);
return [{}, zone, cursor + 3];
}
function extractIANAZone(match, cursor) {
const zone = match[cursor] ? IANAZone.create(match[cursor]) : null;
return [{}, zone, cursor + 1];
}
// ISO time parsing
const isoTimeOnly = RegExp(`^T?${isoTimeBaseRegex.source}$`);
// ISO duration parsing
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 hasNegativePrefix = s[0] === "-";
const negativeSeconds = secondStr && secondStr[0] === "-";
const maybeNegate = (num, force = false) =>
num !== undefined && (force || (num && hasNegativePrefix)) ? -num : num;
return [
{
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),
},
];
}
// These are a little braindead. EDT *should* tell us that we're in, say, America/New_York
// and not just that we're in -240 *right now*. But since I don't think these are used that often
// I'm just going to ignore that
const obsOffsets = {
GMT: 0,
EDT: -4 * 60,
EST: -5 * 60,
CDT: -5 * 60,
CST: -6 * 60,
MDT: -6 * 60,
MST: -7 * 60,
PDT: -7 * 60,
PST: -8 * 60,
};
function fromStrings(weekdayStr, yearStr, monthStr, dayStr, hourStr, minuteStr, secondStr) {
const result = {
year: yearStr.length === 2 ? untruncateYear(parseInteger(yearStr)) : parseInteger(yearStr),
month: English.monthsShort.indexOf(monthStr) + 1,
day: parseInteger(dayStr),
hour: parseInteger(hourStr),
minute: parseInteger(minuteStr),
};
if (secondStr) result.second = parseInteger(secondStr);
if (weekdayStr) {
result.weekday =
weekdayStr.length > 3
? English.weekdaysLong.indexOf(weekdayStr) + 1
: English.weekdaysShort.indexOf(weekdayStr) + 1;
}
return result;
}
// 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)))$/;
function extractRFC2822(match) {
const [
,
weekdayStr,
dayStr,
monthStr,
yearStr,
hourStr,
minuteStr,
secondStr,
obsOffset,
milOffset,
offHourStr,
offMinuteStr,
] = match,
result = fromStrings(weekdayStr, yearStr, monthStr, dayStr, hourStr, minuteStr, secondStr);
let offset;
if (obsOffset) {
offset = obsOffsets[obsOffset];
} else if (milOffset) {
offset = 0;
} else {
offset = signedOffset(offHourStr, offMinuteStr);
}
return [result, new FixedOffsetZone(offset)];
}
function preprocessRFC2822(s) {
// Remove comments and folding whitespace and replace multiple-spaces with a single space
return s
.replace(/\([^()]*\)|[\n\t]/g, " ")
.replace(/(\s\s+)/g, " ")
.trim();
}
// http date
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})$/;
function extractRFC1123Or850(match) {
const [, weekdayStr, dayStr, monthStr, yearStr, hourStr, minuteStr, secondStr] = match,
result = fromStrings(weekdayStr, yearStr, monthStr, dayStr, hourStr, minuteStr, secondStr);
return [result, FixedOffsetZone.utcInstance];
}
function extractASCII(match) {
const [, weekdayStr, monthStr, dayStr, hourStr, minuteStr, secondStr, yearStr] = match,
result = fromStrings(weekdayStr, yearStr, monthStr, dayStr, hourStr, minuteStr, secondStr);
return [result, FixedOffsetZone.utcInstance];
}
const isoYmdWithTimeExtensionRegex = combineRegexes(isoYmdRegex, isoTimeExtensionRegex);
const isoWeekWithTimeExtensionRegex = combineRegexes(isoWeekRegex, isoTimeExtensionRegex);
const isoOrdinalWithTimeExtensionRegex = combineRegexes(isoOrdinalRegex, isoTimeExtensionRegex);
const isoTimeCombinedRegex = combineRegexes(isoTimeRegex);
const extractISOYmdTimeAndOffset = combineExtractors(
extractISOYmd,
extractISOTime,
extractISOOffset,
extractIANAZone
);
const extractISOWeekTimeAndOffset = combineExtractors(
extractISOWeekData,
extractISOTime,
extractISOOffset,
extractIANAZone
);
const extractISOOrdinalDateAndTime = combineExtractors(
extractISOOrdinalData,
extractISOTime,
extractISOOffset,
extractIANAZone
);
const extractISOTimeAndOffset = combineExtractors(
extractISOTime,
extractISOOffset,
extractIANAZone
);
/*
* @private
*/
export function parseISODate(s) {
return parse(
s,
[isoYmdWithTimeExtensionRegex, extractISOYmdTimeAndOffset],
[isoWeekWithTimeExtensionRegex, extractISOWeekTimeAndOffset],
[isoOrdinalWithTimeExtensionRegex, extractISOOrdinalDateAndTime],
[isoTimeCombinedRegex, extractISOTimeAndOffset]
);
}
export function parseRFC2822Date(s) {
return parse(preprocessRFC2822(s), [rfc2822, extractRFC2822]);
}
export function parseHTTPDate(s) {
return parse(
s,
[rfc1123, extractRFC1123Or850],
[rfc850, extractRFC1123Or850],
[ascii, extractASCII]
);
}
export function parseISODuration(s) {
return parse(s, [isoDuration, extractISODuration]);
}
const extractISOTimeOnly = combineExtractors(extractISOTime);
export function parseISOTimeOnly(s) {
return parse(s, [isoTimeOnly, extractISOTimeOnly]);
}
const sqlYmdWithTimeExtensionRegex = combineRegexes(sqlYmdRegex, sqlTimeExtensionRegex);
const sqlTimeCombinedRegex = combineRegexes(sqlTimeRegex);
const extractISOTimeOffsetAndIANAZone = combineExtractors(
extractISOTime,
extractISOOffset,
extractIANAZone
);
export function parseSQL(s) {
return parse(
s,
[sqlYmdWithTimeExtensionRegex, extractISOYmdTimeAndOffset],
[sqlTimeCombinedRegex, extractISOTimeOffsetAndIANAZone]
);
}

View file

@ -0,0 +1,473 @@
import { parseMillis, isUndefined, untruncateYear, signedOffset, hasOwnProperty } from "./util.js";
import Formatter from "./formatter.js";
import FixedOffsetZone from "../zones/fixedOffsetZone.js";
import IANAZone from "../zones/IANAZone.js";
import DateTime from "../datetime.js";
import { digitRegex, parseDigits } from "./digits.js";
import { ConflictingSpecificationError } from "../errors.js";
const MISSING_FTP = "missing Intl.DateTimeFormat.formatToParts support";
function intUnit(regex, post = (i) => i) {
return { regex, deser: ([s]) => post(parseDigits(s)) };
}
const NBSP = String.fromCharCode(160);
const spaceOrNBSP = `[ ${NBSP}]`;
const spaceOrNBSPRegExp = new RegExp(spaceOrNBSP, "g");
function fixListRegex(s) {
// make dots optional and also make them literal
// make space and non breakable space characters interchangeable
return s.replace(/\./g, "\\.?").replace(spaceOrNBSPRegExp, spaceOrNBSP);
}
function stripInsensitivities(s) {
return s
.replace(/\./g, "") // ignore dots that were made optional
.replace(spaceOrNBSPRegExp, " ") // interchange space and nbsp
.toLowerCase();
}
function oneOf(strings, startIndex) {
if (strings === null) {
return null;
} else {
return {
regex: RegExp(strings.map(fixListRegex).join("|")),
deser: ([s]) =>
strings.findIndex((i) => stripInsensitivities(s) === stripInsensitivities(i)) + startIndex,
};
}
}
function offset(regex, groups) {
return { regex, deser: ([, h, m]) => signedOffset(h, m), groups };
}
function simple(regex) {
return { regex, deser: ([s]) => s };
}
function escapeToken(value) {
return value.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&");
}
/**
* @param token
* @param {Locale} loc
*/
function unitForToken(token, loc) {
const one = digitRegex(loc),
two = digitRegex(loc, "{2}"),
three = digitRegex(loc, "{3}"),
four = digitRegex(loc, "{4}"),
six = digitRegex(loc, "{6}"),
oneOrTwo = digitRegex(loc, "{1,2}"),
oneToThree = digitRegex(loc, "{1,3}"),
oneToSix = digitRegex(loc, "{1,6}"),
oneToNine = digitRegex(loc, "{1,9}"),
twoToFour = digitRegex(loc, "{2,4}"),
fourToSix = digitRegex(loc, "{4,6}"),
literal = (t) => ({ regex: RegExp(escapeToken(t.val)), deser: ([s]) => s, literal: true }),
unitate = (t) => {
if (token.literal) {
return literal(t);
}
switch (t.val) {
// era
case "G":
return oneOf(loc.eras("short"), 0);
case "GG":
return oneOf(loc.eras("long"), 0);
// years
case "y":
return intUnit(oneToSix);
case "yy":
return intUnit(twoToFour, untruncateYear);
case "yyyy":
return intUnit(four);
case "yyyyy":
return intUnit(fourToSix);
case "yyyyyy":
return intUnit(six);
// months
case "M":
return intUnit(oneOrTwo);
case "MM":
return intUnit(two);
case "MMM":
return oneOf(loc.months("short", true), 1);
case "MMMM":
return oneOf(loc.months("long", true), 1);
case "L":
return intUnit(oneOrTwo);
case "LL":
return intUnit(two);
case "LLL":
return oneOf(loc.months("short", false), 1);
case "LLLL":
return oneOf(loc.months("long", false), 1);
// dates
case "d":
return intUnit(oneOrTwo);
case "dd":
return intUnit(two);
// ordinals
case "o":
return intUnit(oneToThree);
case "ooo":
return intUnit(three);
// time
case "HH":
return intUnit(two);
case "H":
return intUnit(oneOrTwo);
case "hh":
return intUnit(two);
case "h":
return intUnit(oneOrTwo);
case "mm":
return intUnit(two);
case "m":
return intUnit(oneOrTwo);
case "q":
return intUnit(oneOrTwo);
case "qq":
return intUnit(two);
case "s":
return intUnit(oneOrTwo);
case "ss":
return intUnit(two);
case "S":
return intUnit(oneToThree);
case "SSS":
return intUnit(three);
case "u":
return simple(oneToNine);
case "uu":
return simple(oneOrTwo);
case "uuu":
return intUnit(one);
// meridiem
case "a":
return oneOf(loc.meridiems(), 0);
// weekYear (k)
case "kkkk":
return intUnit(four);
case "kk":
return intUnit(twoToFour, untruncateYear);
// weekNumber (W)
case "W":
return intUnit(oneOrTwo);
case "WW":
return intUnit(two);
// weekdays
case "E":
case "c":
return intUnit(one);
case "EEE":
return oneOf(loc.weekdays("short", false), 1);
case "EEEE":
return oneOf(loc.weekdays("long", false), 1);
case "ccc":
return oneOf(loc.weekdays("short", true), 1);
case "cccc":
return oneOf(loc.weekdays("long", true), 1);
// offset/zone
case "Z":
case "ZZ":
return offset(new RegExp(`([+-]${oneOrTwo.source})(?::(${two.source}))?`), 2);
case "ZZZ":
return offset(new RegExp(`([+-]${oneOrTwo.source})(${two.source})?`), 2);
// we don't support ZZZZ (PST) or ZZZZZ (Pacific Standard Time) in parsing
// because we don't have any way to figure out what they are
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:
return literal(t);
}
};
const unit = unitate(token) || {
invalidReason: MISSING_FTP,
};
unit.token = token;
return unit;
}
const partTypeStyleToTokenVal = {
year: {
"2-digit": "yy",
numeric: "yyyyy",
},
month: {
numeric: "M",
"2-digit": "MM",
short: "MMM",
long: "MMMM",
},
day: {
numeric: "d",
"2-digit": "dd",
},
weekday: {
short: "EEE",
long: "EEEE",
},
dayperiod: "a",
dayPeriod: "a",
hour12: {
numeric: "h",
"2-digit": "hh",
},
hour24: {
numeric: "H",
"2-digit": "HH",
},
minute: {
numeric: "m",
"2-digit": "mm",
},
second: {
numeric: "s",
"2-digit": "ss",
},
timeZoneName: {
long: "ZZZZZ",
short: "ZZZ",
},
};
function tokenForPart(part, formatOpts, resolvedOpts) {
const { type, value } = part;
if (type === "literal") {
const isSpace = /^\s+$/.test(value);
return {
literal: !isSpace,
val: isSpace ? " " : value,
};
}
const style = formatOpts[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") {
val = val[style];
}
if (val) {
return {
literal: false,
val,
};
}
return undefined;
}
function buildRegex(units) {
const re = units.map((u) => u.regex).reduce((f, r) => `${f}(${r.source})`, "");
return [`^${re}$`, units];
}
function match(input, regex, handlers) {
const matches = input.match(regex);
if (matches) {
const all = {};
let matchIndex = 1;
for (const i in handlers) {
if (hasOwnProperty(handlers, i)) {
const h = handlers[i],
groups = h.groups ? h.groups + 1 : 1;
if (!h.literal && h.token) {
all[h.token.val[0]] = h.deser(matches.slice(matchIndex, matchIndex + groups));
}
matchIndex += groups;
}
}
return [matches, all];
} else {
return [matches, {}];
}
}
function dateTimeFromMatches(matches) {
const toField = (token) => {
switch (token) {
case "S":
return "millisecond";
case "s":
return "second";
case "m":
return "minute";
case "h":
case "H":
return "hour";
case "d":
return "day";
case "o":
return "ordinal";
case "L":
case "M":
return "month";
case "y":
return "year";
case "E":
case "c":
return "weekday";
case "W":
return "weekNumber";
case "k":
return "weekYear";
case "q":
return "quarter";
default:
return null;
}
};
let zone = null;
let specificOffset;
if (!isUndefined(matches.z)) {
zone = IANAZone.create(matches.z);
}
if (!isUndefined(matches.Z)) {
if (!zone) {
zone = new FixedOffsetZone(matches.Z);
}
specificOffset = matches.Z;
}
if (!isUndefined(matches.q)) {
matches.M = (matches.q - 1) * 3 + 1;
}
if (!isUndefined(matches.h)) {
if (matches.h < 12 && matches.a === 1) {
matches.h += 12;
} else if (matches.h === 12 && matches.a === 0) {
matches.h = 0;
}
}
if (matches.G === 0 && matches.y) {
matches.y = -matches.y;
}
if (!isUndefined(matches.u)) {
matches.S = parseMillis(matches.u);
}
const vals = Object.keys(matches).reduce((r, k) => {
const f = toField(k);
if (f) {
r[f] = matches[k];
}
return r;
}, {});
return [vals, zone, specificOffset];
}
let dummyDateTimeCache = null;
function getDummyDateTime() {
if (!dummyDateTimeCache) {
dummyDateTimeCache = DateTime.fromMillis(1555555555555);
}
return dummyDateTimeCache;
}
function maybeExpandMacroToken(token, locale) {
if (token.literal) {
return token;
}
const formatOpts = Formatter.macroTokenToFormatOpts(token.val);
const tokens = formatOptsToTokens(formatOpts, locale);
if (tokens == null || tokens.includes(undefined)) {
return token;
}
return tokens;
}
export function expandMacroTokens(tokens, locale) {
return Array.prototype.concat(...tokens.map((t) => maybeExpandMacroToken(t, locale)));
}
/**
* @private
*/
export function explainFromTokens(locale, input, format) {
const tokens = expandMacroTokens(Formatter.parseFormat(format), locale),
units = tokens.map((t) => unitForToken(t, locale)),
disqualifyingUnit = units.find((t) => t.invalidReason);
if (disqualifyingUnit) {
return { input, tokens, invalidReason: disqualifyingUnit.invalidReason };
} else {
const [regexString, handlers] = buildRegex(units),
regex = RegExp(regexString, "i"),
[rawMatches, matches] = match(input, regex, handlers),
[result, zone, specificOffset] = matches
? dateTimeFromMatches(matches)
: [null, null, undefined];
if (hasOwnProperty(matches, "a") && hasOwnProperty(matches, "H")) {
throw new ConflictingSpecificationError(
"Can't include meridiem when specifying 24-hour format"
);
}
return { input, tokens, regex, rawMatches, matches, result, zone, specificOffset };
}
}
export function parseFromTokens(locale, input, format) {
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));
}

309
priv/vendor/luxon/src/impl/util.js vendored Normal file
View file

@ -0,0 +1,309 @@
/*
This is just a junk drawer, containing anything used across multiple classes.
Because Luxon is small(ish), this should stay small and we won't worry about splitting
it up into, say, parsingUtil.js and basicUtil.js and so on. But they are divided up by feature area.
*/
import { InvalidArgumentError } from "../errors.js";
import Settings from "../settings.js";
import { dayOfWeek, isoWeekdayToLocal } from "./conversions.js";
/**
* @private
*/
// TYPES
export function isUndefined(o) {
return typeof o === "undefined";
}
export function isNumber(o) {
return typeof o === "number";
}
export function isInteger(o) {
return typeof o === "number" && o % 1 === 0;
}
export function isString(o) {
return typeof o === "string";
}
export function isDate(o) {
return Object.prototype.toString.call(o) === "[object Date]";
}
// CAPABILITIES
export function hasRelative() {
try {
return typeof Intl !== "undefined" && !!Intl.RelativeTimeFormat;
} catch (e) {
return false;
}
}
export function hasLocaleWeekInfo() {
try {
return (
typeof Intl !== "undefined" &&
!!Intl.Locale &&
("weekInfo" in Intl.Locale.prototype || "getWeekInfo" in Intl.Locale.prototype)
);
} catch (e) {
return false;
}
}
// OBJECTS AND ARRAYS
export function maybeArray(thing) {
return Array.isArray(thing) ? thing : [thing];
}
export function bestBy(arr, by, compare) {
if (arr.length === 0) {
return undefined;
}
return arr.reduce((best, next) => {
const pair = [by(next), next];
if (!best) {
return pair;
} else if (compare(best[0], pair[0]) === best[0]) {
return best;
} else {
return pair;
}
}, null)[1];
}
export function pick(obj, keys) {
return keys.reduce((a, k) => {
a[k] = obj[k];
return a;
}, {});
}
export function hasOwnProperty(obj, prop) {
return Object.prototype.hasOwnProperty.call(obj, prop);
}
export function validateWeekSettings(settings) {
if (settings == null) {
return null;
} else if (typeof settings !== "object") {
throw new InvalidArgumentError("Week settings must be an object");
} else {
if (
!integerBetween(settings.firstDay, 1, 7) ||
!integerBetween(settings.minimalDays, 1, 7) ||
!Array.isArray(settings.weekend) ||
settings.weekend.some((v) => !integerBetween(v, 1, 7))
) {
throw new InvalidArgumentError("Invalid week settings");
}
return {
firstDay: settings.firstDay,
minimalDays: settings.minimalDays,
weekend: Array.from(settings.weekend),
};
}
}
// NUMBERS AND STRINGS
export function integerBetween(thing, bottom, top) {
return isInteger(thing) && thing >= bottom && thing <= top;
}
// x % n but takes the sign of n instead of x
export function floorMod(x, n) {
return x - n * Math.floor(x / n);
}
export function padStart(input, n = 2) {
const isNeg = input < 0;
let padded;
if (isNeg) {
padded = "-" + ("" + -input).padStart(n, "0");
} else {
padded = ("" + input).padStart(n, "0");
}
return padded;
}
export function parseInteger(string) {
if (isUndefined(string) || string === null || string === "") {
return undefined;
} else {
return parseInt(string, 10);
}
}
export function parseFloating(string) {
if (isUndefined(string) || string === null || string === "") {
return undefined;
} else {
return parseFloat(string);
}
}
export function parseMillis(fraction) {
// Return undefined (instead of 0) in these cases, where fraction is not set
if (isUndefined(fraction) || fraction === null || fraction === "") {
return undefined;
} else {
const f = parseFloat("0." + fraction) * 1000;
return Math.floor(f);
}
}
export function roundTo(number, digits, towardZero = false) {
const factor = 10 ** digits,
rounder = towardZero ? Math.trunc : Math.round;
return rounder(number * factor) / factor;
}
// DATE BASICS
export function isLeapYear(year) {
return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
}
export function daysInYear(year) {
return isLeapYear(year) ? 366 : 365;
}
export function daysInMonth(year, month) {
const modMonth = floorMod(month - 1, 12) + 1,
modYear = year + (month - modMonth) / 12;
if (modMonth === 2) {
return isLeapYear(modYear) ? 29 : 28;
} else {
return [31, null, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][modMonth - 1];
}
}
// convert a calendar object to a local timestamp (epoch, but with the offset baked in)
export function objToLocalTS(obj) {
let d = Date.UTC(
obj.year,
obj.month - 1,
obj.day,
obj.hour,
obj.minute,
obj.second,
obj.millisecond
);
// for legacy reasons, years between 0 and 99 are interpreted as 19XX; revert that
if (obj.year < 100 && obj.year >= 0) {
d = new Date(d);
// 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);
}
return +d;
}
// adapted from moment.js: https://github.com/moment/moment/blob/000ac1800e620f770f4eb31b5ae908f6167b0ab2/src/lib/units/week-calendar-utils.js
function firstWeekOffset(year, minDaysInFirstWeek, startOfWeek) {
const fwdlw = isoWeekdayToLocal(dayOfWeek(year, 1, minDaysInFirstWeek), startOfWeek);
return -fwdlw + minDaysInFirstWeek - 1;
}
export function weeksInWeekYear(weekYear, minDaysInFirstWeek = 4, startOfWeek = 1) {
const weekOffset = firstWeekOffset(weekYear, minDaysInFirstWeek, startOfWeek);
const weekOffsetNext = firstWeekOffset(weekYear + 1, minDaysInFirstWeek, startOfWeek);
return (daysInYear(weekYear) - weekOffset + weekOffsetNext) / 7;
}
export function untruncateYear(year) {
if (year > 99) {
return year;
} else return year > Settings.twoDigitCutoffYear ? 1900 + year : 2000 + year;
}
// PARSING
export function parseZoneInfo(ts, offsetFormat, locale, timeZone = null) {
const date = new Date(ts),
intlOpts = {
hourCycle: "h23",
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
};
if (timeZone) {
intlOpts.timeZone = timeZone;
}
const modified = { timeZoneName: offsetFormat, ...intlOpts };
const parsed = new Intl.DateTimeFormat(locale, modified)
.formatToParts(date)
.find((m) => m.type.toLowerCase() === "timezonename");
return parsed ? parsed.value : null;
}
// signedOffset('-5', '30') -> -330
export function signedOffset(offHourStr, offMinuteStr) {
let offHour = parseInt(offHourStr, 10);
// don't || this because we want to preserve -0
if (Number.isNaN(offHour)) {
offHour = 0;
}
const offMin = parseInt(offMinuteStr, 10) || 0,
offMinSigned = offHour < 0 || Object.is(offHour, -0) ? -offMin : offMin;
return offHour * 60 + offMinSigned;
}
// COERCION
export function asNumber(value) {
const numericValue = Number(value);
if (typeof value === "boolean" || value === "" || Number.isNaN(numericValue))
throw new InvalidArgumentError(`Invalid unit value ${value}`);
return numericValue;
}
export function normalizeObject(obj, normalizer) {
const normalized = {};
for (const u in obj) {
if (hasOwnProperty(obj, u)) {
const v = obj[u];
if (v === undefined || v === null) continue;
normalized[normalizer(u)] = asNumber(v);
}
}
return normalized;
}
export function formatOffset(offset, format) {
const hours = Math.trunc(Math.abs(offset / 60)),
minutes = Math.trunc(Math.abs(offset % 60)),
sign = offset >= 0 ? "+" : "-";
switch (format) {
case "short":
return `${sign}${padStart(hours, 2)}:${padStart(minutes, 2)}`;
case "narrow":
return `${sign}${hours}${minutes > 0 ? `:${minutes}` : ""}`;
case "techie":
return `${sign}${padStart(hours, 2)}${padStart(minutes, 2)}`;
default:
throw new RangeError(`Value format ${format} is out of range for property format`);
}
}
export function timeObject(obj) {
return pick(obj, ["hour", "minute", "second", "millisecond"]);
}

34
priv/vendor/luxon/src/impl/zoneUtil.js vendored Normal file
View file

@ -0,0 +1,34 @@
/**
* @private
*/
import Zone from "../zone.js";
import IANAZone from "../zones/IANAZone.js";
import FixedOffsetZone from "../zones/fixedOffsetZone.js";
import InvalidZone from "../zones/invalidZone.js";
import { isUndefined, isString, isNumber } from "./util.js";
import SystemZone from "../zones/systemZone.js";
export function normalizeZone(input, defaultZone) {
let offset;
if (isUndefined(input) || input === null) {
return defaultZone;
} else if (input instanceof Zone) {
return input;
} else if (isString(input)) {
const lowered = input.toLowerCase();
if (lowered === "default") return defaultZone;
else if (lowered === "local" || lowered === "system") return SystemZone.instance;
else if (lowered === "utc" || lowered === "gmt") return FixedOffsetZone.utcInstance;
else return FixedOffsetZone.parseSpecifier(lowered) || IANAZone.create(input);
} else if (isNumber(input)) {
return FixedOffsetZone.instance(input);
} 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
// so we're duck checking it
return input;
} else {
return new InvalidZone(input);
}
}

205
priv/vendor/luxon/src/info.js vendored Normal file
View file

@ -0,0 +1,205 @@
import DateTime from "./datetime.js";
import Settings from "./settings.js";
import Locale from "./impl/locale.js";
import IANAZone from "./zones/IANAZone.js";
import { normalizeZone } from "./impl/zoneUtil.js";
import { hasLocaleWeekInfo, hasRelative } from "./impl/util.js";
/**
* The Info class contains static methods for retrieving general time and date related data. For example, it has methods for finding out if a time zone has a DST, for listing the months in any supported locale, and for discovering which of Luxon features are available in the current environment.
*/
export default class Info {
/**
* Return whether the specified zone contains a DST.
* @param {string|Zone} [zone='local'] - Zone to check. Defaults to the environment's local zone.
* @return {boolean}
*/
static hasDST(zone = Settings.defaultZone) {
const proto = DateTime.now().setZone(zone).set({ month: 12 });
return !zone.isUniversal && proto.offset !== proto.set({ month: 6 }).offset;
}
/**
* Return whether the specified zone is a valid IANA specifier.
* @param {string} zone - Zone to check
* @return {boolean}
*/
static isValidIANAZone(zone) {
return IANAZone.isValidZone(zone);
}
/**
* Converts the input into a {@link Zone} instance.
*
* * If `input` is already a Zone instance, it is returned unchanged.
* * If `input` is a string containing a valid time zone name, a Zone instance
* with that name is returned.
* * If `input` is a string that doesn't refer to a known time zone, a Zone
* instance with {@link Zone#isValid} == false is returned.
* * If `input is a number, a Zone instance with the specified fixed offset
* in minutes is returned.
* * If `input` is `null` or `undefined`, the default zone is returned.
* @param {string|Zone|number} [input] - the value to be converted
* @return {Zone}
*/
static normalizeZone(input) {
return normalizeZone(input, Settings.defaultZone);
}
/**
* Get the weekday on which the week starts according to the given locale.
* @param {Object} opts - options
* @param {string} [opts.locale] - the locale code
* @param {string} [opts.locObj=null] - an existing locale object to use
* @returns {number} the start of the week, 1 for Monday through 7 for Sunday
*/
static getStartOfWeek({ locale = null, locObj = null } = {}) {
return (locObj || Locale.create(locale)).getStartOfWeek();
}
/**
* Get the minimum number of days necessary in a week before it is considered part of the next year according
* to the given locale.
* @param {Object} opts - options
* @param {string} [opts.locale] - the locale code
* @param {string} [opts.locObj=null] - an existing locale object to use
* @returns {number}
*/
static getMinimumDaysInFirstWeek({ locale = null, locObj = null } = {}) {
return (locObj || Locale.create(locale)).getMinDaysInFirstWeek();
}
/**
* Get the weekdays, which are considered the weekend according to the given locale
* @param {Object} opts - options
* @param {string} [opts.locale] - the locale code
* @param {string} [opts.locObj=null] - an existing locale object to use
* @returns {number[]} an array of weekdays, 1 for Monday through 7 for Sunday
*/
static getWeekendWeekdays({ locale = null, locObj = null } = {}) {
// copy the array, because we cache it internally
return (locObj || Locale.create(locale)).getWeekendDays().slice();
}
/**
* Return an array of standalone month names.
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat
* @param {string} [length='long'] - the length of the month representation, such as "numeric", "2-digit", "narrow", "short", "long"
* @param {Object} opts - options
* @param {string} [opts.locale] - the locale code
* @param {string} [opts.numberingSystem=null] - the numbering system
* @param {string} [opts.locObj=null] - an existing locale object to use
* @param {string} [opts.outputCalendar='gregory'] - the calendar
* @example Info.months()[0] //=> 'January'
* @example Info.months('short')[0] //=> 'Jan'
* @example Info.months('numeric')[0] //=> '1'
* @example Info.months('short', { locale: 'fr-CA' } )[0] //=> 'janv.'
* @example Info.months('numeric', { locale: 'ar' })[0] //=> '١'
* @example Info.months('long', { outputCalendar: 'islamic' })[0] //=> 'Rabiʻ I'
* @return {Array}
*/
static months(
length = "long",
{ locale = null, numberingSystem = null, locObj = null, outputCalendar = "gregory" } = {}
) {
return (locObj || Locale.create(locale, numberingSystem, outputCalendar)).months(length);
}
/**
* Return an array of format month names.
* Format months differ from standalone months in that they're meant to appear next to the day of the month. In some languages, that
* changes the string.
* See {@link Info#months}
* @param {string} [length='long'] - the length of the month representation, such as "numeric", "2-digit", "narrow", "short", "long"
* @param {Object} opts - options
* @param {string} [opts.locale] - the locale code
* @param {string} [opts.numberingSystem=null] - the numbering system
* @param {string} [opts.locObj=null] - an existing locale object to use
* @param {string} [opts.outputCalendar='gregory'] - the calendar
* @return {Array}
*/
static monthsFormat(
length = "long",
{ locale = null, numberingSystem = null, locObj = null, outputCalendar = "gregory" } = {}
) {
return (locObj || Locale.create(locale, numberingSystem, outputCalendar)).months(length, true);
}
/**
* Return an array of standalone week names.
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat
* @param {string} [length='long'] - the length of the weekday representation, such as "narrow", "short", "long".
* @param {Object} opts - options
* @param {string} [opts.locale] - the locale code
* @param {string} [opts.numberingSystem=null] - the numbering system
* @param {string} [opts.locObj=null] - an existing locale object to use
* @example Info.weekdays()[0] //=> 'Monday'
* @example Info.weekdays('short')[0] //=> 'Mon'
* @example Info.weekdays('short', { locale: 'fr-CA' })[0] //=> 'lun.'
* @example Info.weekdays('short', { locale: 'ar' })[0] //=> 'الاثنين'
* @return {Array}
*/
static weekdays(length = "long", { locale = null, numberingSystem = null, locObj = null } = {}) {
return (locObj || Locale.create(locale, numberingSystem, null)).weekdays(length);
}
/**
* Return an array of format week names.
* Format weekdays differ from standalone weekdays in that they're meant to appear next to more date information. In some languages, that
* changes the string.
* See {@link Info#weekdays}
* @param {string} [length='long'] - the length of the month representation, such as "narrow", "short", "long".
* @param {Object} opts - options
* @param {string} [opts.locale=null] - the locale code
* @param {string} [opts.numberingSystem=null] - the numbering system
* @param {string} [opts.locObj=null] - an existing locale object to use
* @return {Array}
*/
static weekdaysFormat(
length = "long",
{ locale = null, numberingSystem = null, locObj = null } = {}
) {
return (locObj || Locale.create(locale, numberingSystem, null)).weekdays(length, true);
}
/**
* Return an array of meridiems.
* @param {Object} opts - options
* @param {string} [opts.locale] - the locale code
* @example Info.meridiems() //=> [ 'AM', 'PM' ]
* @example Info.meridiems({ locale: 'my' }) //=> [ 'နံနက်', 'ညနေ' ]
* @return {Array}
*/
static meridiems({ locale = null } = {}) {
return Locale.create(locale).meridiems();
}
/**
* Return an array of eras, such as ['BC', 'AD']. The locale can be specified, but the calendar system is always Gregorian.
* @param {string} [length='short'] - the length of the era representation, such as "short" or "long".
* @param {Object} opts - options
* @param {string} [opts.locale] - the locale code
* @example Info.eras() //=> [ 'BC', 'AD' ]
* @example Info.eras('long') //=> [ 'Before Christ', 'Anno Domini' ]
* @example Info.eras('long', { locale: 'fr' }) //=> [ 'avant Jésus-Christ', 'après Jésus-Christ' ]
* @return {Array}
*/
static eras(length = "short", { locale = null } = {}) {
return Locale.create(locale, null, "gregory").eras(length);
}
/**
* Return the set of available features in this environment.
* 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:
* * `relative`: whether this environment supports relative time formatting
* * `localeWeek`: whether this environment supports different weekdays for the start of the week based on the locale
* @example Info.features() //=> { relative: false, localeWeek: true }
* @return {Object}
*/
static features() {
return { relative: hasRelative(), localeWeek: hasLocaleWeekInfo() };
}
}

657
priv/vendor/luxon/src/interval.js vendored Normal file
View file

@ -0,0 +1,657 @@
import DateTime, { friendlyDateTime } from "./datetime.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";
const INVALID = "Invalid Interval";
// checks if the start is equal to or before the end
function validateStartEnd(start, end) {
if (!start || !start.isValid) {
return Interval.invalid("missing or invalid start");
} else if (!end || !end.isValid) {
return Interval.invalid("missing or invalid end");
} else if (end < start) {
return Interval.invalid(
"end before start",
`The end of an interval must be after its start, but you had start=${start.toISO()} and end=${end.toISO()}`
);
} else {
return null;
}
}
/**
* An Interval object represents a half-open interval of time, where each endpoint is a {@link DateTime}. Conceptually, it's a container for those two endpoints, accompanied by methods for creating, parsing, interrogating, comparing, transforming, and formatting them.
*
* Here is a brief overview of the most commonly used methods and getters in Interval:
*
* * **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}.
*/
export default class Interval {
/**
* @private
*/
constructor(config) {
/**
* @access private
*/
this.s = config.start;
/**
* @access private
*/
this.e = config.end;
/**
* @access private
*/
this.invalid = config.invalid || null;
/**
* @access private
*/
this.isLuxonInterval = true;
}
/**
* Create an invalid Interval.
* @param {string} reason - simple string of why this Interval is invalid. Should not contain parameters or anything else data-dependent
* @param {string} [explanation=null] - longer explanation, may include parameters and other useful debugging information
* @return {Interval}
*/
static invalid(reason, explanation = null) {
if (!reason) {
throw new InvalidArgumentError("need to specify a reason the Interval is invalid");
}
const invalid = reason instanceof Invalid ? reason : new Invalid(reason, explanation);
if (Settings.throwOnInvalid) {
throw new InvalidIntervalError(invalid);
} else {
return new Interval({ invalid });
}
}
/**
* Create an Interval from a start DateTime and an end DateTime. Inclusive of the start but not the end.
* @param {DateTime|Date|Object} start
* @param {DateTime|Date|Object} end
* @return {Interval}
*/
static fromDateTimes(start, end) {
const builtStart = friendlyDateTime(start),
builtEnd = friendlyDateTime(end);
const validateError = validateStartEnd(builtStart, builtEnd);
if (validateError == null) {
return new Interval({
start: builtStart,
end: builtEnd,
});
} else {
return validateError;
}
}
/**
* Create an Interval from a start DateTime and a Duration to extend to.
* @param {DateTime|Date|Object} start
* @param {Duration|Object|number} duration - the length of the Interval.
* @return {Interval}
*/
static after(start, duration) {
const dur = Duration.fromDurationLike(duration),
dt = friendlyDateTime(start);
return Interval.fromDateTimes(dt, dt.plus(dur));
}
/**
* Create an Interval from an end DateTime and a Duration to extend backwards to.
* @param {DateTime|Date|Object} end
* @param {Duration|Object|number} duration - the length of the Interval.
* @return {Interval}
*/
static before(end, duration) {
const dur = Duration.fromDurationLike(duration),
dt = friendlyDateTime(end);
return Interval.fromDateTimes(dt.minus(dur), dt);
}
/**
* Create an Interval from an ISO 8601 string.
* Accepts `<start>/<end>`, `<start>/<duration>`, and `<duration>/<end>` formats.
* @param {string} text - the ISO string to parse
* @param {Object} [opts] - options to pass {@link DateTime#fromISO} and optionally {@link Duration#fromISO}
* @see https://en.wikipedia.org/wiki/ISO_8601#Time_intervals
* @return {Interval}
*/
static fromISO(text, opts) {
const [s, e] = (text || "").split("/", 2);
if (s && e) {
let start, startIsValid;
try {
start = DateTime.fromISO(s, opts);
startIsValid = start.isValid;
} catch (e) {
startIsValid = false;
}
let end, endIsValid;
try {
end = DateTime.fromISO(e, opts);
endIsValid = end.isValid;
} catch (e) {
endIsValid = false;
}
if (startIsValid && endIsValid) {
return Interval.fromDateTimes(start, end);
}
if (startIsValid) {
const dur = Duration.fromISO(e, opts);
if (dur.isValid) {
return Interval.after(start, dur);
}
} else if (endIsValid) {
const dur = Duration.fromISO(s, opts);
if (dur.isValid) {
return Interval.before(end, dur);
}
}
}
return Interval.invalid("unparsable", `the input "${text}" can't be parsed as ISO 8601`);
}
/**
* Check if an object is an Interval. Works across context boundaries
* @param {object} o
* @return {boolean}
*/
static isInterval(o) {
return (o && o.isLuxonInterval) || false;
}
/**
* Returns the start of the Interval
* @type {DateTime}
*/
get start() {
return this.isValid ? this.s : null;
}
/**
* Returns the end of the Interval
* @type {DateTime}
*/
get end() {
return this.isValid ? this.e : null;
}
/**
* Returns whether this Interval's end is at least its start, meaning that the Interval isn't 'backwards'.
* @type {boolean}
*/
get isValid() {
return this.invalidReason === null;
}
/**
* Returns an error code if this Interval is invalid, or null if the Interval is valid
* @type {string}
*/
get invalidReason() {
return this.invalid ? this.invalid.reason : null;
}
/**
* Returns an explanation of why this Interval became invalid, or null if the Interval is valid
* @type {string}
*/
get invalidExplanation() {
return this.invalid ? this.invalid.explanation : null;
}
/**
* Returns the length of the Interval in the specified unit.
* @param {string} unit - the unit (such as 'hours' or 'days') to return the length in.
* @return {number}
*/
length(unit = "milliseconds") {
return this.isValid ? this.toDuration(...[unit]).get(unit) : NaN;
}
/**
* Returns the count of minutes, hours, days, months, or years included in the Interval, even in part.
* 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?'
* @param {string} [unit='milliseconds'] - the unit of time to count.
* @param {Object} opts - options
* @param {boolean} [opts.useLocaleWeeks=false] - If true, use weeks based on the locale, i.e. use the locale-dependent start of the week; this operation will always use the locale of the start DateTime
* @return {number}
*/
count(unit = "milliseconds", opts) {
if (!this.isValid) return NaN;
const start = this.start.startOf(unit, opts);
let end;
if (opts?.useLocaleWeeks) {
end = this.end.reconfigure({ locale: start.locale });
} else {
end = this.end;
}
end = end.startOf(unit, opts);
return Math.floor(end.diff(start, unit).get(unit)) + (end.valueOf() !== this.end.valueOf());
}
/**
* Returns whether this Interval's start and end are both in the same unit of time
* @param {string} unit - the unit of time to check sameness on
* @return {boolean}
*/
hasSame(unit) {
return this.isValid ? this.isEmpty() || this.e.minus(1).hasSame(this.s, unit) : false;
}
/**
* Return whether this Interval has the same start and end DateTimes.
* @return {boolean}
*/
isEmpty() {
return this.s.valueOf() === this.e.valueOf();
}
/**
* Return whether this Interval's start is after the specified DateTime.
* @param {DateTime} dateTime
* @return {boolean}
*/
isAfter(dateTime) {
if (!this.isValid) return false;
return this.s > dateTime;
}
/**
* Return whether this Interval's end is before the specified DateTime.
* @param {DateTime} dateTime
* @return {boolean}
*/
isBefore(dateTime) {
if (!this.isValid) return false;
return this.e <= dateTime;
}
/**
* Return whether this Interval contains the specified DateTime.
* @param {DateTime} dateTime
* @return {boolean}
*/
contains(dateTime) {
if (!this.isValid) return false;
return this.s <= dateTime && this.e > dateTime;
}
/**
* "Sets" the start and/or end dates. Returns a newly-constructed Interval.
* @param {Object} values - the values to set
* @param {DateTime} values.start - the starting DateTime
* @param {DateTime} values.end - the ending DateTime
* @return {Interval}
*/
set({ start, end } = {}) {
if (!this.isValid) return this;
return Interval.fromDateTimes(start || this.s, end || this.e);
}
/**
* Split this Interval at each of the specified DateTimes
* @param {...DateTime} dateTimes - the unit of time to count.
* @return {Array}
*/
splitAt(...dateTimes) {
if (!this.isValid) return [];
const sorted = dateTimes
.map(friendlyDateTime)
.filter((d) => this.contains(d))
.sort((a, b) => a.toMillis() - b.toMillis()),
results = [];
let { s } = this,
i = 0;
while (s < this.e) {
const added = sorted[i] || this.e,
next = +added > +this.e ? this.e : added;
results.push(Interval.fromDateTimes(s, next));
s = next;
i += 1;
}
return results;
}
/**
* Split this Interval into smaller Intervals, each of the specified length.
* Left over time is grouped into a smaller interval
* @param {Duration|Object|number} duration - The length of each resulting interval.
* @return {Array}
*/
splitBy(duration) {
const dur = Duration.fromDurationLike(duration);
if (!this.isValid || !dur.isValid || dur.as("milliseconds") === 0) {
return [];
}
let { s } = this,
idx = 1,
next;
const results = [];
while (s < this.e) {
const added = this.start.plus(dur.mapUnits((x) => x * idx));
next = +added > +this.e ? this.e : added;
results.push(Interval.fromDateTimes(s, next));
s = next;
idx += 1;
}
return results;
}
/**
* Split this Interval into the specified number of smaller intervals.
* @param {number} numberOfParts - The number of Intervals to divide the Interval into.
* @return {Array}
*/
divideEqually(numberOfParts) {
if (!this.isValid) return [];
return this.splitBy(this.length() / numberOfParts).slice(0, numberOfParts);
}
/**
* Return whether this Interval overlaps with the specified Interval
* @param {Interval} other
* @return {boolean}
*/
overlaps(other) {
return this.e > other.s && this.s < other.e;
}
/**
* Return whether this Interval's end is adjacent to the specified Interval's start.
* @param {Interval} other
* @return {boolean}
*/
abutsStart(other) {
if (!this.isValid) return false;
return +this.e === +other.s;
}
/**
* Return whether this Interval's start is adjacent to the specified Interval's end.
* @param {Interval} other
* @return {boolean}
*/
abutsEnd(other) {
if (!this.isValid) return false;
return +other.e === +this.s;
}
/**
* Return whether this Interval engulfs the start and end of the specified Interval.
* @param {Interval} other
* @return {boolean}
*/
engulfs(other) {
if (!this.isValid) return false;
return this.s <= other.s && this.e >= other.e;
}
/**
* Return whether this Interval has the same start and end as the specified Interval.
* @param {Interval} other
* @return {boolean}
*/
equals(other) {
if (!this.isValid || !other.isValid) {
return false;
}
return this.s.equals(other.s) && this.e.equals(other.e);
}
/**
* Return an Interval representing the intersection of this Interval and the specified Interval.
* Specifically, the resulting Interval has the maximum start time and the minimum end time of the two Intervals.
* Returns null if the intersection is empty, meaning, the intervals don't intersect.
* @param {Interval} other
* @return {Interval}
*/
intersection(other) {
if (!this.isValid) return this;
const s = this.s > other.s ? this.s : other.s,
e = this.e < other.e ? this.e : other.e;
if (s >= e) {
return null;
} else {
return Interval.fromDateTimes(s, e);
}
}
/**
* Return an Interval representing the union of this Interval and the specified Interval.
* Specifically, the resulting Interval has the minimum start time and the maximum end time of the two Intervals.
* @param {Interval} other
* @return {Interval}
*/
union(other) {
if (!this.isValid) return this;
const s = this.s < other.s ? this.s : other.s,
e = this.e > other.e ? this.e : other.e;
return Interval.fromDateTimes(s, e);
}
/**
* Merge an array of Intervals into a equivalent minimal set of Intervals.
* Combines overlapping and adjacent Intervals.
* @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]
);
if (final) {
found.push(final);
}
return found;
}
/**
* Return an array of Intervals representing the spans of time that only appear in one of the specified Intervals.
* @param {Array} intervals
* @return {Array}
*/
static xor(intervals) {
let start = null,
currentCount = 0;
const results = [],
ends = intervals.map((i) => [
{ time: i.s, type: "s" },
{ time: i.e, type: "e" },
]),
flattened = Array.prototype.concat(...ends),
arr = flattened.sort((a, b) => a.time - b.time);
for (const i of arr) {
currentCount += i.type === "s" ? 1 : -1;
if (currentCount === 1) {
start = i.time;
} else {
if (start && +start !== +i.time) {
results.push(Interval.fromDateTimes(start, i.time));
}
start = null;
}
}
return Interval.merge(results);
}
/**
* Return an Interval representing the span of time in this Interval that doesn't overlap with any of the specified Intervals.
* @param {...Interval} intervals
* @return {Array}
*/
difference(...intervals) {
return Interval.xor([this].concat(intervals))
.map((i) => this.intersection(i))
.filter((i) => i && !i.isEmpty());
}
/**
* Returns a string representation of this Interval appropriate for debugging.
* @return {string}
*/
toString() {
if (!this.isValid) return INVALID;
return `[${this.s.toISO()} ${this.e.toISO()})`;
}
/**
* Returns a string representation of this Interval appropriate for the REPL.
* @return {string}
*/
[Symbol.for("nodejs.util.inspect.custom")]() {
if (this.isValid) {
return `Interval { start: ${this.s.toISO()}, end: ${this.e.toISO()} }`;
} else {
return `Interval { Invalid, reason: ${this.invalidReason} }`;
}
}
/**
* 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' }); //=> 78 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}
* @return {string}
*/
toISO(opts) {
if (!this.isValid) return INVALID;
return `${this.s.toISO(opts)}/${this.e.toISO(opts)}`;
}
/**
* Returns an ISO 8601-compliant string representation of date of this Interval.
* The time components are ignored.
* @see https://en.wikipedia.org/wiki/ISO_8601#Time_intervals
* @return {string}
*/
toISODate() {
if (!this.isValid) return INVALID;
return `${this.s.toISODate()}/${this.e.toISODate()}`;
}
/**
* Returns an ISO 8601-compliant string representation of time of this Interval.
* The date components are ignored.
* @see https://en.wikipedia.org/wiki/ISO_8601#Time_intervals
* @param {Object} opts - The same options as {@link DateTime#toISO}
* @return {string}
*/
toISOTime(opts) {
if (!this.isValid) return INVALID;
return `${this.s.toISOTime(opts)}/${this.e.toISOTime(opts)}`;
}
/**
* 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}
*/
toFormat(dateFormat, { separator = " " } = {}) {
if (!this.isValid) return INVALID;
return `${this.s.toFormat(dateFormat)}${separator}${this.e.toFormat(dateFormat)}`;
}
/**
* Return a Duration representing the time spanned by this interval.
* @param {string|string[]} [unit=['milliseconds']] - the unit or units (such as 'hours' or 'days') to include in the duration.
* @param {Object} opts - options that affect the creation of the Duration
* @param {string} [opts.conversionAccuracy='casual'] - the conversion system to use
* @example Interval.fromDateTimes(dt1, dt2).toDuration().toObject() //=> { milliseconds: 88489257 }
* @example Interval.fromDateTimes(dt1, dt2).toDuration('days').toObject() //=> { days: 1.0241812152777778 }
* @example Interval.fromDateTimes(dt1, dt2).toDuration(['hours', 'minutes']).toObject() //=> { hours: 24, minutes: 34.82095 }
* @example Interval.fromDateTimes(dt1, dt2).toDuration(['hours', 'minutes', 'seconds']).toObject() //=> { hours: 24, minutes: 34, seconds: 49.257 }
* @example Interval.fromDateTimes(dt1, dt2).toDuration('seconds').toObject() //=> { seconds: 88489.257 }
* @return {Duration}
*/
toDuration(unit, opts) {
if (!this.isValid) {
return Duration.invalid(this.invalidReason);
}
return this.e.diff(this.s, unit, opts);
}
/**
* Run mapFn on the interval start and end, returning a new Interval from the resulting DateTimes
* @param {function} mapFn
* @return {Interval}
* @example Interval.fromDateTimes(dt1, dt2).mapEndpoints(endpoint => endpoint.toUTC())
* @example Interval.fromDateTimes(dt1, dt2).mapEndpoints(endpoint => endpoint.plus({ hours: 2 }))
*/
mapEndpoints(mapFn) {
return Interval.fromDateTimes(mapFn(this.s), mapFn(this.e));
}
}

26
priv/vendor/luxon/src/luxon.js vendored Normal file
View file

@ -0,0 +1,26 @@
import DateTime from "./datetime.js";
import Duration from "./duration.js";
import Interval from "./interval.js";
import Info from "./info.js";
import Zone from "./zone.js";
import FixedOffsetZone from "./zones/fixedOffsetZone.js";
import IANAZone from "./zones/IANAZone.js";
import InvalidZone from "./zones/invalidZone.js";
import SystemZone from "./zones/systemZone.js";
import Settings from "./settings.js";
const VERSION = "3.4.4";
export {
VERSION,
DateTime,
Duration,
Interval,
Info,
Zone,
FixedOffsetZone,
IANAZone,
InvalidZone,
SystemZone,
Settings,
};

4
priv/vendor/luxon/src/package.json vendored Normal file
View file

@ -0,0 +1,4 @@
{
"type": "module",
"version": "3.4.4"
}

175
priv/vendor/luxon/src/settings.js vendored Normal file
View file

@ -0,0 +1,175 @@
import SystemZone from "./zones/systemZone.js";
import IANAZone from "./zones/IANAZone.js";
import Locale from "./impl/locale.js";
import { normalizeZone } from "./impl/zoneUtil.js";
import { validateWeekSettings } from "./impl/util.js";
let now = () => Date.now(),
defaultZone = "system",
defaultLocale = null,
defaultNumberingSystem = null,
defaultOutputCalendar = null,
twoDigitCutoffYear = 60,
throwOnInvalid,
defaultWeekSettings = null;
/**
* Settings contains static getters and setters that control Luxon's overall behavior. Luxon is a simple library with few options, but the ones it does have live here.
*/
export default class Settings {
/**
* Get the callback for returning the current timestamp.
* @type {function}
*/
static get now() {
return now;
}
/**
* Set the callback for returning the current timestamp.
* The function should return a number, which will be interpreted as an Epoch millisecond count
* @type {function}
* @example Settings.now = () => Date.now() + 3000 // pretend it is 3 seconds in the future
* @example Settings.now = () => 0 // always pretend it's Jan 1, 1970 at midnight in UTC time
*/
static set now(n) {
now = n;
}
/**
* 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 defaultZone(zone) {
defaultZone = zone;
}
/**
* 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 normalizeZone(defaultZone, SystemZone.instance);
}
/**
* Get the default locale to create DateTimes with. Does not affect existing instances.
* @type {string}
*/
static get defaultLocale() {
return defaultLocale;
}
/**
* Set the default locale to create DateTimes with. Does not affect existing instances.
* @type {string}
*/
static set defaultLocale(locale) {
defaultLocale = locale;
}
/**
* Get the default numbering system to create DateTimes with. Does not affect existing instances.
* @type {string}
*/
static get defaultNumberingSystem() {
return defaultNumberingSystem;
}
/**
* Set the default numbering system to create DateTimes with. Does not affect existing instances.
* @type {string}
*/
static set defaultNumberingSystem(numberingSystem) {
defaultNumberingSystem = numberingSystem;
}
/**
* Get the default output calendar to create DateTimes with. Does not affect existing instances.
* @type {string}
*/
static get defaultOutputCalendar() {
return defaultOutputCalendar;
}
/**
* Set the default output calendar to create DateTimes with. Does not affect existing instances.
* @type {string}
*/
static set defaultOutputCalendar(outputCalendar) {
defaultOutputCalendar = outputCalendar;
}
/**
* @typedef {Object} WeekSettings
* @property {number} firstDay
* @property {number} minimalDays
* @property {number[]} weekend
*/
/**
* @return {WeekSettings|null}
*/
static get defaultWeekSettings() {
return defaultWeekSettings;
}
/**
* Allows overriding the default locale week settings, i.e. the start of the week, the weekend and
* how many days are required in the first week of a year.
* Does not affect existing instances.
*
* @param {WeekSettings|null} weekSettings
*/
static set defaultWeekSettings(weekSettings) {
defaultWeekSettings = validateWeekSettings(weekSettings);
}
/**
* 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
* @type {boolean}
*/
static get throwOnInvalid() {
return throwOnInvalid;
}
/**
* Set whether Luxon will throw when it encounters invalid DateTimes, Durations, or Intervals
* @type {boolean}
*/
static set throwOnInvalid(t) {
throwOnInvalid = t;
}
/**
* Reset Luxon's global caches. Should only be necessary in testing scenarios.
* @return {void}
*/
static resetCaches() {
Locale.resetCache();
IANAZone.resetCache();
}
}

91
priv/vendor/luxon/src/zone.js vendored Normal file
View file

@ -0,0 +1,91 @@
import { ZoneIsAbstractError } from "./errors.js";
/**
* @interface
*/
export default class Zone {
/**
* The type of zone
* @abstract
* @type {string}
*/
get type() {
throw new ZoneIsAbstractError();
}
/**
* The name of this zone.
* @abstract
* @type {string}
*/
get name() {
throw new ZoneIsAbstractError();
}
get ianaName() {
return this.name;
}
/**
* Returns whether the offset is known to be fixed for the whole year.
* @abstract
* @type {boolean}
*/
get isUniversal() {
throw new ZoneIsAbstractError();
}
/**
* Returns the offset's common name (such as EST) at the specified timestamp
* @abstract
* @param {number} ts - Epoch milliseconds for which to get the name
* @param {Object} opts - Options to affect the format
* @param {string} opts.format - What style of offset to return. Accepts 'long' or 'short'.
* @param {string} opts.locale - What locale to return the offset name in.
* @return {string}
*/
offsetName(ts, opts) {
throw new ZoneIsAbstractError();
}
/**
* Returns the offset's value as a string
* @abstract
* @param {number} ts - Epoch milliseconds for which to get the offset
* @param {string} format - What style of offset to return.
* Accepts 'narrow', 'short', or 'techie'. Returning '+6', '+06:00', or '+0600' respectively
* @return {string}
*/
formatOffset(ts, format) {
throw new ZoneIsAbstractError();
}
/**
* Return the offset in minutes for this zone at the specified timestamp.
* @abstract
* @param {number} ts - Epoch milliseconds for which to compute the offset
* @return {number}
*/
offset(ts) {
throw new ZoneIsAbstractError();
}
/**
* Return whether this Zone is equal to another zone
* @abstract
* @param {Zone} otherZone - the zone to compare
* @return {boolean}
*/
equals(otherZone) {
throw new ZoneIsAbstractError();
}
/**
* Return whether this Zone is valid.
* @abstract
* @type {boolean}
*/
get isValid() {
throw new ZoneIsAbstractError();
}
}

189
priv/vendor/luxon/src/zones/IANAZone.js vendored Normal file
View file

@ -0,0 +1,189 @@
import { formatOffset, parseZoneInfo, isUndefined, objToLocalTS } from "../impl/util.js";
import Zone from "../zone.js";
let dtfCache = {};
function makeDTF(zone) {
if (!dtfCache[zone]) {
dtfCache[zone] = new Intl.DateTimeFormat("en-US", {
hour12: false,
timeZone: zone,
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
era: "short",
});
}
return dtfCache[zone];
}
const typeToPos = {
year: 0,
month: 1,
day: 2,
era: 3,
hour: 4,
minute: 5,
second: 6,
};
function hackyOffset(dtf, date) {
const formatted = dtf.format(date).replace(/\u200E/g, ""),
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);
const filled = [];
for (let i = 0; i < formatted.length; i++) {
const { type, value } = formatted[i];
const pos = typeToPos[type];
if (type === "era") {
filled[pos] = value;
} else if (!isUndefined(pos)) {
filled[pos] = parseInt(value, 10);
}
}
return filled;
}
let ianaZoneCache = {};
/**
* A zone identified by an IANA identifier, like America/New_York
* @implements {Zone}
*/
export default class IANAZone extends Zone {
/**
* @param {string} name - Zone name
* @return {IANAZone}
*/
static create(name) {
if (!ianaZoneCache[name]) {
ianaZoneCache[name] = new IANAZone(name);
}
return ianaZoneCache[name];
}
/**
* Reset local caches. Should only be necessary in testing scenarios.
* @return {void}
*/
static resetCache() {
ianaZoneCache = {};
dtfCache = {};
}
/**
* Returns whether the provided string is a valid specifier. This only checks the string's format, not that the specifier identifies a known zone; see isValidZone for that.
* @param {string} s - The string to check validity on
* @example IANAZone.isValidSpecifier("America/New_York") //=> 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 this.isValidZone(s);
}
/**
* Returns whether the provided string identifies a real zone
* @param {string} zone - The string to check
* @example IANAZone.isValidZone("America/New_York") //=> true
* @example IANAZone.isValidZone("Fantasia/Castle") //=> false
* @example IANAZone.isValidZone("Sport~~blorp") //=> false
* @return {boolean}
*/
static isValidZone(zone) {
if (!zone) {
return false;
}
try {
new Intl.DateTimeFormat("en-US", { timeZone: zone }).format();
return true;
} catch (e) {
return false;
}
}
constructor(name) {
super();
/** @private **/
this.zoneName = name;
/** @private **/
this.valid = IANAZone.isValidZone(name);
}
/** @override **/
get type() {
return "iana";
}
/** @override **/
get name() {
return this.zoneName;
}
/** @override **/
get isUniversal() {
return false;
}
/** @override **/
offsetName(ts, { format, locale }) {
return parseZoneInfo(ts, format, locale, this.name);
}
/** @override **/
formatOffset(ts, format) {
return formatOffset(this.offset(ts), format);
}
/** @override **/
offset(ts) {
const date = new Date(ts);
if (isNaN(date)) return NaN;
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({
year,
month,
day,
hour: adjustedHour,
minute,
second,
millisecond: 0,
});
let asTS = +date;
const over = asTS % 1000;
asTS -= over >= 0 ? over : 1000 + over;
return (asUTC - asTS) / (60 * 1000);
}
/** @override **/
equals(otherZone) {
return otherZone.type === "iana" && otherZone.name === this.name;
}
/** @override **/
get isValid() {
return this.valid;
}
}

View file

@ -0,0 +1,102 @@
import { formatOffset, signedOffset } from "../impl/util.js";
import Zone from "../zone.js";
let singleton = null;
/**
* A zone with a fixed offset (meaning no DST)
* @implements {Zone}
*/
export default class FixedOffsetZone extends Zone {
/**
* Get a singleton instance of UTC
* @return {FixedOffsetZone}
*/
static get utcInstance() {
if (singleton === null) {
singleton = new FixedOffsetZone(0);
}
return singleton;
}
/**
* Get an instance with a specified offset
* @param {number} offset - The offset in minutes
* @return {FixedOffsetZone}
*/
static instance(offset) {
return offset === 0 ? FixedOffsetZone.utcInstance : new FixedOffsetZone(offset);
}
/**
* Get an instance of FixedOffsetZone from a UTC offset string, like "UTC+6"
* @param {string} s - The offset string to parse
* @example FixedOffsetZone.parseSpecifier("UTC+6")
* @example FixedOffsetZone.parseSpecifier("UTC+06")
* @example FixedOffsetZone.parseSpecifier("UTC-6:00")
* @return {FixedOffsetZone}
*/
static parseSpecifier(s) {
if (s) {
const r = s.match(/^utc(?:([+-]\d{1,2})(?::(\d{2}))?)?$/i);
if (r) {
return new FixedOffsetZone(signedOffset(r[1], r[2]));
}
}
return null;
}
constructor(offset) {
super();
/** @private **/
this.fixed = offset;
}
/** @override **/
get type() {
return "fixed";
}
/** @override **/
get name() {
return this.fixed === 0 ? "UTC" : `UTC${formatOffset(this.fixed, "narrow")}`;
}
get ianaName() {
if (this.fixed === 0) {
return "Etc/UTC";
} else {
return `Etc/GMT${formatOffset(-this.fixed, "narrow")}`;
}
}
/** @override **/
offsetName() {
return this.name;
}
/** @override **/
formatOffset(ts, format) {
return formatOffset(this.fixed, format);
}
/** @override **/
get isUniversal() {
return true;
}
/** @override **/
offset() {
return this.fixed;
}
/** @override **/
equals(otherZone) {
return otherZone.type === "fixed" && otherZone.fixed === this.fixed;
}
/** @override **/
get isValid() {
return true;
}
}

View file

@ -0,0 +1,53 @@
import Zone from "../zone.js";
/**
* A zone that failed to parse. You should never need to instantiate this.
* @implements {Zone}
*/
export default class InvalidZone extends Zone {
constructor(zoneName) {
super();
/** @private */
this.zoneName = zoneName;
}
/** @override **/
get type() {
return "invalid";
}
/** @override **/
get name() {
return this.zoneName;
}
/** @override **/
get isUniversal() {
return false;
}
/** @override **/
offsetName() {
return null;
}
/** @override **/
formatOffset() {
return "";
}
/** @override **/
offset() {
return NaN;
}
/** @override **/
equals() {
return false;
}
/** @override **/
get isValid() {
return false;
}
}

View file

@ -0,0 +1,61 @@
import { formatOffset, parseZoneInfo } from "../impl/util.js";
import Zone from "../zone.js";
let singleton = null;
/**
* Represents the local zone for this JavaScript environment.
* @implements {Zone}
*/
export default class SystemZone extends Zone {
/**
* Get a singleton instance of the local zone
* @return {SystemZone}
*/
static get instance() {
if (singleton === null) {
singleton = new SystemZone();
}
return singleton;
}
/** @override **/
get type() {
return "system";
}
/** @override **/
get name() {
return new Intl.DateTimeFormat().resolvedOptions().timeZone;
}
/** @override **/
get isUniversal() {
return false;
}
/** @override **/
offsetName(ts, { format, locale }) {
return parseZoneInfo(ts, format, locale);
}
/** @override **/
formatOffset(ts, format) {
return formatOffset(this.offset(ts), format);
}
/** @override **/
offset(ts) {
return -new Date(ts).getTimezoneOffset();
}
/** @override **/
equals(otherZone) {
return otherZone.type === "system";
}
/** @override **/
get isValid() {
return true;
}
}

View file

@ -1,4 +1,4 @@
import { DateTime } from "luxon";
import { DateTime } from "./priv/vendor/luxon/src/luxon.js";
import { Ok, Error } from "./gleam.mjs";
import { Date, parse_month } from "./gloss/utils/date.mjs";
import { Time } from "./gloss/utils/time.mjs";

View file

@ -24,7 +24,7 @@ pub type WriteError {
pub fn write(db: RenderDatabase, path_conf: PathConfiguration) {
let site =
ssg.new(default_output)
|> ssg.add_static_dir("./assets")
|> ssg.add_static_dir("./build/dev/javascript/gloss/priv/assets")
let single_posts =
db.single_posts