├── .gitignore ├── .npmignore ├── .github └── workflows │ ├── unit-tests.yml │ └── release.yml ├── package.json ├── test ├── temporal.js └── utils.js ├── parse.test.js ├── README.md └── parse.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .* 2 | *.test.* 3 | test/* -------------------------------------------------------------------------------- /.github/workflows/unit-tests.yml: -------------------------------------------------------------------------------- 1 | name: Unit Tests 2 | on: [push, pull_request] 3 | permissions: read-all 4 | jobs: 5 | vitest: 6 | name: Vitest on ${{ matrix.os }} (Node.js v${{ matrix.node }}) 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | matrix: 10 | os: ["ubuntu-latest", "macos-latest", "windows-latest"] 11 | node: ["20", "22", "24"] 12 | steps: 13 | - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # 4.1.7 14 | - name: Setup node 15 | uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # 4.0.3 16 | with: 17 | node-version: ${{ matrix.node }} 18 | - run: npm ci 19 | - run: npm test 20 | env: 21 | YARN_GPG: no 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@11ty/parse-date-strings", 3 | "version": "2.0.6", 4 | "description": "Parsing content date strings in Eleventy core.", 5 | "type": "module", 6 | "main": "parse.js", 7 | "scripts": { 8 | "test": "vitest" 9 | }, 10 | "engines": { 11 | "node": ">=20" 12 | }, 13 | "funding": { 14 | "type": "opencollective", 15 | "url": "https://opencollective.com/11ty" 16 | }, 17 | "publishConfig": { 18 | "access": "public" 19 | }, 20 | "license": "MIT", 21 | "devDependencies": { 22 | "luxon": "^3.7.2", 23 | "temporal-polyfill": "^0.3.0", 24 | "vitest": "^4.0.15" 25 | }, 26 | "author": "Zach Leatherman (https://zachleat.com/)", 27 | "repository": { 28 | "type": "git", 29 | "url": "git://github.com/11ty/parse-date-strings.git" 30 | }, 31 | "bugs": "https://github.com/11ty/parse-date-strings/issues", 32 | "homepage": "https://www.11ty.dev/" 33 | } 34 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Publish Release to npm 2 | on: 3 | release: 4 | types: [published] 5 | permissions: read-all 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | environment: GitHub Publish 10 | permissions: 11 | contents: read 12 | id-token: write 13 | steps: 14 | - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # 4.1.7 15 | - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # 4.0.3 16 | with: 17 | node-version: "22" 18 | registry-url: 'https://registry.npmjs.org' 19 | - run: npm install -g npm@latest 20 | - run: npm ci 21 | - run: npm test 22 | - if: ${{ github.event.release.tag_name != '' && env.NPM_PUBLISH_TAG != '' }} 23 | run: npm publish --provenance --access=public --tag=${{ env.NPM_PUBLISH_TAG }} 24 | env: 25 | NPM_PUBLISH_TAG: ${{ contains(github.event.release.tag_name, '-beta.') && 'beta' || contains(github.event.release.tag_name, '-alpha.') && 'alpha' || 'latest' }} 26 | -------------------------------------------------------------------------------- /test/temporal.js: -------------------------------------------------------------------------------- 1 | import { Temporal } from 'temporal-polyfill'; 2 | 3 | const TIMEZONE_REGEX = /([+-]\d{2}):?(\d{2})?$/; 4 | 5 | function convertTemporalToTimestamp(d) { 6 | // for Temporal.Instant 7 | if(d.epochMilliseconds) { 8 | return d.epochMilliseconds; 9 | } 10 | 11 | // PlainDate and PlainDateTime 12 | return Date.UTC(d.year, d.month-1, d.day, d.hour || 0, d.minute || 0, d.second || 0, d.millisecond || 0); 13 | } 14 | 15 | export function parseDate(str) { 16 | let d = Temporal.PlainDate.from(str, { 17 | overflow: "reject" 18 | }); 19 | return new Date(convertTemporalToTimestamp(d)).toUTCString(); 20 | } 21 | 22 | export function parseDateTime(str) { 23 | let d; 24 | if(str.endsWith("Z") || str.match(TIMEZONE_REGEX)) { 25 | d = Temporal.Instant.from(str, { 26 | overflow: "reject" 27 | }); 28 | } else { 29 | d = Temporal.PlainDateTime.from(str, { 30 | overflow: "reject" 31 | }); 32 | } 33 | 34 | let timestamp = convertTemporalToTimestamp(d); 35 | return new Date(timestamp).toUTCString(); 36 | } 37 | 38 | export function parse(str) { 39 | let result; 40 | let errors = []; 41 | try { 42 | result = parseDate(str); 43 | } catch(e) { 44 | errors.push(e); 45 | } 46 | try { 47 | result = parseDateTime(str); 48 | } catch(e) { 49 | errors.push(e); 50 | } 51 | 52 | // if both methods resulted in errors 53 | if(errors.length === 2) { 54 | throw errors.pop(); 55 | } 56 | 57 | return result; 58 | } 59 | -------------------------------------------------------------------------------- /parse.test.js: -------------------------------------------------------------------------------- 1 | import { assert, test } from 'vitest'; 2 | import { DateTime } from 'luxon'; 3 | import { IsoDate, parse } from "./parse.js"; 4 | import { parse as temporalParse } from "./test/temporal.js"; 5 | 6 | // This test suite compares with Luxon output for maximum backwards compatibility 7 | import { shouldSkip, VALID_TEST_CASES, VALID_BUT_INVALID_IN_LUXON_TEST_CASES, INVALID_TEST_CASES, SUPPLIED_TEST_CASES } from './test/utils.js'; 8 | 9 | // Some test cases from https://moment.github.io/luxon/#/parsing?id=ad-hoc-parsing 10 | // ISO8601 date parsing https://github.com/11ty/eleventy/issues/3587 11 | for(let line of VALID_TEST_CASES.split("\n")) { 12 | if(shouldSkip(line)) { 13 | continue; 14 | } 15 | 16 | test(`Parse ${line}`, () => { 17 | // assert.equal(received, expected) 18 | 19 | // Compare to luxon 20 | assert.equal(parse(line).toUTCString(), DateTime.fromISO(line, {zone: "utc"}).toJSDate().toUTCString(), `Invalid compared to luxon for '${line}'`); 21 | 22 | // Compare to Temporal 23 | assert.equal(parse(line).toUTCString(), temporalParse(line).toString(), `Invalid compared to Temporal for '${line}'`); 24 | }); 25 | } 26 | 27 | for(let line of VALID_BUT_INVALID_IN_LUXON_TEST_CASES.split("\n")) { 28 | if(shouldSkip(line)) { 29 | continue; 30 | } 31 | 32 | test(`Parse ${line}`, () => { 33 | // assert.equal(received, expected) 34 | 35 | // Should not equal luxon 36 | assert.notEqual(parse(line).toUTCString(), DateTime.fromISO(line, {zone: "utc"}).toJSDate().toUTCString(), `Should not match luxon for '${line}'`); 37 | 38 | // Compare to Temporal 39 | assert.equal(parse(line).toUTCString(), temporalParse(line).toString(), `Invalid compared to Temporal for '${line}'`); 40 | }); 41 | } 42 | 43 | // These test cases are expected to fail 44 | for(let line of INVALID_TEST_CASES.split("\n")) { 45 | if(shouldSkip(line)) { 46 | continue; 47 | } 48 | 49 | test(`Bad syntax: ${line}`, () => { 50 | try { 51 | parse(line); 52 | assert.isTrue(false); 53 | } catch(e) { 54 | assert.isTrue(e.message.startsWith("Unsupported date format")) 55 | assert.isTrue(e.message.endsWith(line)) 56 | } 57 | }); 58 | 59 | test(`Bad syntax: ${line} (Temporal)`, () => { 60 | try { 61 | temporalParse(line); 62 | assert.isTrue(false); 63 | } catch(e) { 64 | assert.isTrue(e instanceof Error) 65 | } 66 | }); 67 | } 68 | 69 | for(let line of SUPPLIED_TEST_CASES.split("\n")) { 70 | if(shouldSkip(line)) { 71 | continue; 72 | } 73 | 74 | test(`Parse ${line}`, () => { 75 | // assert.equal(received, expected) 76 | 77 | // Compare to luxon 78 | assert.equal(parse(line).toUTCString(), DateTime.fromISO(line, {zone: "utc"}).toJSDate().toUTCString()); 79 | 80 | // Compare to Temporal 81 | assert.equal(parse(line).toUTCString(), temporalParse(line).toString()); 82 | }); 83 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `@11ty/parse-date-strings` 2 | 3 | Features: 4 | 5 | - Zero dependency super minimal [RFC 9557](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainDateTime#rfc_9557_format)-compatible date parsing library. Date strings are also parseable by `Temporal.PlainDate.from`, `Temporal.Instant.from`, or `Temporal.PlainDateTime.from` to prepare for wider [Temporal API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal) support. 6 | - Alternatives: [`temporal-polyfill`](https://github.com/fullcalendar/temporal-polyfill) or [`js-temporal/temporal-polyfill`](https://github.com/js-temporal/temporal-polyfill) 7 | - Invalid strings throw errors. 8 | - Time zone notes: 9 | - Defaults to UTC when time zone is unknown (*not* local time). This matches previous behavior in Eleventy and this feature maintains consistency between build and deploy servers. 10 | - Supports `+00`, `+00:00`, `-00`, or `-00:00` style time zone offsets (`:` delimiter is optional) 11 | - Delimiter notes: 12 | - *Requires* a `T` or `t` or ` ` (space) delimiter with DateTime strings 13 | - Delimiters in dates (`-`) and times (`:`) are optional 14 | 15 | Not supported (for RFC 9557 compatibility): 16 | 17 | - Standalone time formats are *not* supported (must be Date or DateTime) 18 | - [ISO week date syntax](https://en.wikipedia.org/wiki/ISO_week_date) is *not* supported (e.g. `YYYY-W01-1`) 19 | - Day of year syntax is *not* supported (e.g. `YYYY-001`, `YYYY-365`) 20 | - Fractional hours or minutes (e.g. `T14.6` or `T10:30.6`) are *not* supported. Fractional seconds (e.g. milliseconds) are supported (of course). 21 | 22 | ## Usage 23 | 24 | ``` 25 | npm install @11ty/parse-date-strings 26 | ``` 27 | 28 | ```js 29 | import { parse } from "@11ty/parse-date-strings 30 | 31 | // `parse` returns JavaScript Date 32 | 33 | parse("2000-01-01") instanceof Date 34 | // true 35 | 36 | parse("2000-01-01").toUTCString() 37 | // "Mon, 01 Jan 2001 00:00:00 GMT" 38 | ``` 39 | 40 | ## Comparisons 41 | 42 | ### `luxon` 43 | 44 | More strict parsing compared with [Luxon’s `fromISO`](https://moment.github.io/luxon/#/parsing?id=iso-8601) (used in Eleventy v0.x through v3): 45 | 46 | ``` 47 | 2016-05-25 48 | 20160525 49 | 2016-05-25T09 50 | 2016-05-25T09:24 51 | 2016-05-25T09:24:15 52 | 2016-05-25T09:24:15.123 53 | 2016-05-25T0924 54 | 2016-05-25T092415 55 | 2016-05-25T092415.123 56 | 2016-05-25T09:24:15,123 57 | 58 | # No YYYY or YYYYMM syntax 59 | 2016 # Dropped 60 | 2016-05 # Dropped 61 | 201605 # Dropped 62 | 63 | # No ISO week date syntax 64 | 2016-W21-3 # Dropped 65 | 2016W213 # Dropped 66 | 2016-W21-3T09:24:15.123 # Dropped 67 | 2016W213T09:24:15.123 # Dropped 68 | 69 | # No day of year syntax (e.g. 200th day of the year) 70 | 2016-200 # Dropped 71 | 2016200 # Dropped 72 | 2016-200T09:24:15.123 # Dropped 73 | 74 | # No implied current day (time-only syntax) 75 | 09:24 # Dropped 76 | 09:24:15 # Dropped 77 | 09:24:15.123 # Dropped 78 | 09:24:15,123 # Dropped 79 | ``` -------------------------------------------------------------------------------- /parse.js: -------------------------------------------------------------------------------- 1 | /** 2 | * _Times are parsed as UTC if no offset is specified_ 3 | */ 4 | export class IsoDateParts { 5 | static FULL_DATE_REGEX = /^([+-]\d{6}|\d{4})-?([01]\d)-?([0-3]\d)$/; 6 | static DATETIME_REGEX = /^([+-]\d{6}|\d{4})-?([01]\d)-?([0-3]\d)[Tt ]([0-2]\d(?:[\.\,]\d+)?)(?::?([0-5]\d(?:[\.\,]\d+)?)(?::?([0-5]\d))?(?:[\.\,](\d{1,9}))?)?(Z|[+-][0-2]\d(?::?[0-5]\d)?)?$/; 7 | static TIMEZONE_REGEX = /^([+-]\d{2})(?::?(\d{2}))?$/; 8 | static IS_FRACTIONAL_REGEX = /^\d+[\.\,]\d+$/; 9 | 10 | static getTimezoneOffset(offset = "Z") { 11 | let [, hours = "0", minutes = "0"] = offset.match(this.TIMEZONE_REGEX) ?? []; 12 | 13 | let sign = hours[0] === '-' ? -1 : 1; 14 | return { 15 | hours: parseInt(hours, 10), 16 | minutes: parseInt(minutes, 10) * sign 17 | }; 18 | } 19 | 20 | /** @param {RegExpMatchArray} match */ 21 | static getByDateTime( 22 | _, // full match 23 | year = "", 24 | month = "0", // 0-indexed default 25 | day = "1", // 1-indexed default 26 | hours = "0", 27 | minutes = "0", 28 | seconds = "0", 29 | milliseconds = "0", 30 | timezone = "Z", 31 | ) { 32 | let offset = this.getTimezoneOffset(timezone); 33 | 34 | return { 35 | year: parseInt(year, 10), 36 | month: parseInt(month, 10) - 1, 37 | day: parseInt(day, 10), 38 | hours: parseInt(hours, 10) - offset.hours, 39 | minutes: parseInt(minutes, 10) - offset.minutes, 40 | seconds: parseInt(seconds, 10), 41 | // may include extra precision but we only count the first 3 digits for milliseconds 42 | milliseconds: parseInt(milliseconds.slice(0, 3), 10), 43 | }; 44 | } 45 | 46 | /** @param {string} str An [RFC 9557](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainDateTime#rfc_9557_format)-compatible string */ 47 | static getParts(str = "") { 48 | let dateTimeMatch = str.match(this.FULL_DATE_REGEX) ?? str.match(this.DATETIME_REGEX); 49 | if(!dateTimeMatch) { 50 | throw new Error(`Unsupported date format: ${str}`); 51 | } 52 | if(dateTimeMatch.slice(4,6).some(part => !!part?.match(this.IS_FRACTIONAL_REGEX))) { 53 | throw new Error(`Unsupported date format (fractional hours or minutes): ${str}`); 54 | } 55 | 56 | return this.getByDateTime(...dateTimeMatch); 57 | } 58 | } 59 | 60 | export class IsoDate { 61 | /** @type {number} */ 62 | year; 63 | /** @type {number} */ 64 | month; 65 | /** @type {number} */ 66 | day; 67 | /** @type {number} */ 68 | hours; 69 | /** @type {number} */ 70 | minutes; 71 | /** @type {number} */ 72 | seconds; 73 | /** @type {number} */ 74 | milliseconds; 75 | /** @type {string} */ 76 | source; 77 | 78 | /** @param {string} str An [RFC 9557](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainDateTime#rfc_9557_format)-compatible string */ 79 | static parse(str) { 80 | let parts = IsoDateParts.getParts(str); 81 | if(parts) { 82 | let iso = new IsoDate(parts); 83 | iso.source = str; 84 | iso.checkParts(); 85 | 86 | return new Date(Date.UTC(...iso.getArgs())); 87 | } 88 | 89 | throw new Error(`Unsupported date format: ${str}`); 90 | } 91 | 92 | /** 93 | * @param {object} parts 94 | * @param {number} parts.year 95 | * @param {number} parts.month 96 | * @param {number} parts.day 97 | * @param {number} parts.hours 98 | * @param {number} parts.minutes 99 | * @param {number} parts.seconds 100 | * @param {number} parts.milliseconds 101 | */ 102 | constructor(parts) { 103 | // parts.day, parts.year, parts.month 104 | Object.assign(this, parts); 105 | } 106 | 107 | /** @returns {[number, number, number, number, number, number, number]} */ 108 | getArgs() { 109 | return [this.year, this.month, this.day, this.hours, this.minutes, this.seconds, this.milliseconds]; 110 | } 111 | 112 | checkParts() { 113 | // months: 0-indexed, 0–11 are valid 114 | if(this.month < 0 || this.month > 11) { 115 | throw new Error(`Unsupported date format (invalid month): ${this.source}`); 116 | } 117 | 118 | // check if days are too big 119 | if(this.day < 1 || new Date(Date.UTC(this.year, this.month, 1)).getUTCMonth() !== new Date(Date.UTC(this.year, this.month, this.day)).getUTCMonth()) { 120 | throw new Error(`Unsupported date format (invalid days for month): ${this.source}`); 121 | } 122 | } 123 | } 124 | 125 | /** @param {string} str An [RFC 9557](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainDateTime#rfc_9557_format)-compatible string */ 126 | export function parse(str) { 127 | return IsoDate.parse(str); 128 | } -------------------------------------------------------------------------------- /test/utils.js: -------------------------------------------------------------------------------- 1 | export function shouldSkip(line) { 2 | line = line.trim(); 3 | return !line || line.startsWith("//"); 4 | } 5 | 6 | // Expected to pass 7 | export const VALID_TEST_CASES = ` 8 | 2016-05-25 9 | 2000-01-07 10 | 2016-05-25T09 11 | 2016-05-25T09:24 12 | 2016-05-25T09:24:15 13 | 2016-05-25T09:24:15.123 14 | 2016-05-25T09:24:15,123 15 | 2016-05-25T09Z 16 | 2016-05-25T09:24Z 17 | 2016-05-25T09:24:15Z 18 | 2016-05-25T09:24:15.123Z 19 | 2016-05-25T09:24:15,123Z 20 | 2016-05-25T09+00:00 21 | 2016-05-25T09:24+00:00 22 | 2016-05-25T09:24:15+00:00 23 | 2016-05-25T09:24:15.123+00:00 24 | 2016-05-25T09:24:15,123+00:00 25 | 2016-05-25T09+01:00 26 | 2016-05-25T09:24+01:00 27 | 2016-05-25T09:24:15+01:00 28 | 2016-05-25T09:24:15+01:15 29 | 2016-05-25T09:24:15.123+01:00 30 | 2016-05-25T09:24:15,123+01:00 31 | 2016-05-25T09-01:00 32 | 2016-05-25T09:24-01:00 33 | 2016-05-25T09:24:15-01:00 34 | 2016-05-25T09:24:15-01:15 35 | 2016-05-25T09:24:15.123-01:00 36 | 2016-05-25T09:24:15,123-01:00 37 | 2016-05-25T09+06:00 38 | 2016-05-25T09:24+06:00 39 | 2016-05-25T09:24:15+06:00 40 | 2016-05-25T09:24:15.123+06:00 41 | 2016-05-25T09:24:15,123+06:00 42 | 2016-05-25T09-06:00 43 | 2016-05-25T09:24-06:00 44 | 2016-05-25T09:24:15-06:00 45 | 2016-05-25T09:24:15.123-06:00 46 | 2016-05-25T09:24:15,123-06:00 47 | 48 | // delimiters optional 49 | 20000107 50 | 20160525 51 | 52 | 2016-0525 53 | 201605-25 54 | 20160525T09:2415 55 | 20160525T0924:15 56 | 57 | 20160525T092415 58 | 20160525T092415.123 59 | 20160525T092415,123 60 | 20160525T0924 61 | 62 | 20160525T09:24 63 | 20160525T09:24:15 64 | 20160525T09:24:15.123 65 | 20160525T09:24:15,123 66 | 67 | 2016-05-25T092415 68 | 2016-05-25T092415.123 69 | 2016-05-25T092415,123 70 | 2016-05-25T0924 71 | 72 | // Inspired by supplied test cases 73 | 2025-07-22T05:11:40+0845 74 | 75 | // Issue #1 76 | 2016-05-25T09:24:15+06 77 | 2016-05-25T09:24:15+0600 78 | 2016-05-25T09:24:15+0615 79 | 2016-05-25T09:24:15-06 80 | 2016-05-25T09:24:15-0600 81 | 2016-05-25T09:24:15-0615 82 | 83 | // 6 digit years 84 | +002000-01-01 85 | -002000-01-01 86 | +0020000101 87 | -0020000101 88 | 89 | // case insensitive delimiter (RFC 9557-valid) 90 | 2016-05-25t12:00:00 91 | `; 92 | 93 | export const VALID_BUT_INVALID_IN_LUXON_TEST_CASES = ` 94 | // space delimiter (RFC 9557-valid) 95 | 2016-05-25 12:00:00 96 | ` 97 | 98 | // Expected to fail 99 | export const INVALID_TEST_CASES = ` 100 | 2016 101 | 2016-05 102 | 103 | // Not a real date 104 | 2016-05-99 105 | 2016-05-00 106 | 2016-00-00 107 | 108 | // Support removed (missing delimiters) 109 | 201605 110 | 202618 111 | 2016-05-2501 112 | 2016-05-25T01: 113 | 114 | // Bad syntax, number of time digits 115 | 2016-05-25T1:11 116 | 2016-05-25T1:1:1 117 | 2016-05-25T01:11:1 118 | 2016-05-25T1 119 | 2016-05-25T111 120 | 2016-05-25T11111 121 | 2016-05-25T1111111 122 | 123 | // Fractional hours 124 | 20250721T15.6 125 | 20250721T15,6 126 | 20250721T15.6-0500 127 | 20250721T15,6-0500 128 | 129 | // Fractional minutes 130 | 20250721T1526.6 131 | 20250721T1526,6 132 | 20250721T1526.6-0500 133 | 20250721T1526,6-0500 134 | 135 | // Support removed (Week date) 136 | 2016-W01-1 137 | 138 | // Support removed (Year day) 139 | 2016-200 140 | 2016200 141 | 2016-200 142 | 143 | // Support removed (Time only) 144 | 09:24 145 | 09:24:15 146 | 09:24:15.123 147 | 09:24:15,123 148 | 149 | // Ten digit precision not supported (RFC 9557) 150 | 20250721T152640.3965097519 151 | 20250721T152640,3965097519 152 | ` 153 | 154 | // Some Date/DateTime tests cribbed from https://ijmacd.github.io/rfc3339-iso8601/ 155 | // More info: https://ijmacd.github.io/iso8601/ 156 | // Invalid syntax is commented out (not removed) for posterity 157 | export const SUPPLIED_TEST_CASES = ` 158 | // # Dates 159 | 2025-07-21 160 | // 20 161 | // 202 162 | // 2025 163 | // 2025-07 164 | // 2025-202 165 | // 2025-W30 166 | // 2025-W30-1 167 | 20250721 168 | // 2025202 169 | // 2025W30 170 | // 2025W301 171 | 172 | // # Date-Times 173 | 2025-07-21T20:26:40Z 174 | 2025-07-21T20:26:40.3Z 175 | 2025-07-21T20:26:40.39Z 176 | 2025-07-21T20:26:40.396Z 177 | 2025-07-21T20:26:40.396510Z 178 | 2025-07-21T20:26:40.396509751Z 179 | 2025-07-21T15:26:40-05:00 180 | 2025-07-21T15:26:40.3-05:00 181 | 2025-07-21T15:26:40.39-05:00 182 | 2025-07-21T15:26:40.396-05:00 183 | 2025-07-21T15:26:40.396510-05:00 184 | 2025-07-21T15:26:40.396509751-05:00 185 | 2025-07-22T05:11:40+08:45 186 | 2025-07-21T20:26:40+00:00 187 | 2025-07-21T20:26:40.396+00:00 188 | 2025-07-21T15 189 | // 2025-07-21T15,4 190 | // 2025-07-21T15.4 191 | 2025-07-21T15:26 192 | // 2025-07-21T15:26,6 193 | // 2025-07-21T15:26.6 194 | 2025-07-21T15:26:40 195 | 2025-07-21T15:26:40.3 196 | 2025-07-21T15:26:40.39 197 | 2025-07-21T15:26:40,396 198 | 2025-07-21T15:26:40.396 199 | 2025-07-21T15:26:40,396510 200 | 2025-07-21T15:26:40.396510 201 | 2025-07-21T15:26:40.396509751 202 | 2025-07-21T15:26:40,396509751 203 | 2025-07-21T20Z 204 | // 2025-07-21T20,4Z 205 | // 2025-07-21T20.4Z 206 | 2025-07-21T20:26Z 207 | // 2025-07-21T20:26,6Z 208 | // 2025-07-21T20:26.6Z 209 | 2025-07-21T20:26:40,396Z 210 | 2025-07-21T20:26:40,396510Z 211 | 2025-07-21T20:26:40,396509751Z 212 | 2025-07-21T15-05 213 | // 2025-07-21T15,4-05 214 | // 2025-07-21T15.4-05 215 | 2025-07-21T15:26-05 216 | // 2025-07-21T15:26,6-05 217 | // 2025-07-21T15:26.6-05 218 | 2025-07-21T15:26:40-05 219 | 2025-07-21T15:26:40.3-05 220 | 2025-07-21T15:26:40.39-05 221 | 2025-07-21T15:26:40,396-05 222 | 2025-07-21T15:26:40.396-05 223 | 2025-07-21T15:26:40,396510-05 224 | 2025-07-21T15:26:40.396510-05 225 | 2025-07-21T15:26:40.396509751-05 226 | 2025-07-21T15:26:40,396509751-05 227 | 2025-07-21T15-05:00 228 | // 2025-07-21T15,4-05:00 229 | // 2025-07-21T15.4-05:00 230 | 2025-07-21T15:26-05:00 231 | // 2025-07-21T15:26,6-05:00 232 | // 2025-07-21T15:26.6-05:00 233 | 2025-07-21T15:26:40,396-05:00 234 | 2025-07-21T15:26:40,396510-05:00 235 | 2025-07-21T15:26:40,396509751-05:00 236 | 237 | // Week date syntax 238 | // 2025-W30-1T15 239 | // 2025-W30-1T15,4 240 | // 2025-W30-1T15.4 241 | // 2025-W30-1T15:26 242 | // 2025-W30-1T15:26,6 243 | // 2025-W30-1T15:26.6 244 | // 2025-W30-1T15:26:40 245 | // 2025-W30-1T15:26:40.3 246 | // 2025-W30-1T15:26:40.39 247 | // 2025-W30-1T15:26:40,396 248 | // 2025-W30-1T15:26:40.396 249 | // 2025-W30-1T15:26:40,396510 250 | // 2025-W30-1T15:26:40.396510 251 | // 2025-W30-1T15:26:40.396509751 252 | // 2025-W30-1T15:26:40,396509751 253 | // 2025-W30-1T20Z 254 | // 2025-W30-1T20,4Z 255 | // 2025-W30-1T20.4Z 256 | // 2025-W30-1T20:26Z 257 | // 2025-W30-1T20:26,6Z 258 | // 2025-W30-1T20:26.6Z 259 | // 2025-W30-1T20:26:40Z 260 | // 2025-W30-1T20:26:40.3Z 261 | // 2025-W30-1T20:26:40.39Z 262 | // 2025-W30-1T20:26:40,396Z 263 | // 2025-W30-1T20:26:40.396Z 264 | // 2025-W30-1T20:26:40,396510Z 265 | // 2025-W30-1T20:26:40.396510Z 266 | // 2025-W30-1T20:26:40.396509751Z 267 | // 2025-W30-1T20:26:40,396509751Z 268 | // 2025-W30-1T15-05 269 | // 2025-W30-1T15,4-05 270 | // 2025-W30-1T15.4-05 271 | // 2025-W30-1T15:26-05 272 | // 2025-W30-1T15:26,6-05 273 | // 2025-W30-1T15:26.6-05 274 | // 2025-W30-1T15:26:40-05 275 | // 2025-W30-1T15:26:40.3-05 276 | // 2025-W30-1T15:26:40.39-05 277 | // 2025-W30-1T15:26:40,396-05 278 | // 2025-W30-1T15:26:40.396-05 279 | // 2025-W30-1T15:26:40,396510-05 280 | // 2025-W30-1T15:26:40.396510-05 281 | // 2025-W30-1T15:26:40.396509751-05 282 | // 2025-W30-1T15:26:40,396509751-05 283 | // 2025-W30-1T15-05:00 284 | // 2025-W30-1T15,4-05:00 285 | // 2025-W30-1T15.4-05:00 286 | // 2025-W30-1T15:26-05:00 287 | // 2025-W30-1T15:26,6-05:00 288 | // 2025-W30-1T15:26.6-05:00 289 | // 2025-W30-1T15:26:40-05:00 290 | // 2025-W30-1T15:26:40.3-05:00 291 | // 2025-W30-1T15:26:40.39-05:00 292 | // 2025-W30-1T15:26:40,396-05:00 293 | // 2025-W30-1T15:26:40.396-05:00 294 | // 2025-W30-1T15:26:40,396510-05:00 295 | // 2025-W30-1T15:26:40.396510-05:00 296 | // 2025-W30-1T15:26:40.396509751-05:00 297 | // 2025-W30-1T15:26:40,396509751-05:00 298 | 299 | // Day of year syntax 300 | // 2025-202T15 301 | // 2025-202T15,4 302 | // 2025-202T15.4 303 | // 2025-202T15:26 304 | // 2025-202T15:26,6 305 | // 2025-202T15:26.6 306 | // 2025-202T15:26:40 307 | // 2025-202T15:26:40.3 308 | // 2025-202T15:26:40.39 309 | // 2025-202T15:26:40,396 310 | // 2025-202T15:26:40.396 311 | // 2025-202T15:26:40,396510 312 | // 2025-202T15:26:40.396510 313 | // 2025-202T15:26:40.396509751 314 | // 2025-202T15:26:40,396509751 315 | // 2025-202T20Z 316 | // 2025-202T20,4Z 317 | // 2025-202T20.4Z 318 | // 2025-202T20:26Z 319 | // 2025-202T20:26,6Z 320 | // 2025-202T20:26.6Z 321 | // 2025-202T20:26:40Z 322 | // 2025-202T20:26:40.3Z 323 | // 2025-202T20:26:40.39Z 324 | // 2025-202T20:26:40,396Z 325 | // 2025-202T20:26:40.396Z 326 | // 2025-202T20:26:40,396510Z 327 | // 2025-202T20:26:40.396510Z 328 | // 2025-202T20:26:40.396509751Z 329 | // 2025-202T20:26:40,396509751Z 330 | // 2025-202T15-05 331 | // 2025-202T15,4-05 332 | // 2025-202T15.4-05 333 | // 2025-202T15:26-05 334 | // 2025-202T15:26,6-05 335 | // 2025-202T15:26.6-05 336 | // 2025-202T15:26:40-05 337 | // 2025-202T15:26:40.3-05 338 | // 2025-202T15:26:40.39-05 339 | // 2025-202T15:26:40,396-05 340 | // 2025-202T15:26:40.396-05 341 | // 2025-202T15:26:40,396510-05 342 | // 2025-202T15:26:40.396510-05 343 | // 2025-202T15:26:40.396509751-05 344 | // 2025-202T15:26:40,396509751-05 345 | // 2025-202T15-05:00 346 | // 2025-202T15,4-05:00 347 | // 2025-202T15.4-05:00 348 | // 2025-202T15:26-05:00 349 | // 2025-202T15:26,6-05:00 350 | // 2025-202T15:26.6-05:00 351 | // 2025-202T15:26:40-05:00 352 | // 2025-202T15:26:40.3-05:00 353 | // 2025-202T15:26:40.39-05:00 354 | // 2025-202T15:26:40,396-05:00 355 | // 2025-202T15:26:40.396-05:00 356 | // 2025-202T15:26:40,396510-05:00 357 | // 2025-202T15:26:40.396510-05:00 358 | // 2025-202T15:26:40.396509751-05:00 359 | // 2025-202T15:26:40,396509751-05:00 360 | 361 | 20250721T15 362 | // 20250721T15,4 363 | // 20250721T15.4 364 | 20250721T1526 365 | // 20250721T1526,6 366 | // 20250721T1526.6 367 | 20250721T152640 368 | 20250721T152640.3 369 | 20250721T152640.39 370 | 20250721T152640,396 371 | 20250721T152640.396 372 | 20250721T152640,396510 373 | 20250721T152640.396510 374 | 20250721T152640.396509751 375 | 20250721T152640,396509751 376 | 20250721T20Z 377 | // 20250721T20,4Z 378 | // 20250721T20.4Z 379 | 20250721T2026Z 380 | // 20250721T2026,6Z 381 | // 20250721T2026.6Z 382 | 20250721T202640Z 383 | 20250721T202640.3Z 384 | 20250721T202640.39Z 385 | 20250721T202640,396Z 386 | 20250721T202640.396Z 387 | 20250721T202640,396510Z 388 | 20250721T202640.396510Z 389 | 20250721T202640.396509751Z 390 | 20250721T202640,396509751Z 391 | 20250721T15-05 392 | // 20250721T15,4-05 393 | // 20250721T15.4-05 394 | 20250721T1526-05 395 | // 20250721T1526,6-05 396 | // 20250721T1526.6-05 397 | 20250721T152640-05 398 | 20250721T152640.3-05 399 | 20250721T152640.39-05 400 | 20250721T152640,396-05 401 | 20250721T152640.396-05 402 | 20250721T152640,396510-05 403 | 20250721T152640.396510-05 404 | 20250721T152640.396509751-05 405 | 20250721T152640,396509751-05 406 | 20250721T15-0500 407 | // 20250721T15,4-0500 408 | // 20250721T15.4-0500 409 | 20250721T1526-0500 410 | // 20250721T1526,6-0500 411 | // 20250721T1526.6-0500 412 | 20250721T152640-0500 413 | 20250721T152640.3-0500 414 | 20250721T152640.39-0500 415 | 20250721T152640,396-0500 416 | 20250721T152640.396-0500 417 | 20250721T152640,396510-0500 418 | 20250721T152640.396510-0500 419 | 20250721T152640.396509751-0500 420 | 20250721T152640,396509751-0500 421 | 422 | // Week date syntax 423 | // 2025W301T15 424 | // 2025W301T15,4 425 | // 2025W301T15.4 426 | // 2025W301T1526 427 | // 2025W301T1526,6 428 | // 2025W301T1526.6 429 | // 2025W301T152640 430 | // 2025W301T152640.3 431 | // 2025W301T152640.39 432 | // 2025W301T152640,396 433 | // 2025W301T152640.396 434 | // 2025W301T152640,396510 435 | // 2025W301T152640.396510 436 | // 2025W301T152640.396509751 437 | // 2025W301T152640,396509751 438 | // 2025W301T20Z 439 | // 2025W301T20,4Z 440 | // 2025W301T20.4Z 441 | // 2025W301T2026Z 442 | // 2025W301T2026,6Z 443 | // 2025W301T2026.6Z 444 | // 2025W301T202640Z 445 | // 2025W301T202640.3Z 446 | // 2025W301T202640.39Z 447 | // 2025W301T202640,396Z 448 | // 2025W301T202640.396Z 449 | // 2025W301T202640,396510Z 450 | // 2025W301T202640.396510Z 451 | // 2025W301T202640.396509751Z 452 | // 2025W301T202640,396509751Z 453 | // 2025W301T15-05 454 | // 2025W301T15,4-05 455 | // 2025W301T15.4-05 456 | // 2025W301T1526-05 457 | // 2025W301T1526,6-05 458 | // 2025W301T1526.6-05 459 | // 2025W301T152640-05 460 | // 2025W301T152640.3-05 461 | // 2025W301T152640.39-05 462 | // 2025W301T152640,396-05 463 | // 2025W301T152640.396-05 464 | // 2025W301T152640,396510-05 465 | // 2025W301T152640.396510-05 466 | // 2025W301T152640.396509751-05 467 | // 2025W301T152640,396509751-05 468 | // 2025W301T15-0500 469 | // 2025W301T15,4-0500 470 | // 2025W301T15.4-0500 471 | // 2025W301T1526-0500 472 | // 2025W301T1526,6-0500 473 | // 2025W301T1526.6-0500 474 | // 2025W301T152640-0500 475 | // 2025W301T152640.3-0500 476 | // 2025W301T152640.39-0500 477 | // 2025W301T152640,396-0500 478 | // 2025W301T152640.396-0500 479 | // 2025W301T152640,396510-0500 480 | // 2025W301T152640.396510-0500 481 | // 2025W301T152640.396509751-0500 482 | // 2025W301T152640,396509751-0500 483 | 484 | // Day of year syntax 485 | // 2025202T15 486 | // 2025202T15,4 487 | // 2025202T15.4 488 | // 2025202T1526 489 | // 2025202T1526,6 490 | // 2025202T1526.6 491 | // 2025202T152640 492 | // 2025202T152640.3 493 | // 2025202T152640.39 494 | // 2025202T152640,396 495 | // 2025202T152640.396 496 | // 2025202T152640,396510 497 | // 2025202T152640.396510 498 | // 2025202T152640.396509751 499 | // 2025202T152640,396509751 500 | // 2025202T20Z 501 | // 2025202T20,4Z 502 | // 2025202T20.4Z 503 | // 2025202T2026Z 504 | // 2025202T2026,6Z 505 | // 2025202T2026.6Z 506 | // 2025202T202640Z 507 | // 2025202T202640.3Z 508 | // 2025202T202640.39Z 509 | // 2025202T202640,396Z 510 | // 2025202T202640.396Z 511 | // 2025202T202640,396510Z 512 | // 2025202T202640.396510Z 513 | // 2025202T202640.396509751Z 514 | // 2025202T202640,396509751Z 515 | // 2025202T15-05 516 | // 2025202T15,4-05 517 | // 2025202T15.4-05 518 | // 2025202T1526-05 519 | // 2025202T1526,6-05 520 | // 2025202T1526.6-05 521 | // 2025202T152640-05 522 | // 2025202T152640.3-05 523 | // 2025202T152640.39-05 524 | // 2025202T152640,396-05 525 | // 2025202T152640.396-05 526 | // 2025202T152640,396510-05 527 | // 2025202T152640.396510-05 528 | // 2025202T152640.396509751-05 529 | // 2025202T152640,396509751-05 530 | // 2025202T15-0500 531 | // 2025202T15,4-0500 532 | // 2025202T15.4-0500 533 | // 2025202T1526-0500 534 | // 2025202T1526,6-0500 535 | // 2025202T1526.6-0500 536 | // 2025202T152640-0500 537 | // 2025202T152640.3-0500 538 | // 2025202T152640.39-0500 539 | // 2025202T152640,396-0500 540 | // 2025202T152640.396-0500 541 | // 2025202T152640,396510-0500 542 | // 2025202T152640.396510-0500 543 | // 2025202T152640.396509751-0500 544 | // 2025202T152640,396509751-0500 545 | 546 | 2025-07-22T04:26:40+08 547 | 2025-07-21T08-12 548 | 2025-07-21T08-12:00 549 | 2025-07-21T08:26-12 550 | 2025-07-21T08:26-12:00 551 | ` --------------------------------------------------------------------------------