diff --git a/.size-limit.json b/.size-limit.json index ec1c34f..8ba8625 100644 --- a/.size-limit.json +++ b/.size-limit.json @@ -4,63 +4,127 @@ "path": "src/index.js", "import": "{ datetime }" }, + { + "name": "`datetime` (Temporal)", + "path": "src/temporal-same-features/index.js", + "import": "{ datetime }" + }, { "name": "`utc`", "path": "src/index.js", "import": "{ utc }" }, + { + "name": "`utc` (Temporal)", + "path": "src/temporal-same-features/index.js", + "import": "{ utc }" + }, { "name": "`datetimeTz`", "path": "src/index.js", "import": "{ datetimeTz }" }, + { + "name": "`datetimeTz` (Temporal)", + "path": "src/temporal-same-features/index.js", + "import": "{ datetimeTz }" + }, { "name": "`duration`", "path": "src/index.js", "import": "{ duration }" }, + { + "name": "`duration` (Temporal)", + "path": "src/temporal-same-features/index.js", + "import": "{ duration }" + }, { "name": "`tzOffset`", "path": "src/index.js", "import": "{ tzOffset }" }, + { + "name": "`tzOffset` (Temporal)", + "path": "src/temporal-same-features/index.js", + "import": "{ tzOffset }" + }, { "name": "`setTimeSeparator`", "path": "src/index.js", "import": "{ setTimeSeparator }" }, + { + "name": "`setTimeSeparator` (Temporal)", + "path": "src/temporal-same-features/index.js", + "import": "{ setTimeSeparator }" + }, { "name": "`setTzConfig`", "path": "src/index.js", "import": "{ setTzConfig }" }, + { + "name": "`setTzConfig` (Temporal)", + "path": "src/temporal-same-features/index.js", + "import": "{ setTzConfig }" + }, { "name": "`setTzInRealWorldRange`", "path": "src/index.js", "import": "{ setTzInRealWorldRange }" }, + { + "name": "`setTzInRealWorldRange` (Temporal)", + "path": "src/temporal-same-features/index.js", + "import": "{ setTzInRealWorldRange }" + }, { "name": "`setTzSeparator`", "path": "src/index.js", "import": "{ setTzSeparator }" }, + { + "name": "`setTzSeparator` (Temporal)", + "path": "src/temporal-same-features/index.js", + "import": "{ setTzSeparator }" + }, { "name": "`daysBetween`", "path": "src/index.js", "import": "{ daysBetween }" }, + { + "name": "`daysBetween` (Temporal)", + "path": "src/temporal-same-features/index.js", + "import": "{ daysBetween }" + }, { "name": "`weekNumber`", "path": "src/index.js", "import": "{ weekNumber }" }, + { + "name": "`weekNumber` (Temporal)", + "path": "src/temporal-same-features/index.js", + "import": "{ weekNumber }" + }, { "name": "`DateTime`", "path": "src/index.js", "import": "{ DateTime }" }, + { + "name": "`DateTime` (Temporal)", + "path": "src/temporal-same-features/index.js", + "import": "{ DateTime }" + }, { "name": "**All modules** (`import *`)", "path": "src/index.js" + }, + { + "name": "**All modules** (`import *`, Temporal)", + "path": "src/temporal-same-features/index.js" } ] diff --git a/package.json b/package.json index fc98446..e09d9a3 100644 --- a/package.json +++ b/package.json @@ -73,5 +73,9 @@ "has": "npm:@nolyfill/has@latest", "hasown": "npm:@nolyfill/hasown@latest" } + }, + "peerDependencies": { + "@js-temporal/polyfill": "^0.4.4", + "temporal-polyfill": "^0.2.4" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 57e090a..8f128d4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,14 @@ overrides: has: npm:@nolyfill/has@latest hasown: npm:@nolyfill/hasown@latest +dependencies: + '@js-temporal/polyfill': + specifier: ^0.4.4 + version: 0.4.4 + temporal-polyfill: + specifier: ^0.2.4 + version: 0.2.5 + devDependencies: '@eslint/js': specifier: ^9.9.0 @@ -429,6 +437,14 @@ packages: '@jridgewell/sourcemap-codec': 1.5.0 dev: true + /@js-temporal/polyfill@0.4.4: + resolution: {integrity: sha512-2X6bvghJ/JAoZO52lbgyAPFj8uCflhTo2g7nkFzEQdXd/D8rEeD4HtmTEpmtGCva260fcd66YNXBOYdnmHqSOg==} + engines: {node: '>=12'} + dependencies: + jsbi: 4.3.0 + tslib: 2.6.3 + dev: false + /@nodelib/fs.scandir@2.1.5: resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -1749,6 +1765,10 @@ packages: argparse: 2.0.1 dev: true + /jsbi@4.3.0: + resolution: {integrity: sha512-SnZNcinB4RIcnEyZqFPdGPVgrg2AcnykiBy0sHVJQKHYeaLUvi3Exj+iaPpLnFVkDPZIV4U0yvgC9/R4uEAZ9g==} + dev: false + /json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} dev: true @@ -2443,6 +2463,16 @@ packages: engines: {node: '>= 0.4'} dev: true + /temporal-polyfill@0.2.5: + resolution: {integrity: sha512-ye47xp8Cb0nDguAhrrDS1JT1SzwEV9e26sSsrWzVu+yPZ7LzceEcH0i2gci9jWfOfSCCgM3Qv5nOYShVUUFUXA==} + dependencies: + temporal-spec: 0.2.4 + dev: false + + /temporal-spec@0.2.4: + resolution: {integrity: sha512-lDMFv4nKQrSjlkHKAlHVqKrBG4DyFfa9F74cmBZ3Iy3ed8yvWnlWSIdi4IKfSqwmazAohBNwiN64qGx4y5Q3IQ==} + dev: false + /test-exclude@7.0.1: resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} engines: {node: '>=18'} @@ -2520,6 +2550,10 @@ packages: read-pkg-up: 7.0.1 dev: true + /tslib@2.6.3: + resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==} + dev: false + /type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} diff --git a/src/datetime.js b/src/datetime.js index 15f9475..5fb17ac 100644 --- a/src/datetime.js +++ b/src/datetime.js @@ -106,7 +106,6 @@ export function datetime(date = new Date(), precision = 'day') { * - `datetime(someDate, 'somePrecision utc')` * - `utc(someDate, 'somePrecision')` * - * * @param {Date} [date=new Date()] - default to now * @param {'datetime'|'datetime second'|'datetime ms'|'time'|'second'|'ms'} [precision=datetime] * @returns {string} diff --git a/src/temporal-experimenting/README.md b/src/temporal-experimenting/README.md new file mode 100644 index 0000000..e62499f --- /dev/null +++ b/src/temporal-experimenting/README.md @@ -0,0 +1,32 @@ +> [!INFO] +> +> Work in progress document. Very drafty. + +This folder will contain the implementation of `datetime-attribute` where functions will accept [`Temporal`](https://tc39.es/proposal-temporal/docs) objects while keeping current parameters. + +It’s possible that some functions will not require to accept a `Temporal` object because what they accomplish is maybe already available as a `Temporal.{className}` feature. + +It’s also possible that new functions are created to facilitate the usage of `Temporal` objects, and the extended scope they cover in comparison with `Date`. + +## Parity between `Date` and `Temporal` implementations + +**Drafty.** + +function | in `Date` | in `Temporal` | note +---|---|---|--- +`tzOffset` | yes | yes | @todo accept a `Temporal.TimeZone` (or any temporal) and output its “real-world” timezone. +`datetime` | yes | yes | @todo evaluate +`utc` | yes | yes | @todo evaluate +`datetimeTz` | yes | yes | @todo evaluate +`DateTime` | yes | yes | @todo evaluate +`duration` | yes | yes | @todo evaluate +`daysBetween` | yes | yes | @todo evaluate +`weekNumber` | yes | yes | @todo evaluate +(config feature…)| yes | yes | @todo evaluate + +## `tzOffset` notes + +```js +const tz = new Temporal.TimeZone('-23:59') +console.log(tz.id) // -23:59 +``` diff --git a/src/temporal-same-features/README.md b/src/temporal-same-features/README.md new file mode 100644 index 0000000..c5a59f2 --- /dev/null +++ b/src/temporal-same-features/README.md @@ -0,0 +1,5 @@ +# datetime-attribute implemented with the `Temporal` API + +This folder contains the implementation of `datetime-attribute` using the [`Temporal` API](https://tc39.es/proposal-temporal/docs). + +It has feature parity with the initial `datetime-attribute`. diff --git a/src/temporal-same-features/config/datetime.js b/src/temporal-same-features/config/datetime.js new file mode 100644 index 0000000..576a79e --- /dev/null +++ b/src/temporal-same-features/config/datetime.js @@ -0,0 +1,38 @@ +/** + * @typedef {Object} DateTimeOptions + * @property {('T'|' ')} separator The separator between date and time + * (`'2024-04-21T10:34'`) can be a + * 'T' (default) or a space. + */ + +/** @type DateTimeOptions */ +export const config = { + separator: 'T', +} + +/** + * Set datetime related configuration + * + * @param {DateTimeOptions} options + * @returns {DateTimeOptions} + */ +export const setConfig = options => { + + /** + * The separator can only be 'T' or ' '. + * https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#local-dates-and-times + */ + if (options.separator != 'T' && options.separator != ' ') { + throw new Error(`Invalid timezone separator. Use 'T' or ' '.`) + } + + return Object.assign(config, options) +} + +/** + * Set separator between date and time + * + * @param {DateTimeOptions['separator']} [separator='T'] + * @returns {DateTimeOptions} + */ +export const setTimeSeparator = (separator = 'T') => setConfig({ separator }) diff --git a/src/temporal-same-features/config/datetime.test.js b/src/temporal-same-features/config/datetime.test.js new file mode 100644 index 0000000..1039d04 --- /dev/null +++ b/src/temporal-same-features/config/datetime.test.js @@ -0,0 +1,63 @@ +import { describe, expect, test } from 'vitest' + +import { datetime, setTimeSeparator } from '../index.js' + +import { setConfig } from './datetime.js' + +const togoIndependanceDay = new Date(1960, 3, 27) +const date = togoIndependanceDay // alias for the sake of brevity + +describe('setTimeSeparator', () => { + + test('is a function', () => { + expect(setTimeSeparator).toBeInstanceOf(Function) + }) + + test('throws with invalid separator', () => { + expect(() => setTimeSeparator(42)).toThrow(Error) + }) + + test('no time separator', () => { + setTimeSeparator() + expect(datetime(date, 'datetime')).toBe('1960-04-27T00:00') + }) + + test('T as time separator', () => { + setTimeSeparator('T') + expect(datetime(date, 'datetime')).toBe('1960-04-27T00:00') + }) + + test('space as time separator', () => { + setTimeSeparator(' ') + expect(datetime(date, 'datetime')).toBe('1960-04-27 00:00') + }) +}) + +describe('setConfig', () => { + + test('is a function', () => { + expect(setConfig).toBeInstanceOf(Function) + }) + + test('throws without parameter', () => { + expect(() => setConfig()).toThrow(TypeError) + }) + + test('throws without proper parameter', () => { + expect(() => setConfig({})).toThrow(Error) + }) + + test('throws with invalid separator', () => { + expect(() => setConfig({ separator: 42 })).toThrow(Error) + }) + + test('T as time separator using datetime', () => { + setConfig({ separator: 'T' }) + expect(datetime(date, 'datetime')).toBe('1960-04-27T00:00') + }) + + test('space as time separator using datetime', () => { + setConfig({ separator: ' ' }) + expect(datetime(date, 'datetime')).toBe('1960-04-27 00:00') + }) +}) diff --git a/src/temporal-same-features/config/index.js b/src/temporal-same-features/config/index.js new file mode 100644 index 0000000..c3fff3a --- /dev/null +++ b/src/temporal-same-features/config/index.js @@ -0,0 +1,6 @@ +export { setTimeSeparator } from './datetime.js' +export { + setTzConfig, + setTzInRealWorldRange, + setTzSeparator, +} from './tz.js' diff --git a/src/temporal-same-features/config/tz.js b/src/temporal-same-features/config/tz.js new file mode 100644 index 0000000..07b1084 --- /dev/null +++ b/src/temporal-same-features/config/tz.js @@ -0,0 +1,59 @@ +/** + * @typedef {Object} TimezoneOptions + * + * @property {boolean} [inRealWorldRange] Adapt the timezone value to be inside + * the real-world IANA range. `false` + * by default. + * + * @property {(':'|'')} [separator] The separator between hours and minutes + * (`'+02:00'`) can be ':' (default) or + * an empty string. + */ + +/** @type TimezoneOptions */ +export const tzConfig = { + inRealWorldRange: false, + separator: ':', +} + +const defaultConfig = { ...tzConfig } + +/** + * Set timezone related configuration + * + * @param {TimezoneOptions} options + * @returns {TimezoneOptions} + */ +export const setTzConfig = (options = defaultConfig) => { + const { separator, inRealWorldRange } = options + + /** + * The separator can only be ':' or ''. + * https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#time-zones + */ + if ('separator' in options && separator != ':' && separator != '') { + throw new Error(`Invalid timezone separator. Use ':' or ''.`) + } + + if ('inRealWorldRange' in options && typeof inRealWorldRange != 'boolean') { + throw new Error(`Invalid value for 'inRealWorldRange'. Use true or false.`) + } + + return Object.assign(tzConfig, options) +} + +/** + * Set timezone separator + * + * @param {TimezoneOptions['separator']} [separator=''] + * @returns {TimezoneOptions} + */ +export const setTzSeparator = (separator = '') => setTzConfig({ separator }) + +/** + * Shift timezones to be inside the real-world (IANA) range + * + * @param {TimezoneOptions['inRealWorldRange']} [inRealWorldRange=false] + * @returns {TimezoneOptions} + */ +export const setTzInRealWorldRange = (inRealWorldRange = false) => setTzConfig({ inRealWorldRange }) diff --git a/src/temporal-same-features/config/tz.test.js b/src/temporal-same-features/config/tz.test.js new file mode 100644 index 0000000..ea5240a --- /dev/null +++ b/src/temporal-same-features/config/tz.test.js @@ -0,0 +1,124 @@ +import { beforeEach, describe, expect, test } from 'vitest' + +import { datetimeTz, tzOffset, setTzConfig, setTzInRealWorldRange, setTzSeparator } from '../index.js' +import { tzConfig } from './tz.js' + +const togoIndependanceDay = new Date(1960, 3, 27) +const date = togoIndependanceDay // alias for the sake of brevity + +// Reset default config between each test. +const defaultConfig = { ...tzConfig } +beforeEach(() => setTzConfig(defaultConfig)) + +describe('setTzSeparator', () => { + test('is a function', () => expect(setTzSeparator).toBeInstanceOf(Function)) + + test('throws with invalid separator', () => { + expect(() => setTzSeparator(42)).toThrow(Error) + }) + + test('no timezone separator', () => { + setTzSeparator() + expect(tzOffset(3)).toBe('+0300') + }) + + test('empty string timezone separator', () => { + setTzSeparator('') + expect(tzOffset(3)).toBe('+0300') + }) + + test(': as timezone separator', () => { + setTzSeparator(':') + expect(tzOffset(3)).toBe('+03:00') + }) + + test('empty string timezone separator with datetimeTz', () => { + setTzSeparator('') + expect(datetimeTz(date, 'datetime second', -6)).toBe('1960-04-27T00:00:00-0600') + }) +}) + +describe('setTzInRealWorldRange', () => { + test('is a function', () => expect(setTzInRealWorldRange).toBeInstanceOf(Function)) + test('throws with invalid value', () => expect(() => setTzInRealWorldRange(42)).toThrow(Error)) + + test('default value disables real-world range', () => { + expect(tzOffset(20)).toBe('+20:00') + setTzInRealWorldRange() + expect(tzOffset(20)).toBe('+20:00') + }) + + test('enable real-world range', () => { + expect(tzOffset(20)).toBe('+20:00') + setTzInRealWorldRange(true) + expect(tzOffset(20)).toBe('-04:00') + }) + + test('disable real-world range', () => { + setTzInRealWorldRange(false) + expect(tzOffset(20)).toBe('+20:00') + }) + + test('enable real-world range with datetimeTz', () => { + setTzInRealWorldRange(true) + expect(datetimeTz(date, 'datetime second', 20)).toBe('1960-04-27T00:00:00-04:00') + }) +}) + +describe('setTzConfig', () => { + + describe('general', () => { + test('is a function', () => expect(setTzConfig).toBeInstanceOf(Function)) + + test('reset the default config without parameter', () => { + setTzConfig({ separator: '', inRealWorldRange: true }) + expect(tzOffset(20)).toBe('-0400') + + setTzConfig() + expect(tzOffset(20)).toBe('+20:00') + }) + }) + + describe('timezone separator', () => { + test('throws with invalid separator', () => expect(() => setTzConfig({ separator: 42 })).toThrow(Error)) + + test('empty string separator using tzOffset', () => { + setTzConfig({ separator: '' }) + expect(tzOffset(3)).toBe('+0300') + }) + + test(': as separator using tzOffset', () => { + setTzConfig({ separator: ':' }) + expect(tzOffset(3)).toBe('+03:00') + }) + + test(': as separator with datetimeTz', () => { + setTzConfig({ separator: ':' }) + expect(datetimeTz(date, 'datetime second', -6)).toBe('1960-04-27T00:00:00-06:00') + }) + }) + + describe('real-world boundaries', () => { + test('throws with invalid value', () => expect(() => setTzConfig({ inRealWorldRange: 42 })).toThrow(Error)) + + test('inside boundaries using tzOffset', () => { + setTzConfig({ inRealWorldRange: true }) + expect(tzOffset(20)).toBe('-04:00') + }) + + test('outside boundaries using tzOffset', () => { + setTzConfig({ inRealWorldRange: false }) + expect(tzOffset(20)).toBe('+20:00') + }) + + test('inside boundaries using datetimeTz', () => { + setTzConfig({ inRealWorldRange: true }) + expect(datetimeTz(date, 'datetime second', 20)).toBe('1960-04-27T00:00:00-04:00') + }) + + test('outside boundaries using datetimeTz', () => { + setTzConfig({ inRealWorldRange: false }) + expect(datetimeTz(date, 'datetime second', 20)).toBe('1960-04-27T00:00:00+20:00') + }) + }) +}) diff --git a/src/temporal-same-features/datetime-class.js b/src/temporal-same-features/datetime-class.js new file mode 100644 index 0000000..f7e0e1c --- /dev/null +++ b/src/temporal-same-features/datetime-class.js @@ -0,0 +1,54 @@ +import { MILLISECONDS_PER_WEEK } from './utils/const.js' +import { datetime, weekNumber } from './index.js' + +export class DateTime extends Date { + + /** + * Returns the week of the year (`1`–`53`) of the specified date according to + * local time, as defined by the WHATWG and the ISO-8601 specs: + * + * - a week starts on Monday (https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#concept-week); + * - the first week of the year includes a Thursday; + * - week numbers go from 1 to 53 (https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#week-number-of-the-last-day). + * + * @todo: cache the result of weekNumber as long as date isn’t changed: + * compare the current object timestamp wuth a stored/cached one in + * order to decide if the cache is stale. + * + * @returns {number} + */ + getWeek = () => weekNumber(this) + + /** + * Set the week number. + * + * If the week number is outside the year range, the `DateTime` object is + * updated accordingly. + * + * @param {number} weekNumber + * @returns {number} Milliseconds since midnight on January 1, 1970, UTC + */ + setWeek(weekNumber) { + const weeksDiffInMs = (weekNumber - this.getWeek()) * MILLISECONDS_PER_WEEK + + /** + * Temporal API is less efficient here, it’s probably better with a + * Temporal object while this class is really `Date` focused as + * it extends it. Commented code: Temporal implementation. + */ + // const temporalThis = Temporal.Instant.fromEpochMilliseconds( + // this.getTime() + weeksDiffInMs + // ) + // return this.setTime(temporalThis.epochMilliseconds) + + return this.setTime(this.getTime() + weeksDiffInMs) + } + + /** + * Converts a date to a string following the ISO 8601 Extended Format. + * + * @param {import('./datetime.js').Precision} precision + * @returns {string} + */ + to = precision => datetime(this, precision) +} diff --git a/src/temporal-same-features/datetime-class.test.js b/src/temporal-same-features/datetime-class.test.js new file mode 100644 index 0000000..c5c8748 --- /dev/null +++ b/src/temporal-same-features/datetime-class.test.js @@ -0,0 +1,70 @@ +import { beforeEach, describe, expect, test } from 'vitest' + +import { DateTime, datetime } from './index.js' +import { MILLISECONDS_PER_DAY } from './utils/const.js' + +const FOURTEEN_DAYS_IN_MS = MILLISECONDS_PER_DAY * 14 + +describe('DateTime class', () => { + /** @type {Date} */ let summer + /** @type {Date} */ let twoWeeksIntoSummer + /** @type {Date} */ let winter + /** @type {Date} */ let twoWeeksIntoWinter + + beforeEach(() => { + summer = new DateTime(2021, 5, 21) + twoWeeksIntoSummer = new DateTime(summer.getTime() + FOURTEEN_DAYS_IN_MS) // same as `new DateTime(2021, 6, 5)` + + winter = new DateTime(2021, 11, 21) + twoWeeksIntoWinter = new DateTime(winter.getTime() + FOURTEEN_DAYS_IN_MS) // same as `new DateTime(2022, 1, 4)` + }) + + test('extends the Date class', () => { + expect(summer).toBeInstanceOf(DateTime) + expect(summer).toBeInstanceOf(Date) + }) + + describe('.to() == datetime()', () => { + + test('with default precision', () => { + expect(summer.to()).toBe('2021-06-21') + expect(datetime(summer)).toBe('2021-06-21') + }) + + test(`with 'month' precision`, () => { + expect(twoWeeksIntoSummer.to('month')).toBe('2021-07') + expect(datetime(twoWeeksIntoSummer, 'month')).toBe('2021-07') + }) + + test(`with 'week' precision, after the week is changed to a new year`, () => { + winter.setDate(winter.getDate() + 14) + + expect(winter.to('week')).toBe(twoWeeksIntoWinter.to('week')) + expect(winter.to('week')).toBe('2022-W01') + expect(datetime(winter, 'week')).toBe('2022-W01') + }) + }) + + describe('.getWeek()', () => { + + test('returns the week number', () => { + expect(summer.getWeek()).toBe(25) + }) + + test('returns the week number after the date is changed', () => { + summer.setDate(summer.getDate() + 14) + + expect(summer.getWeek()).toBe(twoWeeksIntoSummer.getWeek()) + }) + + test('returns the week number after the date is changed to a new year', () => { + winter.setDate(winter.getDate() + 14) + + expect(winter.getWeek()).toBe(twoWeeksIntoWinter.getWeek()) + }) + }) + + test('.setWeek() returns the timestamp of the changed date', () => { + expect(summer.setWeek(27)).toBe(twoWeeksIntoSummer.getTime()) + }) +}) diff --git a/src/temporal-same-features/datetime.js b/src/temporal-same-features/datetime.js new file mode 100644 index 0000000..ac5c699 --- /dev/null +++ b/src/temporal-same-features/datetime.js @@ -0,0 +1,136 @@ +import { p } from './utils/string.js' +import { tzOffset } from './timezone.js' +import { config } from './config/datetime.js' +import { tzConfig } from './config/tz.js' +import { Temporal, toTemporalInstant } from '@js-temporal/polyfill' +// import { Temporal, toTemporalInstant } from 'temporal-polyfill' + +Date.prototype.toTemporalInstant = toTemporalInstant + +/** + * Create `datetime="2021-12-02"` attribute for `