@taskade/temporal-parser
Advanced tools
| import type { AnnotationAst, DateAst, DateTimeAst, DurationAst, OffsetAst, RangeAst, TemporalAst, TimeAst, TimeZoneAst } from './parser-types.js'; | ||
| /** | ||
| * Convert a temporal AST back to its string representation. | ||
| * | ||
| * @param ast - The AST to stringify | ||
| * @returns ISO 8601 / IXDTF formatted string | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const ast = parseTemporal('2025-01-12T10:00:00+08:00'); | ||
| * const str = stringifyTemporal(ast); | ||
| * // '2025-01-12T10:00:00+08:00' | ||
| * ``` | ||
| */ | ||
| export declare function stringifyTemporal(ast: TemporalAst): string; | ||
| /** | ||
| * Stringify a date AST to ISO 8601 format. | ||
| * | ||
| * @param date - The date AST | ||
| * @returns Date string (YYYY, YYYY-MM, or YYYY-MM-DD) | ||
| */ | ||
| export declare function stringifyDate(date: DateAst): string; | ||
| /** | ||
| * Stringify a time AST to ISO 8601 format. | ||
| * | ||
| * @param time - The time AST | ||
| * @returns Time string (HH:MM, HH:MM:SS, or HH:MM:SS.fff) | ||
| */ | ||
| export declare function stringifyTime(time: TimeAst): string; | ||
| /** | ||
| * Stringify a timezone offset AST to canonical format. | ||
| * | ||
| * @param offset - The offset AST | ||
| * @returns Offset string in canonical format (Z or ±HH:MM) | ||
| */ | ||
| export declare function stringifyOffset(offset: OffsetAst): string; | ||
| /** | ||
| * Stringify a timezone AST. | ||
| * | ||
| * @param timeZone - The timezone AST | ||
| * @returns Timezone string [Asia/Singapore] or [!Asia/Singapore] | ||
| */ | ||
| export declare function stringifyTimeZone(timeZone: TimeZoneAst): string; | ||
| /** | ||
| * Stringify an annotation AST. | ||
| * | ||
| * @param annotation - The annotation AST | ||
| * @returns Annotation string [u-ca=gregory] or [!u-ca=gregory] | ||
| */ | ||
| export declare function stringifyAnnotation(annotation: AnnotationAst): string; | ||
| /** | ||
| * Stringify a datetime AST to ISO 8601 / IXDTF format. | ||
| * | ||
| * @param dateTime - The datetime AST | ||
| * @returns DateTime string with optional time, offset, timezone, and annotations | ||
| */ | ||
| export declare function stringifyDateTime(dateTime: DateTimeAst): string; | ||
| /** | ||
| * Stringify a duration AST to ISO 8601 format. | ||
| * | ||
| * @param duration - The duration AST | ||
| * @returns Duration string (P1Y2M3DT4H5M6S) | ||
| */ | ||
| export declare function stringifyDuration(duration: DurationAst): string; | ||
| /** | ||
| * Stringify a range AST to ISO 8601 format. | ||
| * | ||
| * @param range - The range AST | ||
| * @returns Range string (start/end, /end, or start/) | ||
| */ | ||
| export declare function stringifyRange(range: RangeAst): string; | ||
| //# sourceMappingURL=stringify.d.ts.map |
| {"version":3,"file":"stringify.d.ts","sourceRoot":"","sources":["../../src/stringify.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,aAAa,EACb,OAAO,EACP,WAAW,EACX,WAAW,EACX,SAAS,EACT,QAAQ,EACR,WAAW,EACX,OAAO,EACP,WAAW,EACZ,MAAM,mBAAmB,CAAC;AAE3B;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,WAAW,GAAG,MAAM,CAQ1D;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAsBnD;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAenD;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAQzD;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,WAAW,GAAG,MAAM,CAG/D;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,aAAa,GAAG,MAAM,CAErE;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,WAAW,GAAG,MAAM,CAoB/D;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,WAAW,GAAG,MAAM,CA4C/D;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM,CActD"} |
+83
-0
| # @taskade/temporal-parser | ||
| ## 1.1.0 | ||
| ### Minor Changes | ||
| - c557ac0: feat: Add support for BC dates (negative years in ISO 8601) | ||
| Implement parsing and stringification of BC dates using ISO 8601 extended year format with negative year numbers. This follows the astronomical year numbering system where year 0 = 1 BC, year -1 = 2 BC, etc. | ||
| **Key features:** | ||
| - Parse BC dates with negative year notation: `-0044-03-15` (44 BC) | ||
| - Support year 0 representing 1 BC: `0000-01-01` | ||
| - Handle BC dates with time and timezone components | ||
| - Proper year padding in output: `-0044`, `-0001` | ||
| - Full round-trip compatibility | ||
| **Supported formats:** | ||
| - BC year only: `-0100` | ||
| - BC year-month: `-0753-04` | ||
| - BC full date: `-0044-03-15` | ||
| - BC datetime: `-0044-03-15T12:00:00` | ||
| - BC with timezone: `-0044-03-15T12:00:00Z` | ||
| **Examples:** | ||
| ```typescript | ||
| // Parse the Ides of March, 44 BC | ||
| const bcDate = parseTemporal("-0044-03-15"); | ||
| // { kind: 'DateTime', date: { year: -44, month: 3, day: 15 }, ... } | ||
| // Stringify BC date | ||
| stringifyDate({ kind: "Date", year: -44, month: 3, day: 15 }); | ||
| // Returns: '-0044-03-15' | ||
| // Year 0 represents 1 BC in ISO 8601 | ||
| parseTemporal("0000-01-01"); | ||
| // { kind: 'DateTime', date: { year: 0, month: 1, day: 1 }, ... } | ||
| ``` | ||
| This implementation maintains full backward compatibility and follows ISO 8601:2004 extended year format specification. | ||
| - 7223124: feat: add comprehensive stringify API for AST serialization | ||
| Add comprehensive stringify API for AST serialization. Implements full round-trip serialization support by converting parsed temporal ASTs back to ISO 8601/IXDTF formatted strings. | ||
| **New exports:** | ||
| - `stringifyTemporal()` - Main function for all temporal types | ||
| - `stringifyDate()`, `stringifyTime()`, `stringifyDateTime()` - DateTime components | ||
| - `stringifyDuration()`, `stringifyRange()` - Duration and Range types | ||
| - `stringifyOffset()`, `stringifyTimeZone()`, `stringifyAnnotation()` - Supporting components | ||
| **Features:** | ||
| - Automatic normalization of offsets to canonical format (±HH:MM) | ||
| - Component-based reconstruction for consistent output | ||
| - Full round-trip compatibility with parser | ||
| - Preserves all AST information including annotations and critical flags | ||
| **Example:** | ||
| ```typescript | ||
| import { parseTemporal, stringifyTemporal } from "@taskade/temporal-parser"; | ||
| const ast = parseTemporal("2025-01-12T10:00:00+0530"); // Compact format | ||
| const normalized = stringifyTemporal(ast); | ||
| // '2025-01-12T10:00:00+05:30' (canonical format) | ||
| ``` | ||
| ### Patch Changes | ||
| - 692b0f7: fix: Support comma as decimal separator in fractional seconds (European format) | ||
| Add support for comma (`,`) as a decimal separator in fractional seconds for both time and duration components, as specified in ISO 8601. This enables parsing of European-formatted temporal strings while maintaining canonical dot (`.`) notation in serialized output. | ||
| **Supported formats:** | ||
| - Time with fractional seconds: `T10:30:45,123` → `T10:30:45.123` | ||
| - Duration with fractional seconds: `PT1,5S` → `PT1.5S` | ||
| **Behavior:** | ||
| - Parser accepts both `.` and `,` as decimal separators | ||
| - Stringify normalizes all output to use `.` for consistency | ||
| - Full round-trip compatibility maintained | ||
| This change improves ISO 8601 compliance and enables parsing of temporal strings from European locales where comma is the standard decimal separator. | ||
| ## 1.0.5 | ||
@@ -4,0 +87,0 @@ |
+142
-6
@@ -30,3 +30,12 @@ "use strict"; | ||
| parseOffset: () => parseOffset, | ||
| parseTemporal: () => parseTemporal | ||
| parseTemporal: () => parseTemporal, | ||
| stringifyAnnotation: () => stringifyAnnotation, | ||
| stringifyDate: () => stringifyDate, | ||
| stringifyDateTime: () => stringifyDateTime, | ||
| stringifyDuration: () => stringifyDuration, | ||
| stringifyOffset: () => stringifyOffset, | ||
| stringifyRange: () => stringifyRange, | ||
| stringifyTemporal: () => stringifyTemporal, | ||
| stringifyTime: () => stringifyTime, | ||
| stringifyTimeZone: () => stringifyTimeZone | ||
| }); | ||
@@ -474,4 +483,8 @@ module.exports = __toCommonJS(index_exports); | ||
| parseDate() { | ||
| const isNegative = this.tryEat("Dash" /* Dash */); | ||
| const yTok = this.eat("Number" /* Number */); | ||
| const year = toInt(yTok.value, "year", this.i); | ||
| let year = toInt(yTok.value, "year", this.i); | ||
| if (isNegative) { | ||
| year = -year; | ||
| } | ||
| if (!this.tryEat("Dash" /* Dash */)) { | ||
@@ -500,3 +513,3 @@ return { kind: "Date", year }; | ||
| second = toInt(sTok.value, "second", this.i); | ||
| if (this.tryEat("Dot" /* Dot */)) { | ||
| if (this.tryEat("Dot" /* Dot */) || this.tryEat("Comma" /* Comma */)) { | ||
| const fracTok = this.eat("Number" /* Number */); | ||
@@ -612,4 +625,5 @@ fraction = fracTok.value; | ||
| let fraction; | ||
| if (this.tryEat("Dot" /* Dot */)) { | ||
| rawParts.push("."); | ||
| const dotOrComma = this.tryEat("Dot" /* Dot */) || this.tryEat("Comma" /* Comma */); | ||
| if (dotOrComma) { | ||
| rawParts.push(dotOrComma.value); | ||
| const fracTok = this.eat("Number" /* Number */); | ||
@@ -663,2 +677,115 @@ rawParts.push(fracTok.value); | ||
| } | ||
| // src/stringify.ts | ||
| function stringifyTemporal(ast) { | ||
| if (ast.kind === "Range") { | ||
| return stringifyRange(ast); | ||
| } | ||
| if (ast.kind === "Duration") { | ||
| return stringifyDuration(ast); | ||
| } | ||
| return stringifyDateTime(ast); | ||
| } | ||
| function stringifyDate(date) { | ||
| let yearStr; | ||
| if (date.year < 0) { | ||
| yearStr = "-" + Math.abs(date.year).toString().padStart(4, "0"); | ||
| } else { | ||
| yearStr = date.year.toString().padStart(4, "0"); | ||
| } | ||
| const parts = [yearStr]; | ||
| if (date.month != null) { | ||
| parts.push(date.month.toString().padStart(2, "0")); | ||
| if (date.day != null) { | ||
| parts.push(date.day.toString().padStart(2, "0")); | ||
| } | ||
| } | ||
| return parts.join("-"); | ||
| } | ||
| function stringifyTime(time) { | ||
| const parts = [ | ||
| time.hour.toString().padStart(2, "0"), | ||
| time.minute.toString().padStart(2, "0") | ||
| ]; | ||
| if (time.second != null) { | ||
| let secondStr = time.second.toString().padStart(2, "0"); | ||
| if (time.fraction != null) { | ||
| secondStr += `.${time.fraction}`; | ||
| } | ||
| parts.push(secondStr); | ||
| } | ||
| return parts.join(":"); | ||
| } | ||
| function stringifyOffset(offset) { | ||
| if (offset.kind === "UtcOffset") { | ||
| return "Z"; | ||
| } | ||
| const hours = offset.hours.toString().padStart(2, "0"); | ||
| const minutes = offset.minutes.toString().padStart(2, "0"); | ||
| return `${offset.sign}${hours}:${minutes}`; | ||
| } | ||
| function stringifyTimeZone(timeZone) { | ||
| const id = timeZone.critical ? `!${timeZone.id}` : timeZone.id; | ||
| return `[${id}]`; | ||
| } | ||
| function stringifyAnnotation(annotation) { | ||
| return `[${annotation.raw}]`; | ||
| } | ||
| function stringifyDateTime(dateTime) { | ||
| let result = stringifyDate(dateTime.date); | ||
| if (dateTime.time) { | ||
| result += `T${stringifyTime(dateTime.time)}`; | ||
| } | ||
| if (dateTime.offset) { | ||
| result += stringifyOffset(dateTime.offset); | ||
| } | ||
| if (dateTime.timeZone) { | ||
| result += stringifyTimeZone(dateTime.timeZone); | ||
| } | ||
| for (const annotation of dateTime.annotations) { | ||
| result += stringifyAnnotation(annotation); | ||
| } | ||
| return result; | ||
| } | ||
| function stringifyDuration(duration) { | ||
| let result = "P"; | ||
| if (duration.years != null) { | ||
| result += `${duration.years}Y`; | ||
| } | ||
| if (duration.months != null) { | ||
| result += `${duration.months}M`; | ||
| } | ||
| if (duration.weeks != null) { | ||
| result += `${duration.weeks}W`; | ||
| } | ||
| if (duration.days != null) { | ||
| result += `${duration.days}D`; | ||
| } | ||
| const hasTimePart = duration.hours != null || duration.minutes != null || duration.seconds != null; | ||
| if (hasTimePart) { | ||
| result += "T"; | ||
| if (duration.hours != null) { | ||
| result += `${duration.hours}H`; | ||
| } | ||
| if (duration.minutes != null) { | ||
| result += `${duration.minutes}M`; | ||
| } | ||
| if (duration.seconds != null) { | ||
| result += `${duration.seconds}`; | ||
| if (duration.secondsFraction != null && duration.secondsFraction.length > 0) { | ||
| result += `.${duration.secondsFraction}`; | ||
| } | ||
| result += "S"; | ||
| } | ||
| } | ||
| for (const annotation of duration.annotations) { | ||
| result += stringifyAnnotation(annotation); | ||
| } | ||
| return result; | ||
| } | ||
| function stringifyRange(range) { | ||
| const start = range.start ? range.start.kind === "Duration" ? stringifyDuration(range.start) : stringifyDateTime(range.start) : ""; | ||
| const end = range.end ? range.end.kind === "Duration" ? stringifyDuration(range.end) : stringifyDateTime(range.end) : ""; | ||
| return `${start}/${end}`; | ||
| } | ||
| // Annotate the CommonJS export names for ESM import in node: | ||
@@ -673,4 +800,13 @@ 0 && (module.exports = { | ||
| parseOffset, | ||
| parseTemporal | ||
| parseTemporal, | ||
| stringifyAnnotation, | ||
| stringifyDate, | ||
| stringifyDateTime, | ||
| stringifyDuration, | ||
| stringifyOffset, | ||
| stringifyRange, | ||
| stringifyTemporal, | ||
| stringifyTime, | ||
| stringifyTimeZone | ||
| }); | ||
| //# sourceMappingURL=index.cjs.map |
| { | ||
| "version": 3, | ||
| "sources": ["../../src/index.ts", "../../src/lexer-types.ts", "../../src/combineTimezoneOffsets.ts", "../../src/errors.ts", "../../src/helpers/isAlpha.ts", "../../src/helpers/isDigit.ts", "../../src/helpers/isBracketWordChar.ts", "../../src/lexer.ts", "../../src/helpers/toInt.ts", "../../src/parseOffset.ts", "../../src/parser.ts"], | ||
| "sourcesContent": ["// index.ts\n// Main entry point for the temporal parser library\n\n// Export lexer functionality\nexport { combineTimezoneOffsets } from './combineTimezoneOffsets.js';\nexport { lexTemporal } from './lexer.js';\n\n// Export lexer types\nexport type { AnyToken, CombinedToken, Token } from './lexer-types.js';\nexport { CombinedTokType, TokType } from './lexer-types.js';\n\n// Export parser functionality\nexport { parseTemporal } from './parser.js';\n\n// Export offset parser (useful standalone utility)\nexport { parseOffset } from './parseOffset.js';\n\n// Export parser types\nexport type {\n AnnotationAst,\n DateAst,\n DateTimeAst,\n DurationAst,\n OffsetAst,\n RangeAst,\n TemporalAst,\n TimeAst,\n TimeZoneAst,\n ValueAst,\n} from './parser-types.js';\n\n// Export errors\nexport { LexError, ParseError } from './errors.js';\n", "// lexer-types.ts\n// Token types and interfaces for the lexer\n\nexport enum TokType {\n // Numbers: sequences of digits (0-9)\n // Examples: \"2025\", \"01\", \"12\", \"59\"\n Number = 'Number',\n\n // Ident (Identifier): sequences of alphabetic characters (A-Z, a-z)\n // This is the generic \"text\" token for letter-based designators.\n // Examples: \"P\" (duration), \"Y\" (year), \"M\" (month), \"gregory\" (calendar)\n // The lexer keeps it simple; the parser interprets the meaning.\n Ident = 'Ident',\n\n // Single-character punctuation tokens\n Dash = 'Dash', // -\n Colon = 'Colon', // :\n Dot = 'Dot', // .\n Plus = 'Plus', // +\n Slash = 'Slash', // /\n Comma = 'Comma', // ,\n Equals = 'Equals', // =\n Underscore = 'Underscore', // _\n Exclamation = 'Exclamation', // ! (critical flag in IXDTF)\n\n // Special letter tokens with dedicated meaning in ISO 8601\n T = 'T', // Date/time separator: 2025-01-01T10:00:00\n Z = 'Z', // UTC timezone marker: 2025-01-01T10:00:00Z\n\n // Bracket tokens for IXDTF annotations/extensions\n LBracket = 'LBracket', // [\n RBracket = 'RBracket', // ]\n\n // BracketText: complex text inside brackets that contains non-letter chars\n // Example: in [u-ca=gregory], \"u-ca\" is BracketText (has dash), \"gregory\" is Ident (letters only)\n // Example: in [Asia/Singapore], \"Asia/Singapore\" is BracketText (has slash)\n BracketText = 'BracketText',\n\n EOF = 'EOF',\n}\n\nexport type Token = {\n type: TokType;\n value: string;\n start: number; // inclusive\n end: number; // exclusive\n};\n\n// Combined token types for timezone offsets\nexport enum CombinedTokType {\n TZOffset = 'TZOffset', // +08:00, -0530, +09\n}\n\nexport type CombinedToken = {\n type: CombinedTokType;\n value: string;\n tokens: Token[];\n start: number;\n end: number;\n};\n\nexport type AnyToken = Token | CombinedToken;\n", "// combineTimezoneOffsets.ts\n// Combines numeric timezone offset tokens into single tokens for easier parsing\n\nimport { type AnyToken, CombinedTokType, type Token, TokType } from './lexer-types.js';\n\n/**\n * Combine numeric timezone offsets into single tokens for easier parsing.\n * This is an optional post-processing step for the lexer output.\n *\n * Combines patterns like:\n * - (+|-) Number Colon Number => +08:00, -05:30\n * - (+|-) Number (2 or 4 digits) => +09, -0530\n *\n * Only combines when context suggests a timezone offset (after time components),\n * NOT after:\n * - Z (mutually exclusive per ISO 8601)\n * - brackets (offset comes BEFORE brackets per RFC 9557/IXDTF)\n * - standalone dates (e.g., 2025-01 where - is a date separator)\n *\n * @param tokens - The raw token array from lexTemporal\n * @returns Array of tokens (either original Token or combined CombinedToken with TZOffset type where applicable)\n *\n * @example\n * ```typescript\n * const raw = lexTemporal('10:00:00+08:00');\n * const combined = combineTimezoneOffsets(raw);\n * // Result includes: { type: 'TZOffset', value: '+08:00' }\n * ```\n */\nexport function combineTimezoneOffsets(tokens: Token[]): AnyToken[] {\n const out: AnyToken[] = [];\n let i = 0;\n\n const peek = (k: number) => tokens[i + k];\n\n // Helper to check if previous tokens suggest this could be a timezone offset\n // Timezone offsets come after time components (HH:MM:SS)\n // NOT after just a date like 2025-01\n // NOT after Z (Z and numeric offsets are mutually exclusive per ISO 8601)\n // NOT after brackets (per RFC 9557/IXDTF: offset comes BEFORE brackets)\n const couldBeTzOffset = () => {\n if (out.length < 1) {\n return false;\n }\n const prev: AnyToken | undefined = out[out.length - 1];\n if (prev == null) {\n return false;\n }\n\n // After Z - NOT valid (Z and numeric offsets are mutually exclusive)\n // Per ISO 8601: use either Z OR +HH:MM, never both\n if (prev.type === TokType.Z) {\n return false;\n }\n\n // After bracket - NOT valid (offset must come BEFORE brackets per RFC 9557)\n // Valid: Time+08:00[Asia/Tokyo]\n // Invalid: [Asia/Tokyo]+08:00\n if (prev.type === TokType.RBracket) {\n return false;\n }\n\n // After a number, check context\n if (prev.type === TokType.Number) {\n if (out.length < 2) {\n return false;\n }\n const prevPrev = out[out.length - 2];\n\n // After a number with colon before it (time component)\n // e.g., ...10:00:00+08:00 - the number before + has a colon before it\n if (prevPrev?.type === TokType.Colon) {\n return true;\n }\n\n // After a number with dot before it (milliseconds)\n // e.g., ...10:00:00.999+08:00 - the number before + has a dot before it\n if (prevPrev?.type === TokType.Dot) {\n return true;\n }\n }\n\n return false;\n };\n\n while (i < tokens.length) {\n const t = tokens[i]!;\n const isSign = t.type === TokType.Plus || t.type === TokType.Dash;\n\n // Only try to combine if context suggests timezone offset\n if (isSign && couldBeTzOffset()) {\n // (+|-) Number Colon Number => +08:00\n if (\n peek(1)?.type === TokType.Number &&\n peek(2)?.type === TokType.Colon &&\n peek(3)?.type === TokType.Number\n ) {\n const originalTokens = [t, peek(1)!, peek(2)!, peek(3)!];\n const start = t.start;\n const end = peek(3)!.end;\n const value = `${t.value}${peek(1)!.value}:${peek(3)!.value}`;\n out.push({ type: CombinedTokType.TZOffset, value, tokens: originalTokens, start, end });\n i += 4;\n continue;\n }\n\n // (+|-) Number where Number is 2 or 4 digits => +09 or -0530\n if (peek(1)?.type === TokType.Number) {\n const num = peek(1)!.value;\n if (num.length === 2 || num.length === 4) {\n const originalTokens = [t, peek(1)!];\n const start = t.start;\n const end = peek(1)!.end;\n const value = `${t.value}${num}`;\n out.push({ type: CombinedTokType.TZOffset, value, tokens: originalTokens, start, end });\n i += 2;\n continue;\n }\n }\n }\n\n out.push(t);\n i++;\n }\n\n return out;\n}\n", "// errors.ts\n// Error classes for lexer and parser\n\nexport class LexError extends Error {\n public readonly position: number;\n constructor(message: string, position: number) {\n super(`${message} at position ${position}`);\n this.name = 'LexError';\n this.position = position;\n }\n}\n\nexport class ParseError extends Error {\n public readonly position: number;\n constructor(message: string, position: number) {\n super(`${message} at token index ${position}`);\n this.name = 'ParseError';\n this.position = position;\n }\n}\n", "/**\n * Check if character is alphabetic (A-Z or a-z)\n * This defines what can be part of an IDENT token in default mode\n * @param c - the character to check\n * @returns true if character is alphabetic, false otherwise\n */\nexport function isAlpha(c: string): boolean {\n return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');\n}\n", "/**\n * Check if character is a digit (0-9)\n * @param c - the character to check\n * @returns true if character is a digit, false otherwise\n */\nexport function isDigit(c: string) {\n return c >= '0' && c <= '9';\n}\n", "import { isAlpha } from './isAlpha.js';\nimport { isDigit } from './isDigit.js';\n\n/**\n * Check if character is valid inside bracket annotations\n * In default mode, IDENT is letters only (keeps date/time parsing clean).\n * In bracket mode, we allow broader \"words\" like:\n * - u-ca (has dash)\n * - Asia/Singapore (has slash)\n * - Etc/GMT+8 (has plus)\n * - iso8601 (has digits)\n * @param c - the character to check\n * @returns true if character is valid in bracket context, false otherwise\n */\nexport function isBracketWordChar(c: string): boolean {\n return (\n isAlpha(c) || isDigit(c) || c === '-' || c === '/' || c === '+' || c === '.' // allow dots in some TZ-ish values\n );\n}\n", "// lexer.ts\n// Temporal (IXDTF / ISO-8601-ish) lexer with support for *extensions/annotations* like calendar.\n//\n// Supports:\n// - Basic date/time tokens: numbers, identifiers, T, Z, punctuation\n// - Ranges: '/'\n// - Durations: 'P' (as IDENT; parser enforces rules)\n// - Time zone forms:\n// - 'Z'\n// - numeric offsets: +08:00 / -0530 / +09 (optionally combined via helper)\n// - bracketed IANA TZ: [Asia/Singapore]\n// - IXDTF-style annotations/extensions in brackets, e.g.:\n// - [u-ca=gregory] (calendar)\n// - [u-tz=Asia/Singapore] (timeZone)\n// - multiple bracket groups: 2025-01-01T00:00Z[u-ca=iso8601][u-tz=UTC]\n//\n// The lexer is permissive; your parser should validate semantics.\n\nimport { LexError } from './errors.js';\nimport { isAlpha } from './helpers/isAlpha.js';\nimport { isBracketWordChar } from './helpers/isBracketWordChar.js';\nimport { isDigit } from './helpers/isDigit.js';\nimport type { Token } from './lexer-types.js';\nimport { TokType } from './lexer-types.js';\n\nexport function lexTemporal(src: string): Token[] {\n const tokens: Token[] = [];\n let i = 0;\n\n const emit = (type: TokType, start: number, end: number, value?: string) => {\n tokens.push({ type, value: value ?? src.slice(start, end), start, end });\n };\n\n // Lex a number: consume consecutive digits\n // Examples: \"2025\", \"01\", \"59\"\n const lexNumber = () => {\n const start = i;\n i++;\n while (i < src.length && isDigit(src[i]!)) {\n i++;\n }\n emit(TokType.Number, start, i);\n };\n\n // Lex an identifier: consume consecutive alphabetic characters\n // This is where IDENT tokens are created!\n //\n // Examples of what becomes IDENT:\n // - Duration designators: P, Y, M, W, D, H, S\n // - Calendar names: gregory, hebrew, iso8601\n // - Any letter sequence: P, YMD, UTC, America\n //\n // Special cases:\n // - \"T\" gets its own token type (datetime separator)\n // - \"Z\" gets its own token type (UTC marker)\n // - Everything else becomes Ident\n //\n // IMPORTANT: We only consume a single letter at a time to allow T and Z\n // to be recognized individually (e.g., \"DT\" should be \"D\" then \"T\", not \"DT\")\n const lexIdent = () => {\n const start = i;\n const ch = src[i]!;\n i++;\n\n // Special handling for T and Z - emit them as special tokens\n if (ch === 'T') {\n emit(TokType.T, start, i, 'T');\n return;\n }\n if (ch === 'Z') {\n emit(TokType.Z, start, i, 'Z');\n return;\n }\n\n // For other letters, consume consecutive letters that are NOT T or Z\n // This allows multi-letter identifiers like \"gregory\" but keeps T and Z separate\n while (i < src.length && isAlpha(src[i]!) && src[i] !== 'T' && src[i] !== 'Z') {\n i++;\n }\n\n emit(TokType.Ident, start, i); // Generic identifier\n };\n\n const lexBracketContent = () => {\n // We are positioned after '['. Emit content tokens until ']'.\n while (i < src.length) {\n const ch = src[i]!;\n\n if (ch === ']') {\n return;\n }\n\n // Skip whitespace inside brackets (rare but harmless)\n if (/\\s/.test(ch)) {\n i++;\n continue;\n }\n\n // Single-char tokens inside brackets\n if (ch === '=') {\n emit(TokType.Equals, i, i + 1);\n i++;\n continue;\n }\n if (ch === '_') {\n emit(TokType.Underscore, i, i + 1);\n i++;\n continue;\n }\n if (ch === ':') {\n emit(TokType.Colon, i, i + 1);\n i++;\n continue;\n }\n if (ch === '-') {\n emit(TokType.Dash, i, i + 1);\n i++;\n continue;\n }\n if (ch === '+') {\n emit(TokType.Plus, i, i + 1);\n i++;\n continue;\n }\n if (ch === '/') {\n emit(TokType.Slash, i, i + 1);\n i++;\n continue;\n }\n if (ch === '.') {\n emit(TokType.Dot, i, i + 1);\n i++;\n continue;\n }\n if (ch === ',') {\n emit(TokType.Comma, i, i + 1);\n i++;\n continue;\n }\n if (ch === '!') {\n emit(TokType.Exclamation, i, i + 1);\n i++;\n continue;\n }\n\n // Words inside brackets (u-ca, gregory, Asia/Singapore, iso8601, Etc/GMT+8, etc.)\n if (isBracketWordChar(ch)) {\n const start = i;\n i++;\n while (i < src.length && isBracketWordChar(src[i]!)) {\n i++;\n }\n const text = src.slice(start, i);\n\n // Decision: Ident vs BracketText\n // - If purely letters (A-Z, a-z): emit as Ident\n // Examples: \"gregory\", \"hebrew\", \"u\", \"ca\"\n // - If contains other chars (digits, -, /, +, .): emit as BracketText\n // Examples: \"u-ca\", \"Asia/Singapore\", \"iso8601\", \"Etc/GMT+8\"\n if (/^[A-Za-z]+$/.test(text)) {\n emit(TokType.Ident, start, i, text);\n } else {\n emit(TokType.BracketText, start, i, text);\n }\n\n continue;\n }\n\n throw new LexError(`Unexpected character ${JSON.stringify(ch)} inside brackets`, i);\n }\n\n throw new LexError(\"Unterminated bracket annotation, expected ']'\", src.length);\n };\n\n while (i < src.length) {\n const ch = src[i]!;\n\n // Skip whitespace\n if (/\\s/.test(ch)) {\n i++;\n continue;\n }\n\n // Bracketed extensions/annotations/timezone IDs\n if (ch === '[') {\n emit(TokType.LBracket, i, i + 1);\n i++;\n\n lexBracketContent();\n\n if (i >= src.length || src[i] !== ']') {\n throw new LexError(\"Unterminated bracket annotation, expected ']'\", i);\n }\n emit(TokType.RBracket, i, i + 1);\n i++;\n continue;\n }\n\n // Numbers: sequences of digits\n if (isDigit(ch)) {\n lexNumber();\n continue;\n }\n\n // Letters: this is where IDENT tokens are created!\n // Any alphabetic character triggers identifier lexing.\n // Examples: P (duration), Y (year), M (month), gregory, UTC\n if (isAlpha(ch)) {\n lexIdent();\n continue;\n }\n\n // Punctuation (default mode)\n switch (ch) {\n case '-':\n emit(TokType.Dash, i, i + 1);\n i++;\n continue;\n case ':':\n emit(TokType.Colon, i, i + 1);\n i++;\n continue;\n case '.':\n emit(TokType.Dot, i, i + 1);\n i++;\n continue;\n case '+':\n emit(TokType.Plus, i, i + 1);\n i++;\n continue;\n case '/':\n emit(TokType.Slash, i, i + 1);\n i++;\n continue;\n case ',':\n emit(TokType.Comma, i, i + 1);\n i++;\n continue;\n case '=':\n emit(TokType.Equals, i, i + 1);\n i++;\n continue;\n case '_':\n emit(TokType.Underscore, i, i + 1);\n i++;\n continue;\n default:\n throw new LexError(`Unexpected character ${JSON.stringify(ch)}`, i);\n }\n }\n\n emit(TokType.EOF, src.length, src.length, '');\n return tokens;\n}\n", "import { ParseError } from '../errors.js';\n\n/**\n * Convert a string to an integer, throwing a ParseError if invalid\n * @param s - the string to convert\n * @param label - a descriptive label for error messages (e.g., \"year\", \"month\")\n * @param position - the position in the token stream for error reporting\n * @returns the parsed integer value\n * @throws {ParseError} if the string is not a valid integer\n */\nexport function toInt(s: string, label: string, position: number): number {\n const n = Number(s);\n if (!Number.isFinite(n) || !Number.isInteger(n)) {\n throw new ParseError(`Invalid integer for ${label}: ${JSON.stringify(s)}`, position);\n }\n return n;\n}\n", "// parseOffset.ts\n// Parse timezone offset strings into structured components\n\nimport { ParseError } from './errors.js';\nimport { toInt } from './helpers/toInt.js';\nimport type { OffsetAst } from './parser-types.js';\n\n// Timezone offset validation constants\n// Per ISO 8601 and real-world usage:\n// - Westernmost: UTC-12:00 (Baker Island, Howland Island)\n// - Easternmost: UTC+14:00 (Line Islands, Kiritimati)\nconst MAX_OFFSET_HOURS = 14;\nconst MAX_MINUTES = 59;\n\n/**\n * Parse a timezone offset string into structured components.\n *\n * Supports three formats:\n * - Standard: +08:00, -05:30\n * - Compact: +0530, -0800\n * - Short: +09, -05\n *\n * Valid timezone offset ranges (per ISO 8601):\n * - Hours: 0-14 (UTC-12:00 to UTC+14:00)\n * - Minutes: 0-59\n *\n * Note: This function only parses numeric offsets (e.g., +08:00).\n * For UTC, use 'Z' (UtcOffset kind) which is semantically different from +00:00.\n * Per RFC 3339 section 4.3, -00:00 indicates \"local time with unknown offset\".\n *\n * @param offsetString - The offset string to parse (e.g., '+08:00', '-0530', '+09')\n * @param position - Position in token stream for error reporting (optional)\n * @returns Parsed offset with sign, hours, and minutes\n * @throws {ParseError} If the offset format is invalid or out of range\n *\n * @example\n * ```typescript\n * parseOffset('+08:00') // { kind: 'NumericOffset', sign: '+', hours: 8, minutes: 0, raw: '+08:00' }\n * parseOffset('-05:30') // { kind: 'NumericOffset', sign: '-', hours: 5, minutes: 30, raw: '-05:30' }\n * parseOffset('+0530') // { kind: 'NumericOffset', sign: '+', hours: 5, minutes: 30, raw: '+0530' }\n * parseOffset('+09') // { kind: 'NumericOffset', sign: '+', hours: 9, minutes: 0, raw: '+09' }\n * parseOffset('+14:00') // { kind: 'NumericOffset', sign: '+', hours: 14, minutes: 0, raw: '+14:00' }\n * ```\n */\nexport function parseOffset(offsetString: string, position = 0): OffsetAst {\n // Validate input is not empty\n if (offsetString.length === 0) {\n throw new ParseError('Offset string cannot be empty', position);\n }\n\n const raw = offsetString;\n\n // Validate first character is sign and assert type\n const sign = raw[0];\n if (sign !== '+' && sign !== '-') {\n throw new ParseError(`Offset must start with + or -, got ${JSON.stringify(sign)}`, position);\n }\n // Type assertion: TypeScript now knows sign is '+' | '-'\n const validSign: '+' | '-' = sign;\n\n const rest = raw.slice(1);\n\n // Validate rest is not empty\n if (rest.length === 0) {\n throw new ParseError(`Offset sign must be followed by digits`, position);\n }\n\n let hours: number;\n let minutes: number;\n\n if (rest.includes(':')) {\n // Format: +08:00 or -05:30\n const parts = rest.split(':');\n if (parts.length !== 2) {\n throw new ParseError(`Invalid offset format: ${raw} (expected HH:MM)`, position);\n }\n // After length check, we know parts[0] and parts[1] exist\n const [h, m] = parts;\n hours = toInt(h!, 'offset hours', position);\n minutes = toInt(m!, 'offset minutes', position);\n } else if (rest.length === 4) {\n // Format: +0530 or -0800\n hours = toInt(rest.slice(0, 2), 'offset hours', position);\n minutes = toInt(rest.slice(2, 4), 'offset minutes', position);\n } else if (rest.length === 2) {\n // Format: +08 or -05\n hours = toInt(rest, 'offset hours', position);\n minutes = 0;\n } else {\n throw new ParseError(\n `Invalid offset format: ${raw} (expected +HH:MM, +HHMM, or +HH)`,\n position,\n );\n }\n\n // Validate ranges\n if (hours < 0 || hours > MAX_OFFSET_HOURS) {\n throw new ParseError(`Offset hours must be 0-${MAX_OFFSET_HOURS}, got ${hours}`, position);\n }\n if (minutes < 0 || minutes > MAX_MINUTES) {\n throw new ParseError(`Offset minutes must be 0-${MAX_MINUTES}, got ${minutes}`, position);\n }\n\n return { kind: 'NumericOffset', sign: validSign, hours, minutes, raw };\n}\n", "// parser.ts\n// A practical recursive-descent parser that works with the lexer we wrote.\n// It builds an AST for a useful Temporal subset:\n// - date/time (with optional fractional seconds)\n// - timezone: Z | numeric offset (+08:00, -0530, +09) | bracket TZ id [Asia/Singapore]\n// - duration: P... with optional T part\n// - range: <expr> \"/\" <expr> where each side can be dateTime/date/duration, and either side may be empty (open range)\n// - bracket extensions like [u-ca=gregory], [u-tz=Asia/Singapore], etc. (stored as key/value pairs)\n\nimport { combineTimezoneOffsets } from './combineTimezoneOffsets.js';\nimport { ParseError } from './errors.js';\nimport { toInt } from './helpers/toInt.js';\nimport { lexTemporal } from './lexer.js';\nimport { type AnyToken, CombinedTokType, TokType } from './lexer-types.js';\nimport { parseOffset } from './parseOffset.js';\nimport type {\n AnnotationAst,\n DateAst,\n DateTimeAst,\n DurationAst,\n OffsetAst,\n TemporalAst,\n TimeAst,\n TimeZoneAst,\n ValueAst,\n} from './parser-types.js';\n\n// ---------- Parser ----------\n\nclass Parser {\n private i = 0;\n constructor(private toks: AnyToken[]) {}\n\n private peek(k = 0): AnyToken {\n return this.toks[Math.min(this.i + k, this.toks.length - 1)]!;\n }\n private at(type: TokType | CombinedTokType): boolean {\n return this.peek().type === type;\n }\n private eat(type: TokType | CombinedTokType): AnyToken {\n const t = this.peek();\n if (t.type !== type) {\n throw new ParseError(`Expected ${type} but got ${t.type}`, this.i);\n }\n this.i++;\n return t;\n }\n private tryEat(type: TokType | CombinedTokType): AnyToken | null {\n if (this.at(type)) {\n return this.eat(type);\n }\n return null;\n }\n private eof(): boolean {\n return this.at(TokType.EOF);\n }\n\n // Top-level: parse a range if we see '/', otherwise a single value.\n parseTemporal(): TemporalAst {\n // Open-start range: \"/...\"\n if (this.tryEat(TokType.Slash)) {\n const end = this.parseValueOrNull(); // allow empty end too\n this.eat(TokType.EOF);\n return { kind: 'Range', start: null, end };\n }\n\n const first = this.parseValueOrNull();\n // If first is null, only valid if it's \"/\" (handled above). So error:\n if (!first) {\n throw new ParseError('Expected a value', this.i);\n }\n\n if (this.tryEat(TokType.Slash)) {\n const second = this.parseValueOrNull(); // open-ended allowed\n this.eat(TokType.EOF);\n return { kind: 'Range', start: first, end: second };\n }\n\n this.eat(TokType.EOF);\n return first;\n }\n\n private parseValueOrNull(): ValueAst | null {\n // Empty side for open range:\n if (this.at(TokType.EOF) || this.at(TokType.Slash) || this.at(TokType.RBracket)) {\n return null;\n }\n\n // Duration starts with IDENT \"P\" (as produced by lexer)\n if (this.at(TokType.Ident) && String(this.peek().value) === 'P') {\n return this.parseDuration();\n }\n\n // Otherwise parse a date/datetime (common case)\n return this.parseDateTime();\n }\n\n // -------- DateTime parsing --------\n // Supports:\n // - YYYY\n // - YYYY-MM\n // - YYYY-MM-DD\n // - + time part with T: YYYY-MM-DDTHH:MM[:SS[.fff]]\n // - offset: Z | +08:00 | -0530\n // - timezone: [Asia/Singapore] (first bracketed IANA timezone)\n // - annotations: [u-ca=...][u-tz=...] (other bracketed content)\n private parseDateTime(): DateTimeAst {\n const date = this.parseDate();\n\n let time: DateTimeAst['time'] | undefined;\n\n // time separator is TokType.T (from lexer special-casing \"T\")\n if (this.tryEat(TokType.T)) {\n time = this.parseTime();\n }\n\n // Parse numeric offset (Z or +HH:MM)\n const offset = this.parseOffsetIfPresent();\n\n // Parse bracket groups: first IANA timezone becomes timeZone, rest are annotations\n const annotations: AnnotationAst[] = [];\n let timeZone: TimeZoneAst | undefined;\n\n while (this.at(TokType.LBracket)) {\n const { raw, critical, isLikelyTzId, pairs } = this.parseBracketGroup();\n\n // First timezone-like bracket becomes the timeZone field\n // Note: Even though IXDTF doesn't typically use critical flags for timezones,\n // we preserve it in case the input has it\n if (timeZone === undefined && isLikelyTzId) {\n // Strip leading ! from id (it's stored in the critical flag)\n const id = critical ? raw.slice(1) : raw;\n timeZone = { kind: 'IanaTimeZone', id, critical };\n } else {\n annotations.push({ kind: 'Annotation', raw, critical, pairs });\n }\n }\n\n return { kind: 'DateTime', date, time, offset, timeZone, annotations };\n }\n\n private parseDate(): DateAst {\n const yTok = this.eat(TokType.Number);\n const year = toInt(yTok.value, 'year', this.i);\n\n // Optional -MM\n if (!this.tryEat(TokType.Dash)) {\n return { kind: 'Date', year };\n }\n const mTok = this.eat(TokType.Number);\n const month = toInt(mTok.value, 'month', this.i);\n\n // Optional -DD\n if (!this.tryEat(TokType.Dash)) {\n return { kind: 'Date', year, month };\n }\n const dTok = this.eat(TokType.Number);\n const day = toInt(dTok.value, 'day', this.i);\n\n return { kind: 'Date', year, month, day };\n }\n\n private parseTime(): TimeAst {\n const hTok = this.eat(TokType.Number);\n const hour = toInt(hTok.value, 'hour', this.i);\n\n this.eat(TokType.Colon);\n const minTok = this.eat(TokType.Number);\n const minute = toInt(minTok.value, 'minute', this.i);\n\n let second: number | undefined;\n let fraction: string | undefined;\n\n if (this.tryEat(TokType.Colon)) {\n const sTok = this.eat(TokType.Number);\n second = toInt(sTok.value, 'second', this.i);\n\n if (this.tryEat(TokType.Dot)) {\n const fracTok = this.eat(TokType.Number);\n fraction = fracTok.value; // keep raw digits\n }\n }\n\n return { kind: 'Time', hour, minute, second, fraction };\n }\n\n /**\n * Parse timezone offset: Z | +HH:MM | -HH:MM\n *\n * Important semantic distinction per RFC 3339 and ISO 8601:\n *\n * - Z (Zulu time):\n * Explicitly indicates UTC. Used when the time is known to be in UTC.\n * Has no sign ambiguity - it's always UTC, never local time.\n *\n * - +00:00 (zero positive offset):\n * Indicates local time at the prime meridian (UTC+0 timezone).\n * Semantically different from Z - this is a local time that happens to match UTC.\n *\n * - -00:00 (zero negative offset):\n * Per RFC 3339 section 4.3: indicates local time with unknown UTC offset.\n * Used when the time is known to be local but the offset is not known.\n * This is semantically very different from both Z and +00:00.\n * https://www.rfc-editor.org/rfc/rfc3339.html#section-4.3\n *\n * We preserve this distinction by treating Z as UtcOffset (no sign, no components)\n * while +00:00 and -00:00 are NumericOffset (with sign and parsed components).\n */\n private parseOffsetIfPresent(): OffsetAst | undefined {\n if (this.tryEat(TokType.Z)) {\n return { kind: 'UtcOffset' };\n }\n\n // combined numeric offset token if you used combineTimezoneOffsets()\n if (this.at(CombinedTokType.TZOffset)) {\n const t = this.eat(CombinedTokType.TZOffset);\n return parseOffset(t.value, this.i);\n }\n\n return undefined;\n }\n\n // -------- Bracket group parsing --------\n // Consumes: [ ... ]\n // Returns:\n // - raw inner text (reconstructed)\n // - critical flag (true if starts with !)\n // - pairs if it looks like key=value (or flags)\n // - isLikelyTzId for things like [Asia/Singapore]\n private parseBracketGroup(): {\n raw: string;\n critical: boolean;\n pairs: Record<string, string | true>;\n isLikelyTzId: boolean;\n } {\n this.eat(TokType.LBracket);\n\n const parts: string[] = [];\n const pairs: Record<string, string | true> = {};\n\n // Check for critical flag (IXDTF: [!u-ca=gregory])\n const critical = this.tryEat(TokType.Exclamation) != null;\n if (critical) {\n parts.push('!');\n }\n\n // Gather tokens until RBracket\n while (!this.at(TokType.RBracket)) {\n if (this.eof()) {\n throw new ParseError('Unterminated bracket group', this.i);\n }\n\n const t = this.peek();\n this.i++;\n\n parts.push(String(t.value));\n }\n\n this.eat(TokType.RBracket);\n\n const raw = parts.join('');\n\n // Best-effort parse into key/value pairs:\n // Support \"key=value\" and \"flag\" and \"k=v,k2=v2\"\n // Strip leading ! if present for parsing\n const contentToParse = critical ? raw.slice(1) : raw;\n const segments = contentToParse\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean);\n for (const seg of segments) {\n const eq = seg.indexOf('=');\n if (eq === -1) {\n pairs[seg] = true;\n } else {\n const k = seg.slice(0, eq).trim();\n const v = seg.slice(eq + 1).trim();\n if (k) {\n pairs[k] = v || true;\n }\n }\n }\n\n // Heuristic: likely TZ ID if no '=' present and contains '/' (e.g. Asia/Singapore) or starts with Etc/\n // We now preserve critical flag even for timezones, so don't exclude them\n const isLikelyTzId =\n contentToParse.indexOf('=') === -1 &&\n (contentToParse.includes('/') || contentToParse.startsWith('Etc/'));\n\n // Note: If you want STRICTLY separate tz-id brackets vs extension brackets, do it here.\n\n return { raw, critical, pairs, isLikelyTzId };\n }\n\n // -------- Duration parsing --------\n // Grammar (subset):\n // P (Y M W D)* (T (H M S)*)?\n // Examples:\n // P1Y2M\n // P3W\n // P10DT2H30M\n // PT1.5S\n private parseDuration(): DurationAst {\n // Collect raw string as we parse\n const rawParts: string[] = [];\n const annotations: AnnotationAst[] = [];\n\n const pTok = this.eat(TokType.Ident);\n if (String(pTok.value) !== 'P') {\n throw new ParseError(\"Duration must start with 'P'\", this.i);\n }\n rawParts.push('P');\n\n let inTime = false;\n\n const dur: Omit<DurationAst, 'kind' | 'raw' | 'annotations'> = {};\n\n // Parse designators until we hit bracket annotations, slash, or EOF\n while (!this.eof() && !this.at(TokType.Slash) && !this.at(TokType.LBracket)) {\n // Time marker T\n if (this.tryEat(TokType.T)) {\n rawParts.push('T');\n inTime = true;\n continue;\n }\n\n // Expect a number (possibly with fraction for seconds)\n const numTok = this.tryEat(TokType.Number);\n if (!numTok) {\n break;\n } // be permissive; let parser user decide if this is error-worthy\n rawParts.push(numTok.value);\n\n let fraction: string | undefined;\n if (this.tryEat(TokType.Dot)) {\n rawParts.push('.');\n const fracTok = this.eat(TokType.Number);\n rawParts.push(fracTok.value);\n fraction = fracTok.value;\n }\n\n // Unit is IDENT (Y,M,W,D,H,M,S). Note M is months if !inTime else minutes.\n const unitTok = this.eat(TokType.Ident);\n const unit = String(unitTok.value);\n rawParts.push(unit);\n\n const n = toInt(numTok.value, 'duration number', this.i);\n\n if (!inTime) {\n if (unit === 'Y') {\n dur.years = (dur.years ?? 0) + n;\n } else if (unit === 'M') {\n dur.months = (dur.months ?? 0) + n;\n } else if (unit === 'W') {\n dur.weeks = (dur.weeks ?? 0) + n;\n } else if (unit === 'D') {\n dur.days = (dur.days ?? 0) + n;\n } else {\n throw new ParseError(`Invalid duration unit '${unit}' in date part`, this.i);\n }\n } else {\n if (unit === 'H') {\n dur.hours = (dur.hours ?? 0) + n;\n } else if (unit === 'M') {\n dur.minutes = (dur.minutes ?? 0) + n;\n } else if (unit === 'S') {\n dur.seconds = (dur.seconds ?? 0) + n;\n if (fraction !== undefined && fraction.length > 0) {\n dur.secondsFraction = fraction;\n }\n } else {\n throw new ParseError(`Invalid duration unit '${unit}' in time part`, this.i);\n }\n }\n }\n\n // bracket annotations after duration\n while (this.at(TokType.LBracket)) {\n const { raw, critical, pairs } = this.parseBracketGroup();\n annotations.push({ kind: 'Annotation', raw, critical, pairs });\n }\n\n return { kind: 'Duration', ...dur, raw: rawParts.join(''), annotations };\n }\n}\n\n// ---------- Public API ----------\nexport function parseTemporal(src: string): TemporalAst {\n const raw = lexTemporal(src);\n const toks = combineTimezoneOffsets(raw);\n const p = new Parser(toks);\n return p.parseTemporal();\n}\n"], | ||
| "mappings": "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", | ||
| "sources": ["../../src/index.ts", "../../src/lexer-types.ts", "../../src/combineTimezoneOffsets.ts", "../../src/errors.ts", "../../src/helpers/isAlpha.ts", "../../src/helpers/isDigit.ts", "../../src/helpers/isBracketWordChar.ts", "../../src/lexer.ts", "../../src/helpers/toInt.ts", "../../src/parseOffset.ts", "../../src/parser.ts", "../../src/stringify.ts"], | ||
| "sourcesContent": ["// index.ts\n// Main entry point for the temporal parser library\n\n// Export lexer functionality\nexport { combineTimezoneOffsets } from './combineTimezoneOffsets.js';\nexport { lexTemporal } from './lexer.js';\n\n// Export lexer types\nexport type { AnyToken, CombinedToken, Token } from './lexer-types.js';\nexport { CombinedTokType, TokType } from './lexer-types.js';\n\n// Export parser functionality\nexport { parseTemporal } from './parser.js';\n\n// Export offset parser (useful standalone utility)\nexport { parseOffset } from './parseOffset.js';\n\n// Export stringify functionality\nexport {\n stringifyAnnotation,\n stringifyDate,\n stringifyDateTime,\n stringifyDuration,\n stringifyOffset,\n stringifyRange,\n stringifyTemporal,\n stringifyTime,\n stringifyTimeZone,\n} from './stringify.js';\n\n// Export parser types\nexport type {\n AnnotationAst,\n DateAst,\n DateTimeAst,\n DurationAst,\n OffsetAst,\n RangeAst,\n TemporalAst,\n TimeAst,\n TimeZoneAst,\n ValueAst,\n} from './parser-types.js';\n\n// Export errors\nexport { LexError, ParseError } from './errors.js';\n", "// lexer-types.ts\n// Token types and interfaces for the lexer\n\nexport enum TokType {\n // Numbers: sequences of digits (0-9)\n // Examples: \"2025\", \"01\", \"12\", \"59\"\n Number = 'Number',\n\n // Ident (Identifier): sequences of alphabetic characters (A-Z, a-z)\n // This is the generic \"text\" token for letter-based designators.\n // Examples: \"P\" (duration), \"Y\" (year), \"M\" (month), \"gregory\" (calendar)\n // The lexer keeps it simple; the parser interprets the meaning.\n Ident = 'Ident',\n\n // Single-character punctuation tokens\n Dash = 'Dash', // -\n Colon = 'Colon', // :\n Dot = 'Dot', // .\n Plus = 'Plus', // +\n Slash = 'Slash', // /\n Comma = 'Comma', // ,\n Equals = 'Equals', // =\n Underscore = 'Underscore', // _\n Exclamation = 'Exclamation', // ! (critical flag in IXDTF)\n\n // Special letter tokens with dedicated meaning in ISO 8601\n T = 'T', // Date/time separator: 2025-01-01T10:00:00\n Z = 'Z', // UTC timezone marker: 2025-01-01T10:00:00Z\n\n // Bracket tokens for IXDTF annotations/extensions\n LBracket = 'LBracket', // [\n RBracket = 'RBracket', // ]\n\n // BracketText: complex text inside brackets that contains non-letter chars\n // Example: in [u-ca=gregory], \"u-ca\" is BracketText (has dash), \"gregory\" is Ident (letters only)\n // Example: in [Asia/Singapore], \"Asia/Singapore\" is BracketText (has slash)\n BracketText = 'BracketText',\n\n EOF = 'EOF',\n}\n\nexport type Token = {\n type: TokType;\n value: string;\n start: number; // inclusive\n end: number; // exclusive\n};\n\n// Combined token types for timezone offsets\nexport enum CombinedTokType {\n TZOffset = 'TZOffset', // +08:00, -0530, +09\n}\n\nexport type CombinedToken = {\n type: CombinedTokType;\n value: string;\n tokens: Token[];\n start: number;\n end: number;\n};\n\nexport type AnyToken = Token | CombinedToken;\n", "// combineTimezoneOffsets.ts\n// Combines numeric timezone offset tokens into single tokens for easier parsing\n\nimport { type AnyToken, CombinedTokType, type Token, TokType } from './lexer-types.js';\n\n/**\n * Combine numeric timezone offsets into single tokens for easier parsing.\n * This is an optional post-processing step for the lexer output.\n *\n * Combines patterns like:\n * - (+|-) Number Colon Number => +08:00, -05:30\n * - (+|-) Number (2 or 4 digits) => +09, -0530\n *\n * Only combines when context suggests a timezone offset (after time components),\n * NOT after:\n * - Z (mutually exclusive per ISO 8601)\n * - brackets (offset comes BEFORE brackets per RFC 9557/IXDTF)\n * - standalone dates (e.g., 2025-01 where - is a date separator)\n *\n * @param tokens - The raw token array from lexTemporal\n * @returns Array of tokens (either original Token or combined CombinedToken with TZOffset type where applicable)\n *\n * @example\n * ```typescript\n * const raw = lexTemporal('10:00:00+08:00');\n * const combined = combineTimezoneOffsets(raw);\n * // Result includes: { type: 'TZOffset', value: '+08:00' }\n * ```\n */\nexport function combineTimezoneOffsets(tokens: Token[]): AnyToken[] {\n const out: AnyToken[] = [];\n let i = 0;\n\n const peek = (k: number) => tokens[i + k];\n\n // Helper to check if previous tokens suggest this could be a timezone offset\n // Timezone offsets come after time components (HH:MM:SS)\n // NOT after just a date like 2025-01\n // NOT after Z (Z and numeric offsets are mutually exclusive per ISO 8601)\n // NOT after brackets (per RFC 9557/IXDTF: offset comes BEFORE brackets)\n const couldBeTzOffset = () => {\n if (out.length < 1) {\n return false;\n }\n const prev: AnyToken | undefined = out[out.length - 1];\n if (prev == null) {\n return false;\n }\n\n // After Z - NOT valid (Z and numeric offsets are mutually exclusive)\n // Per ISO 8601: use either Z OR +HH:MM, never both\n if (prev.type === TokType.Z) {\n return false;\n }\n\n // After bracket - NOT valid (offset must come BEFORE brackets per RFC 9557)\n // Valid: Time+08:00[Asia/Tokyo]\n // Invalid: [Asia/Tokyo]+08:00\n if (prev.type === TokType.RBracket) {\n return false;\n }\n\n // After a number, check context\n if (prev.type === TokType.Number) {\n if (out.length < 2) {\n return false;\n }\n const prevPrev = out[out.length - 2];\n\n // After a number with colon before it (time component)\n // e.g., ...10:00:00+08:00 - the number before + has a colon before it\n if (prevPrev?.type === TokType.Colon) {\n return true;\n }\n\n // After a number with dot before it (milliseconds)\n // e.g., ...10:00:00.999+08:00 - the number before + has a dot before it\n if (prevPrev?.type === TokType.Dot) {\n return true;\n }\n }\n\n return false;\n };\n\n while (i < tokens.length) {\n const t = tokens[i]!;\n const isSign = t.type === TokType.Plus || t.type === TokType.Dash;\n\n // Only try to combine if context suggests timezone offset\n if (isSign && couldBeTzOffset()) {\n // (+|-) Number Colon Number => +08:00\n if (\n peek(1)?.type === TokType.Number &&\n peek(2)?.type === TokType.Colon &&\n peek(3)?.type === TokType.Number\n ) {\n const originalTokens = [t, peek(1)!, peek(2)!, peek(3)!];\n const start = t.start;\n const end = peek(3)!.end;\n const value = `${t.value}${peek(1)!.value}:${peek(3)!.value}`;\n out.push({ type: CombinedTokType.TZOffset, value, tokens: originalTokens, start, end });\n i += 4;\n continue;\n }\n\n // (+|-) Number where Number is 2 or 4 digits => +09 or -0530\n if (peek(1)?.type === TokType.Number) {\n const num = peek(1)!.value;\n if (num.length === 2 || num.length === 4) {\n const originalTokens = [t, peek(1)!];\n const start = t.start;\n const end = peek(1)!.end;\n const value = `${t.value}${num}`;\n out.push({ type: CombinedTokType.TZOffset, value, tokens: originalTokens, start, end });\n i += 2;\n continue;\n }\n }\n }\n\n out.push(t);\n i++;\n }\n\n return out;\n}\n", "// errors.ts\n// Error classes for lexer and parser\n\nexport class LexError extends Error {\n public readonly position: number;\n constructor(message: string, position: number) {\n super(`${message} at position ${position}`);\n this.name = 'LexError';\n this.position = position;\n }\n}\n\nexport class ParseError extends Error {\n public readonly position: number;\n constructor(message: string, position: number) {\n super(`${message} at token index ${position}`);\n this.name = 'ParseError';\n this.position = position;\n }\n}\n", "/**\n * Check if character is alphabetic (A-Z or a-z)\n * This defines what can be part of an IDENT token in default mode\n * @param c - the character to check\n * @returns true if character is alphabetic, false otherwise\n */\nexport function isAlpha(c: string): boolean {\n return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');\n}\n", "/**\n * Check if character is a digit (0-9)\n * @param c - the character to check\n * @returns true if character is a digit, false otherwise\n */\nexport function isDigit(c: string) {\n return c >= '0' && c <= '9';\n}\n", "import { isAlpha } from './isAlpha.js';\nimport { isDigit } from './isDigit.js';\n\n/**\n * Check if character is valid inside bracket annotations\n * In default mode, IDENT is letters only (keeps date/time parsing clean).\n * In bracket mode, we allow broader \"words\" like:\n * - u-ca (has dash)\n * - Asia/Singapore (has slash)\n * - Etc/GMT+8 (has plus)\n * - iso8601 (has digits)\n * @param c - the character to check\n * @returns true if character is valid in bracket context, false otherwise\n */\nexport function isBracketWordChar(c: string): boolean {\n return (\n isAlpha(c) || isDigit(c) || c === '-' || c === '/' || c === '+' || c === '.' // allow dots in some TZ-ish values\n );\n}\n", "// lexer.ts\n// Temporal (IXDTF / ISO-8601-ish) lexer with support for *extensions/annotations* like calendar.\n//\n// Supports:\n// - Basic date/time tokens: numbers, identifiers, T, Z, punctuation\n// - Ranges: '/'\n// - Durations: 'P' (as IDENT; parser enforces rules)\n// - Time zone forms:\n// - 'Z'\n// - numeric offsets: +08:00 / -0530 / +09 (optionally combined via helper)\n// - bracketed IANA TZ: [Asia/Singapore]\n// - IXDTF-style annotations/extensions in brackets, e.g.:\n// - [u-ca=gregory] (calendar)\n// - [u-tz=Asia/Singapore] (timeZone)\n// - multiple bracket groups: 2025-01-01T00:00Z[u-ca=iso8601][u-tz=UTC]\n//\n// The lexer is permissive; your parser should validate semantics.\n\nimport { LexError } from './errors.js';\nimport { isAlpha } from './helpers/isAlpha.js';\nimport { isBracketWordChar } from './helpers/isBracketWordChar.js';\nimport { isDigit } from './helpers/isDigit.js';\nimport type { Token } from './lexer-types.js';\nimport { TokType } from './lexer-types.js';\n\nexport function lexTemporal(src: string): Token[] {\n const tokens: Token[] = [];\n let i = 0;\n\n const emit = (type: TokType, start: number, end: number, value?: string) => {\n tokens.push({ type, value: value ?? src.slice(start, end), start, end });\n };\n\n // Lex a number: consume consecutive digits\n // Examples: \"2025\", \"01\", \"59\"\n const lexNumber = () => {\n const start = i;\n i++;\n while (i < src.length && isDigit(src[i]!)) {\n i++;\n }\n emit(TokType.Number, start, i);\n };\n\n // Lex an identifier: consume consecutive alphabetic characters\n // This is where IDENT tokens are created!\n //\n // Examples of what becomes IDENT:\n // - Duration designators: P, Y, M, W, D, H, S\n // - Calendar names: gregory, hebrew, iso8601\n // - Any letter sequence: P, YMD, UTC, America\n //\n // Special cases:\n // - \"T\" gets its own token type (datetime separator)\n // - \"Z\" gets its own token type (UTC marker)\n // - Everything else becomes Ident\n //\n // IMPORTANT: We only consume a single letter at a time to allow T and Z\n // to be recognized individually (e.g., \"DT\" should be \"D\" then \"T\", not \"DT\")\n const lexIdent = () => {\n const start = i;\n const ch = src[i]!;\n i++;\n\n // Special handling for T and Z - emit them as special tokens\n if (ch === 'T') {\n emit(TokType.T, start, i, 'T');\n return;\n }\n if (ch === 'Z') {\n emit(TokType.Z, start, i, 'Z');\n return;\n }\n\n // For other letters, consume consecutive letters that are NOT T or Z\n // This allows multi-letter identifiers like \"gregory\" but keeps T and Z separate\n while (i < src.length && isAlpha(src[i]!) && src[i] !== 'T' && src[i] !== 'Z') {\n i++;\n }\n\n emit(TokType.Ident, start, i); // Generic identifier\n };\n\n const lexBracketContent = () => {\n // We are positioned after '['. Emit content tokens until ']'.\n while (i < src.length) {\n const ch = src[i]!;\n\n if (ch === ']') {\n return;\n }\n\n // Skip whitespace inside brackets (rare but harmless)\n if (/\\s/.test(ch)) {\n i++;\n continue;\n }\n\n // Single-char tokens inside brackets\n if (ch === '=') {\n emit(TokType.Equals, i, i + 1);\n i++;\n continue;\n }\n if (ch === '_') {\n emit(TokType.Underscore, i, i + 1);\n i++;\n continue;\n }\n if (ch === ':') {\n emit(TokType.Colon, i, i + 1);\n i++;\n continue;\n }\n if (ch === '-') {\n emit(TokType.Dash, i, i + 1);\n i++;\n continue;\n }\n if (ch === '+') {\n emit(TokType.Plus, i, i + 1);\n i++;\n continue;\n }\n if (ch === '/') {\n emit(TokType.Slash, i, i + 1);\n i++;\n continue;\n }\n if (ch === '.') {\n emit(TokType.Dot, i, i + 1);\n i++;\n continue;\n }\n if (ch === ',') {\n emit(TokType.Comma, i, i + 1);\n i++;\n continue;\n }\n if (ch === '!') {\n emit(TokType.Exclamation, i, i + 1);\n i++;\n continue;\n }\n\n // Words inside brackets (u-ca, gregory, Asia/Singapore, iso8601, Etc/GMT+8, etc.)\n if (isBracketWordChar(ch)) {\n const start = i;\n i++;\n while (i < src.length && isBracketWordChar(src[i]!)) {\n i++;\n }\n const text = src.slice(start, i);\n\n // Decision: Ident vs BracketText\n // - If purely letters (A-Z, a-z): emit as Ident\n // Examples: \"gregory\", \"hebrew\", \"u\", \"ca\"\n // - If contains other chars (digits, -, /, +, .): emit as BracketText\n // Examples: \"u-ca\", \"Asia/Singapore\", \"iso8601\", \"Etc/GMT+8\"\n if (/^[A-Za-z]+$/.test(text)) {\n emit(TokType.Ident, start, i, text);\n } else {\n emit(TokType.BracketText, start, i, text);\n }\n\n continue;\n }\n\n throw new LexError(`Unexpected character ${JSON.stringify(ch)} inside brackets`, i);\n }\n\n throw new LexError(\"Unterminated bracket annotation, expected ']'\", src.length);\n };\n\n while (i < src.length) {\n const ch = src[i]!;\n\n // Skip whitespace\n if (/\\s/.test(ch)) {\n i++;\n continue;\n }\n\n // Bracketed extensions/annotations/timezone IDs\n if (ch === '[') {\n emit(TokType.LBracket, i, i + 1);\n i++;\n\n lexBracketContent();\n\n if (i >= src.length || src[i] !== ']') {\n throw new LexError(\"Unterminated bracket annotation, expected ']'\", i);\n }\n emit(TokType.RBracket, i, i + 1);\n i++;\n continue;\n }\n\n // Numbers: sequences of digits\n if (isDigit(ch)) {\n lexNumber();\n continue;\n }\n\n // Letters: this is where IDENT tokens are created!\n // Any alphabetic character triggers identifier lexing.\n // Examples: P (duration), Y (year), M (month), gregory, UTC\n if (isAlpha(ch)) {\n lexIdent();\n continue;\n }\n\n // Punctuation (default mode)\n switch (ch) {\n case '-':\n emit(TokType.Dash, i, i + 1);\n i++;\n continue;\n case ':':\n emit(TokType.Colon, i, i + 1);\n i++;\n continue;\n case '.':\n emit(TokType.Dot, i, i + 1);\n i++;\n continue;\n case '+':\n emit(TokType.Plus, i, i + 1);\n i++;\n continue;\n case '/':\n emit(TokType.Slash, i, i + 1);\n i++;\n continue;\n case ',':\n emit(TokType.Comma, i, i + 1);\n i++;\n continue;\n case '=':\n emit(TokType.Equals, i, i + 1);\n i++;\n continue;\n case '_':\n emit(TokType.Underscore, i, i + 1);\n i++;\n continue;\n default:\n throw new LexError(`Unexpected character ${JSON.stringify(ch)}`, i);\n }\n }\n\n emit(TokType.EOF, src.length, src.length, '');\n return tokens;\n}\n", "import { ParseError } from '../errors.js';\n\n/**\n * Convert a string to an integer, throwing a ParseError if invalid\n * @param s - the string to convert\n * @param label - a descriptive label for error messages (e.g., \"year\", \"month\")\n * @param position - the position in the token stream for error reporting\n * @returns the parsed integer value\n * @throws {ParseError} if the string is not a valid integer\n */\nexport function toInt(s: string, label: string, position: number): number {\n const n = Number(s);\n if (!Number.isFinite(n) || !Number.isInteger(n)) {\n throw new ParseError(`Invalid integer for ${label}: ${JSON.stringify(s)}`, position);\n }\n return n;\n}\n", "// parseOffset.ts\n// Parse timezone offset strings into structured components\n\nimport { ParseError } from './errors.js';\nimport { toInt } from './helpers/toInt.js';\nimport type { OffsetAst } from './parser-types.js';\n\n// Timezone offset validation constants\n// Per ISO 8601 and real-world usage:\n// - Westernmost: UTC-12:00 (Baker Island, Howland Island)\n// - Easternmost: UTC+14:00 (Line Islands, Kiritimati)\nconst MAX_OFFSET_HOURS = 14;\nconst MAX_MINUTES = 59;\n\n/**\n * Parse a timezone offset string into structured components.\n *\n * Supports three formats:\n * - Standard: +08:00, -05:30\n * - Compact: +0530, -0800\n * - Short: +09, -05\n *\n * Valid timezone offset ranges (per ISO 8601):\n * - Hours: 0-14 (UTC-12:00 to UTC+14:00)\n * - Minutes: 0-59\n *\n * Note: This function only parses numeric offsets (e.g., +08:00).\n * For UTC, use 'Z' (UtcOffset kind) which is semantically different from +00:00.\n * Per RFC 3339 section 4.3, -00:00 indicates \"local time with unknown offset\".\n *\n * @param offsetString - The offset string to parse (e.g., '+08:00', '-0530', '+09')\n * @param position - Position in token stream for error reporting (optional)\n * @returns Parsed offset with sign, hours, and minutes\n * @throws {ParseError} If the offset format is invalid or out of range\n *\n * @example\n * ```typescript\n * parseOffset('+08:00') // { kind: 'NumericOffset', sign: '+', hours: 8, minutes: 0, raw: '+08:00' }\n * parseOffset('-05:30') // { kind: 'NumericOffset', sign: '-', hours: 5, minutes: 30, raw: '-05:30' }\n * parseOffset('+0530') // { kind: 'NumericOffset', sign: '+', hours: 5, minutes: 30, raw: '+0530' }\n * parseOffset('+09') // { kind: 'NumericOffset', sign: '+', hours: 9, minutes: 0, raw: '+09' }\n * parseOffset('+14:00') // { kind: 'NumericOffset', sign: '+', hours: 14, minutes: 0, raw: '+14:00' }\n * ```\n */\nexport function parseOffset(offsetString: string, position = 0): OffsetAst {\n // Validate input is not empty\n if (offsetString.length === 0) {\n throw new ParseError('Offset string cannot be empty', position);\n }\n\n const raw = offsetString;\n\n // Validate first character is sign and assert type\n const sign = raw[0];\n if (sign !== '+' && sign !== '-') {\n throw new ParseError(`Offset must start with + or -, got ${JSON.stringify(sign)}`, position);\n }\n // Type assertion: TypeScript now knows sign is '+' | '-'\n const validSign: '+' | '-' = sign;\n\n const rest = raw.slice(1);\n\n // Validate rest is not empty\n if (rest.length === 0) {\n throw new ParseError(`Offset sign must be followed by digits`, position);\n }\n\n let hours: number;\n let minutes: number;\n\n if (rest.includes(':')) {\n // Format: +08:00 or -05:30\n const parts = rest.split(':');\n if (parts.length !== 2) {\n throw new ParseError(`Invalid offset format: ${raw} (expected HH:MM)`, position);\n }\n // After length check, we know parts[0] and parts[1] exist\n const [h, m] = parts;\n hours = toInt(h!, 'offset hours', position);\n minutes = toInt(m!, 'offset minutes', position);\n } else if (rest.length === 4) {\n // Format: +0530 or -0800\n hours = toInt(rest.slice(0, 2), 'offset hours', position);\n minutes = toInt(rest.slice(2, 4), 'offset minutes', position);\n } else if (rest.length === 2) {\n // Format: +08 or -05\n hours = toInt(rest, 'offset hours', position);\n minutes = 0;\n } else {\n throw new ParseError(\n `Invalid offset format: ${raw} (expected +HH:MM, +HHMM, or +HH)`,\n position,\n );\n }\n\n // Validate ranges\n if (hours < 0 || hours > MAX_OFFSET_HOURS) {\n throw new ParseError(`Offset hours must be 0-${MAX_OFFSET_HOURS}, got ${hours}`, position);\n }\n if (minutes < 0 || minutes > MAX_MINUTES) {\n throw new ParseError(`Offset minutes must be 0-${MAX_MINUTES}, got ${minutes}`, position);\n }\n\n return { kind: 'NumericOffset', sign: validSign, hours, minutes, raw };\n}\n", "// parser.ts\n// A practical recursive-descent parser that works with the lexer we wrote.\n// It builds an AST for a useful Temporal subset:\n// - date/time (with optional fractional seconds)\n// - timezone: Z | numeric offset (+08:00, -0530, +09) | bracket TZ id [Asia/Singapore]\n// - duration: P... with optional T part\n// - range: <expr> \"/\" <expr> where each side can be dateTime/date/duration, and either side may be empty (open range)\n// - bracket extensions like [u-ca=gregory], [u-tz=Asia/Singapore], etc. (stored as key/value pairs)\n\nimport { combineTimezoneOffsets } from './combineTimezoneOffsets.js';\nimport { ParseError } from './errors.js';\nimport { toInt } from './helpers/toInt.js';\nimport { lexTemporal } from './lexer.js';\nimport { type AnyToken, CombinedTokType, TokType } from './lexer-types.js';\nimport { parseOffset } from './parseOffset.js';\nimport type {\n AnnotationAst,\n DateAst,\n DateTimeAst,\n DurationAst,\n OffsetAst,\n TemporalAst,\n TimeAst,\n TimeZoneAst,\n ValueAst,\n} from './parser-types.js';\n\n// ---------- Parser ----------\n\nclass Parser {\n private i = 0;\n constructor(private toks: AnyToken[]) {}\n\n private peek(k = 0): AnyToken {\n return this.toks[Math.min(this.i + k, this.toks.length - 1)]!;\n }\n private at(type: TokType | CombinedTokType): boolean {\n return this.peek().type === type;\n }\n private eat(type: TokType | CombinedTokType): AnyToken {\n const t = this.peek();\n if (t.type !== type) {\n throw new ParseError(`Expected ${type} but got ${t.type}`, this.i);\n }\n this.i++;\n return t;\n }\n private tryEat(type: TokType | CombinedTokType): AnyToken | null {\n if (this.at(type)) {\n return this.eat(type);\n }\n return null;\n }\n private eof(): boolean {\n return this.at(TokType.EOF);\n }\n\n // Top-level: parse a range if we see '/', otherwise a single value.\n parseTemporal(): TemporalAst {\n // Open-start range: \"/...\"\n if (this.tryEat(TokType.Slash)) {\n const end = this.parseValueOrNull(); // allow empty end too\n this.eat(TokType.EOF);\n return { kind: 'Range', start: null, end };\n }\n\n const first = this.parseValueOrNull();\n // If first is null, only valid if it's \"/\" (handled above). So error:\n if (!first) {\n throw new ParseError('Expected a value', this.i);\n }\n\n if (this.tryEat(TokType.Slash)) {\n const second = this.parseValueOrNull(); // open-ended allowed\n this.eat(TokType.EOF);\n return { kind: 'Range', start: first, end: second };\n }\n\n this.eat(TokType.EOF);\n return first;\n }\n\n private parseValueOrNull(): ValueAst | null {\n // Empty side for open range:\n if (this.at(TokType.EOF) || this.at(TokType.Slash) || this.at(TokType.RBracket)) {\n return null;\n }\n\n // Duration starts with IDENT \"P\" (as produced by lexer)\n if (this.at(TokType.Ident) && String(this.peek().value) === 'P') {\n return this.parseDuration();\n }\n\n // Otherwise parse a date/datetime (common case)\n return this.parseDateTime();\n }\n\n // -------- DateTime parsing --------\n // Supports:\n // - YYYY\n // - YYYY-MM\n // - YYYY-MM-DD\n // - + time part with T: YYYY-MM-DDTHH:MM[:SS[.fff]]\n // - offset: Z | +08:00 | -0530\n // - timezone: [Asia/Singapore] (first bracketed IANA timezone)\n // - annotations: [u-ca=...][u-tz=...] (other bracketed content)\n private parseDateTime(): DateTimeAst {\n const date = this.parseDate();\n\n let time: DateTimeAst['time'] | undefined;\n\n // time separator is TokType.T (from lexer special-casing \"T\")\n if (this.tryEat(TokType.T)) {\n time = this.parseTime();\n }\n\n // Parse numeric offset (Z or +HH:MM)\n const offset = this.parseOffsetIfPresent();\n\n // Parse bracket groups: first IANA timezone becomes timeZone, rest are annotations\n const annotations: AnnotationAst[] = [];\n let timeZone: TimeZoneAst | undefined;\n\n while (this.at(TokType.LBracket)) {\n const { raw, critical, isLikelyTzId, pairs } = this.parseBracketGroup();\n\n // First timezone-like bracket becomes the timeZone field\n // Note: Even though IXDTF doesn't typically use critical flags for timezones,\n // we preserve it in case the input has it\n if (timeZone === undefined && isLikelyTzId) {\n // Strip leading ! from id (it's stored in the critical flag)\n const id = critical ? raw.slice(1) : raw;\n timeZone = { kind: 'IanaTimeZone', id, critical };\n } else {\n annotations.push({ kind: 'Annotation', raw, critical, pairs });\n }\n }\n\n return { kind: 'DateTime', date, time, offset, timeZone, annotations };\n }\n\n private parseDate(): DateAst {\n // Check for optional leading dash (negative year / BC date)\n // ISO 8601: Year 0 = 1 BC, Year -1 = 2 BC, etc.\n const isNegative = this.tryEat(TokType.Dash);\n \n const yTok = this.eat(TokType.Number);\n let year = toInt(yTok.value, 'year', this.i);\n if (isNegative) {\n year = -year;\n }\n\n // Optional -MM\n if (!this.tryEat(TokType.Dash)) {\n return { kind: 'Date', year };\n }\n const mTok = this.eat(TokType.Number);\n const month = toInt(mTok.value, 'month', this.i);\n\n // Optional -DD\n if (!this.tryEat(TokType.Dash)) {\n return { kind: 'Date', year, month };\n }\n const dTok = this.eat(TokType.Number);\n const day = toInt(dTok.value, 'day', this.i);\n\n return { kind: 'Date', year, month, day };\n }\n\n private parseTime(): TimeAst {\n const hTok = this.eat(TokType.Number);\n const hour = toInt(hTok.value, 'hour', this.i);\n\n this.eat(TokType.Colon);\n const minTok = this.eat(TokType.Number);\n const minute = toInt(minTok.value, 'minute', this.i);\n\n let second: number | undefined;\n let fraction: string | undefined;\n\n if (this.tryEat(TokType.Colon)) {\n const sTok = this.eat(TokType.Number);\n second = toInt(sTok.value, 'second', this.i);\n\n // ISO 8601 allows both . and , as decimal separators for fractional seconds\n if (this.tryEat(TokType.Dot) || this.tryEat(TokType.Comma)) {\n const fracTok = this.eat(TokType.Number);\n fraction = fracTok.value; // keep raw digits\n }\n }\n\n return { kind: 'Time', hour, minute, second, fraction };\n }\n\n /**\n * Parse timezone offset: Z | +HH:MM | -HH:MM\n *\n * Important semantic distinction per RFC 3339 and ISO 8601:\n *\n * - Z (Zulu time):\n * Explicitly indicates UTC. Used when the time is known to be in UTC.\n * Has no sign ambiguity - it's always UTC, never local time.\n *\n * - +00:00 (zero positive offset):\n * Indicates local time at the prime meridian (UTC+0 timezone).\n * Semantically different from Z - this is a local time that happens to match UTC.\n *\n * - -00:00 (zero negative offset):\n * Per RFC 3339 section 4.3: indicates local time with unknown UTC offset.\n * Used when the time is known to be local but the offset is not known.\n * This is semantically very different from both Z and +00:00.\n * https://www.rfc-editor.org/rfc/rfc3339.html#section-4.3\n *\n * We preserve this distinction by treating Z as UtcOffset (no sign, no components)\n * while +00:00 and -00:00 are NumericOffset (with sign and parsed components).\n */\n private parseOffsetIfPresent(): OffsetAst | undefined {\n if (this.tryEat(TokType.Z)) {\n return { kind: 'UtcOffset' };\n }\n\n // combined numeric offset token if you used combineTimezoneOffsets()\n if (this.at(CombinedTokType.TZOffset)) {\n const t = this.eat(CombinedTokType.TZOffset);\n return parseOffset(t.value, this.i);\n }\n\n return undefined;\n }\n\n // -------- Bracket group parsing --------\n // Consumes: [ ... ]\n // Returns:\n // - raw inner text (reconstructed)\n // - critical flag (true if starts with !)\n // - pairs if it looks like key=value (or flags)\n // - isLikelyTzId for things like [Asia/Singapore]\n private parseBracketGroup(): {\n raw: string;\n critical: boolean;\n pairs: Record<string, string | true>;\n isLikelyTzId: boolean;\n } {\n this.eat(TokType.LBracket);\n\n const parts: string[] = [];\n const pairs: Record<string, string | true> = {};\n\n // Check for critical flag (IXDTF: [!u-ca=gregory])\n const critical = this.tryEat(TokType.Exclamation) != null;\n if (critical) {\n parts.push('!');\n }\n\n // Gather tokens until RBracket\n while (!this.at(TokType.RBracket)) {\n if (this.eof()) {\n throw new ParseError('Unterminated bracket group', this.i);\n }\n\n const t = this.peek();\n this.i++;\n\n parts.push(String(t.value));\n }\n\n this.eat(TokType.RBracket);\n\n const raw = parts.join('');\n\n // Best-effort parse into key/value pairs:\n // Support \"key=value\" and \"flag\" and \"k=v,k2=v2\"\n // Strip leading ! if present for parsing\n const contentToParse = critical ? raw.slice(1) : raw;\n const segments = contentToParse\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean);\n for (const seg of segments) {\n const eq = seg.indexOf('=');\n if (eq === -1) {\n pairs[seg] = true;\n } else {\n const k = seg.slice(0, eq).trim();\n const v = seg.slice(eq + 1).trim();\n if (k) {\n pairs[k] = v || true;\n }\n }\n }\n\n // Heuristic: likely TZ ID if no '=' present and contains '/' (e.g. Asia/Singapore) or starts with Etc/\n // We now preserve critical flag even for timezones, so don't exclude them\n const isLikelyTzId =\n contentToParse.indexOf('=') === -1 &&\n (contentToParse.includes('/') || contentToParse.startsWith('Etc/'));\n\n // Note: If you want STRICTLY separate tz-id brackets vs extension brackets, do it here.\n\n return { raw, critical, pairs, isLikelyTzId };\n }\n\n // -------- Duration parsing --------\n // Grammar (subset):\n // P (Y M W D)* (T (H M S)*)?\n // Examples:\n // P1Y2M\n // P3W\n // P10DT2H30M\n // PT1.5S\n private parseDuration(): DurationAst {\n // Collect raw string as we parse\n const rawParts: string[] = [];\n const annotations: AnnotationAst[] = [];\n\n const pTok = this.eat(TokType.Ident);\n if (String(pTok.value) !== 'P') {\n throw new ParseError(\"Duration must start with 'P'\", this.i);\n }\n rawParts.push('P');\n\n let inTime = false;\n\n const dur: Omit<DurationAst, 'kind' | 'raw' | 'annotations'> = {};\n\n // Parse designators until we hit bracket annotations, slash, or EOF\n while (!this.eof() && !this.at(TokType.Slash) && !this.at(TokType.LBracket)) {\n // Time marker T\n if (this.tryEat(TokType.T)) {\n rawParts.push('T');\n inTime = true;\n continue;\n }\n\n // Expect a number (possibly with fraction for seconds)\n const numTok = this.tryEat(TokType.Number);\n if (!numTok) {\n break;\n } // be permissive; let parser user decide if this is error-worthy\n rawParts.push(numTok.value);\n\n let fraction: string | undefined;\n // ISO 8601 allows both . and , as decimal separators\n const dotOrComma = this.tryEat(TokType.Dot) || this.tryEat(TokType.Comma);\n if (dotOrComma) {\n rawParts.push(dotOrComma.value); // Preserve the actual separator (. or ,)\n const fracTok = this.eat(TokType.Number);\n rawParts.push(fracTok.value);\n fraction = fracTok.value;\n }\n\n // Unit is IDENT (Y,M,W,D,H,M,S). Note M is months if !inTime else minutes.\n const unitTok = this.eat(TokType.Ident);\n const unit = String(unitTok.value);\n rawParts.push(unit);\n\n const n = toInt(numTok.value, 'duration number', this.i);\n\n if (!inTime) {\n if (unit === 'Y') {\n dur.years = (dur.years ?? 0) + n;\n } else if (unit === 'M') {\n dur.months = (dur.months ?? 0) + n;\n } else if (unit === 'W') {\n dur.weeks = (dur.weeks ?? 0) + n;\n } else if (unit === 'D') {\n dur.days = (dur.days ?? 0) + n;\n } else {\n throw new ParseError(`Invalid duration unit '${unit}' in date part`, this.i);\n }\n } else {\n if (unit === 'H') {\n dur.hours = (dur.hours ?? 0) + n;\n } else if (unit === 'M') {\n dur.minutes = (dur.minutes ?? 0) + n;\n } else if (unit === 'S') {\n dur.seconds = (dur.seconds ?? 0) + n;\n if (fraction !== undefined && fraction.length > 0) {\n dur.secondsFraction = fraction;\n }\n } else {\n throw new ParseError(`Invalid duration unit '${unit}' in time part`, this.i);\n }\n }\n }\n\n // bracket annotations after duration\n while (this.at(TokType.LBracket)) {\n const { raw, critical, pairs } = this.parseBracketGroup();\n annotations.push({ kind: 'Annotation', raw, critical, pairs });\n }\n\n return { kind: 'Duration', ...dur, raw: rawParts.join(''), annotations };\n }\n}\n\n// ---------- Public API ----------\nexport function parseTemporal(src: string): TemporalAst {\n const raw = lexTemporal(src);\n const toks = combineTimezoneOffsets(raw);\n const p = new Parser(toks);\n return p.parseTemporal();\n}\n", "// stringify.ts\n// Convert AST back to temporal string representation\n\nimport type {\n AnnotationAst,\n DateAst,\n DateTimeAst,\n DurationAst,\n OffsetAst,\n RangeAst,\n TemporalAst,\n TimeAst,\n TimeZoneAst,\n} from './parser-types.js';\n\n/**\n * Convert a temporal AST back to its string representation.\n *\n * @param ast - The AST to stringify\n * @returns ISO 8601 / IXDTF formatted string\n *\n * @example\n * ```typescript\n * const ast = parseTemporal('2025-01-12T10:00:00+08:00');\n * const str = stringifyTemporal(ast);\n * // '2025-01-12T10:00:00+08:00'\n * ```\n */\nexport function stringifyTemporal(ast: TemporalAst): string {\n if (ast.kind === 'Range') {\n return stringifyRange(ast);\n }\n if (ast.kind === 'Duration') {\n return stringifyDuration(ast);\n }\n return stringifyDateTime(ast);\n}\n\n/**\n * Stringify a date AST to ISO 8601 format.\n *\n * @param date - The date AST\n * @returns Date string (YYYY, YYYY-MM, or YYYY-MM-DD)\n */\nexport function stringifyDate(date: DateAst): string {\n // Handle negative years (BC dates) - ISO 8601 uses negative years\n // Year 0 = 1 BC, Year -1 = 2 BC, etc.\n let yearStr: string;\n if (date.year < 0) {\n // For negative years, pad the absolute value and prepend the minus sign\n yearStr = '-' + Math.abs(date.year).toString().padStart(4, '0');\n } else {\n yearStr = date.year.toString().padStart(4, '0');\n }\n\n const parts: string[] = [yearStr];\n\n if (date.month != null) {\n parts.push(date.month.toString().padStart(2, '0'));\n\n if (date.day != null) {\n parts.push(date.day.toString().padStart(2, '0'));\n }\n }\n\n return parts.join('-');\n}\n\n/**\n * Stringify a time AST to ISO 8601 format.\n *\n * @param time - The time AST\n * @returns Time string (HH:MM, HH:MM:SS, or HH:MM:SS.fff)\n */\nexport function stringifyTime(time: TimeAst): string {\n const parts: string[] = [\n time.hour.toString().padStart(2, '0'),\n time.minute.toString().padStart(2, '0'),\n ];\n\n if (time.second != null) {\n let secondStr = time.second.toString().padStart(2, '0');\n if (time.fraction != null) {\n secondStr += `.${time.fraction}`;\n }\n parts.push(secondStr);\n }\n\n return parts.join(':');\n}\n\n/**\n * Stringify a timezone offset AST to canonical format.\n *\n * @param offset - The offset AST\n * @returns Offset string in canonical format (Z or \u00B1HH:MM)\n */\nexport function stringifyOffset(offset: OffsetAst): string {\n if (offset.kind === 'UtcOffset') {\n return 'Z';\n }\n // Return canonical format: +HH:MM or -HH:MM\n const hours = offset.hours.toString().padStart(2, '0');\n const minutes = offset.minutes.toString().padStart(2, '0');\n return `${offset.sign}${hours}:${minutes}`;\n}\n\n/**\n * Stringify a timezone AST.\n *\n * @param timeZone - The timezone AST\n * @returns Timezone string [Asia/Singapore] or [!Asia/Singapore]\n */\nexport function stringifyTimeZone(timeZone: TimeZoneAst): string {\n const id = timeZone.critical ? `!${timeZone.id}` : timeZone.id;\n return `[${id}]`;\n}\n\n/**\n * Stringify an annotation AST.\n *\n * @param annotation - The annotation AST\n * @returns Annotation string [u-ca=gregory] or [!u-ca=gregory]\n */\nexport function stringifyAnnotation(annotation: AnnotationAst): string {\n return `[${annotation.raw}]`;\n}\n\n/**\n * Stringify a datetime AST to ISO 8601 / IXDTF format.\n *\n * @param dateTime - The datetime AST\n * @returns DateTime string with optional time, offset, timezone, and annotations\n */\nexport function stringifyDateTime(dateTime: DateTimeAst): string {\n let result = stringifyDate(dateTime.date);\n\n if (dateTime.time) {\n result += `T${stringifyTime(dateTime.time)}`;\n }\n\n if (dateTime.offset) {\n result += stringifyOffset(dateTime.offset);\n }\n\n if (dateTime.timeZone) {\n result += stringifyTimeZone(dateTime.timeZone);\n }\n\n for (const annotation of dateTime.annotations) {\n result += stringifyAnnotation(annotation);\n }\n\n return result;\n}\n\n/**\n * Stringify a duration AST to ISO 8601 format.\n *\n * @param duration - The duration AST\n * @returns Duration string (P1Y2M3DT4H5M6S)\n */\nexport function stringifyDuration(duration: DurationAst): string {\n // Always reconstruct from components to ensure normalization\n let result = 'P';\n\n // Date part - include component if defined (even if zero)\n if (duration.years != null) {\n result += `${duration.years}Y`;\n }\n if (duration.months != null) {\n result += `${duration.months}M`;\n }\n if (duration.weeks != null) {\n result += `${duration.weeks}W`;\n }\n if (duration.days != null) {\n result += `${duration.days}D`;\n }\n\n // Time part - add T separator if any time component is defined\n const hasTimePart =\n duration.hours != null || duration.minutes != null || duration.seconds != null;\n if (hasTimePart) {\n result += 'T';\n if (duration.hours != null) {\n result += `${duration.hours}H`;\n }\n if (duration.minutes != null) {\n result += `${duration.minutes}M`;\n }\n if (duration.seconds != null) {\n result += `${duration.seconds}`;\n if (duration.secondsFraction != null && duration.secondsFraction.length > 0) {\n result += `.${duration.secondsFraction}`;\n }\n result += 'S';\n }\n }\n\n // Annotations\n for (const annotation of duration.annotations) {\n result += stringifyAnnotation(annotation);\n }\n\n return result;\n}\n\n/**\n * Stringify a range AST to ISO 8601 format.\n *\n * @param range - The range AST\n * @returns Range string (start/end, /end, or start/)\n */\nexport function stringifyRange(range: RangeAst): string {\n const start = range.start\n ? range.start.kind === 'Duration'\n ? stringifyDuration(range.start)\n : stringifyDateTime(range.start)\n : '';\n\n const end = range.end\n ? range.end.kind === 'Duration'\n ? stringifyDuration(range.end)\n : stringifyDateTime(range.end)\n : '';\n\n return `${start}/${end}`;\n}\n"], | ||
| "mappings": "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", | ||
| "names": ["TokType", "CombinedTokType"] | ||
| } |
+132
-5
@@ -440,4 +440,8 @@ // src/lexer-types.ts | ||
| parseDate() { | ||
| const isNegative = this.tryEat("Dash" /* Dash */); | ||
| const yTok = this.eat("Number" /* Number */); | ||
| const year = toInt(yTok.value, "year", this.i); | ||
| let year = toInt(yTok.value, "year", this.i); | ||
| if (isNegative) { | ||
| year = -year; | ||
| } | ||
| if (!this.tryEat("Dash" /* Dash */)) { | ||
@@ -466,3 +470,3 @@ return { kind: "Date", year }; | ||
| second = toInt(sTok.value, "second", this.i); | ||
| if (this.tryEat("Dot" /* Dot */)) { | ||
| if (this.tryEat("Dot" /* Dot */) || this.tryEat("Comma" /* Comma */)) { | ||
| const fracTok = this.eat("Number" /* Number */); | ||
@@ -578,4 +582,5 @@ fraction = fracTok.value; | ||
| let fraction; | ||
| if (this.tryEat("Dot" /* Dot */)) { | ||
| rawParts.push("."); | ||
| const dotOrComma = this.tryEat("Dot" /* Dot */) || this.tryEat("Comma" /* Comma */); | ||
| if (dotOrComma) { | ||
| rawParts.push(dotOrComma.value); | ||
| const fracTok = this.eat("Number" /* Number */); | ||
@@ -629,2 +634,115 @@ rawParts.push(fracTok.value); | ||
| } | ||
| // src/stringify.ts | ||
| function stringifyTemporal(ast) { | ||
| if (ast.kind === "Range") { | ||
| return stringifyRange(ast); | ||
| } | ||
| if (ast.kind === "Duration") { | ||
| return stringifyDuration(ast); | ||
| } | ||
| return stringifyDateTime(ast); | ||
| } | ||
| function stringifyDate(date) { | ||
| let yearStr; | ||
| if (date.year < 0) { | ||
| yearStr = "-" + Math.abs(date.year).toString().padStart(4, "0"); | ||
| } else { | ||
| yearStr = date.year.toString().padStart(4, "0"); | ||
| } | ||
| const parts = [yearStr]; | ||
| if (date.month != null) { | ||
| parts.push(date.month.toString().padStart(2, "0")); | ||
| if (date.day != null) { | ||
| parts.push(date.day.toString().padStart(2, "0")); | ||
| } | ||
| } | ||
| return parts.join("-"); | ||
| } | ||
| function stringifyTime(time) { | ||
| const parts = [ | ||
| time.hour.toString().padStart(2, "0"), | ||
| time.minute.toString().padStart(2, "0") | ||
| ]; | ||
| if (time.second != null) { | ||
| let secondStr = time.second.toString().padStart(2, "0"); | ||
| if (time.fraction != null) { | ||
| secondStr += `.${time.fraction}`; | ||
| } | ||
| parts.push(secondStr); | ||
| } | ||
| return parts.join(":"); | ||
| } | ||
| function stringifyOffset(offset) { | ||
| if (offset.kind === "UtcOffset") { | ||
| return "Z"; | ||
| } | ||
| const hours = offset.hours.toString().padStart(2, "0"); | ||
| const minutes = offset.minutes.toString().padStart(2, "0"); | ||
| return `${offset.sign}${hours}:${minutes}`; | ||
| } | ||
| function stringifyTimeZone(timeZone) { | ||
| const id = timeZone.critical ? `!${timeZone.id}` : timeZone.id; | ||
| return `[${id}]`; | ||
| } | ||
| function stringifyAnnotation(annotation) { | ||
| return `[${annotation.raw}]`; | ||
| } | ||
| function stringifyDateTime(dateTime) { | ||
| let result = stringifyDate(dateTime.date); | ||
| if (dateTime.time) { | ||
| result += `T${stringifyTime(dateTime.time)}`; | ||
| } | ||
| if (dateTime.offset) { | ||
| result += stringifyOffset(dateTime.offset); | ||
| } | ||
| if (dateTime.timeZone) { | ||
| result += stringifyTimeZone(dateTime.timeZone); | ||
| } | ||
| for (const annotation of dateTime.annotations) { | ||
| result += stringifyAnnotation(annotation); | ||
| } | ||
| return result; | ||
| } | ||
| function stringifyDuration(duration) { | ||
| let result = "P"; | ||
| if (duration.years != null) { | ||
| result += `${duration.years}Y`; | ||
| } | ||
| if (duration.months != null) { | ||
| result += `${duration.months}M`; | ||
| } | ||
| if (duration.weeks != null) { | ||
| result += `${duration.weeks}W`; | ||
| } | ||
| if (duration.days != null) { | ||
| result += `${duration.days}D`; | ||
| } | ||
| const hasTimePart = duration.hours != null || duration.minutes != null || duration.seconds != null; | ||
| if (hasTimePart) { | ||
| result += "T"; | ||
| if (duration.hours != null) { | ||
| result += `${duration.hours}H`; | ||
| } | ||
| if (duration.minutes != null) { | ||
| result += `${duration.minutes}M`; | ||
| } | ||
| if (duration.seconds != null) { | ||
| result += `${duration.seconds}`; | ||
| if (duration.secondsFraction != null && duration.secondsFraction.length > 0) { | ||
| result += `.${duration.secondsFraction}`; | ||
| } | ||
| result += "S"; | ||
| } | ||
| } | ||
| for (const annotation of duration.annotations) { | ||
| result += stringifyAnnotation(annotation); | ||
| } | ||
| return result; | ||
| } | ||
| function stringifyRange(range) { | ||
| const start = range.start ? range.start.kind === "Duration" ? stringifyDuration(range.start) : stringifyDateTime(range.start) : ""; | ||
| const end = range.end ? range.end.kind === "Duration" ? stringifyDuration(range.end) : stringifyDateTime(range.end) : ""; | ||
| return `${start}/${end}`; | ||
| } | ||
| export { | ||
@@ -638,4 +756,13 @@ CombinedTokType, | ||
| parseOffset, | ||
| parseTemporal | ||
| parseTemporal, | ||
| stringifyAnnotation, | ||
| stringifyDate, | ||
| stringifyDateTime, | ||
| stringifyDuration, | ||
| stringifyOffset, | ||
| stringifyRange, | ||
| stringifyTemporal, | ||
| stringifyTime, | ||
| stringifyTimeZone | ||
| }; | ||
| //# sourceMappingURL=index.mjs.map |
| { | ||
| "version": 3, | ||
| "sources": ["../../src/lexer-types.ts", "../../src/combineTimezoneOffsets.ts", "../../src/errors.ts", "../../src/helpers/isAlpha.ts", "../../src/helpers/isDigit.ts", "../../src/helpers/isBracketWordChar.ts", "../../src/lexer.ts", "../../src/helpers/toInt.ts", "../../src/parseOffset.ts", "../../src/parser.ts"], | ||
| "sourcesContent": ["// lexer-types.ts\n// Token types and interfaces for the lexer\n\nexport enum TokType {\n // Numbers: sequences of digits (0-9)\n // Examples: \"2025\", \"01\", \"12\", \"59\"\n Number = 'Number',\n\n // Ident (Identifier): sequences of alphabetic characters (A-Z, a-z)\n // This is the generic \"text\" token for letter-based designators.\n // Examples: \"P\" (duration), \"Y\" (year), \"M\" (month), \"gregory\" (calendar)\n // The lexer keeps it simple; the parser interprets the meaning.\n Ident = 'Ident',\n\n // Single-character punctuation tokens\n Dash = 'Dash', // -\n Colon = 'Colon', // :\n Dot = 'Dot', // .\n Plus = 'Plus', // +\n Slash = 'Slash', // /\n Comma = 'Comma', // ,\n Equals = 'Equals', // =\n Underscore = 'Underscore', // _\n Exclamation = 'Exclamation', // ! (critical flag in IXDTF)\n\n // Special letter tokens with dedicated meaning in ISO 8601\n T = 'T', // Date/time separator: 2025-01-01T10:00:00\n Z = 'Z', // UTC timezone marker: 2025-01-01T10:00:00Z\n\n // Bracket tokens for IXDTF annotations/extensions\n LBracket = 'LBracket', // [\n RBracket = 'RBracket', // ]\n\n // BracketText: complex text inside brackets that contains non-letter chars\n // Example: in [u-ca=gregory], \"u-ca\" is BracketText (has dash), \"gregory\" is Ident (letters only)\n // Example: in [Asia/Singapore], \"Asia/Singapore\" is BracketText (has slash)\n BracketText = 'BracketText',\n\n EOF = 'EOF',\n}\n\nexport type Token = {\n type: TokType;\n value: string;\n start: number; // inclusive\n end: number; // exclusive\n};\n\n// Combined token types for timezone offsets\nexport enum CombinedTokType {\n TZOffset = 'TZOffset', // +08:00, -0530, +09\n}\n\nexport type CombinedToken = {\n type: CombinedTokType;\n value: string;\n tokens: Token[];\n start: number;\n end: number;\n};\n\nexport type AnyToken = Token | CombinedToken;\n", "// combineTimezoneOffsets.ts\n// Combines numeric timezone offset tokens into single tokens for easier parsing\n\nimport { type AnyToken, CombinedTokType, type Token, TokType } from './lexer-types.js';\n\n/**\n * Combine numeric timezone offsets into single tokens for easier parsing.\n * This is an optional post-processing step for the lexer output.\n *\n * Combines patterns like:\n * - (+|-) Number Colon Number => +08:00, -05:30\n * - (+|-) Number (2 or 4 digits) => +09, -0530\n *\n * Only combines when context suggests a timezone offset (after time components),\n * NOT after:\n * - Z (mutually exclusive per ISO 8601)\n * - brackets (offset comes BEFORE brackets per RFC 9557/IXDTF)\n * - standalone dates (e.g., 2025-01 where - is a date separator)\n *\n * @param tokens - The raw token array from lexTemporal\n * @returns Array of tokens (either original Token or combined CombinedToken with TZOffset type where applicable)\n *\n * @example\n * ```typescript\n * const raw = lexTemporal('10:00:00+08:00');\n * const combined = combineTimezoneOffsets(raw);\n * // Result includes: { type: 'TZOffset', value: '+08:00' }\n * ```\n */\nexport function combineTimezoneOffsets(tokens: Token[]): AnyToken[] {\n const out: AnyToken[] = [];\n let i = 0;\n\n const peek = (k: number) => tokens[i + k];\n\n // Helper to check if previous tokens suggest this could be a timezone offset\n // Timezone offsets come after time components (HH:MM:SS)\n // NOT after just a date like 2025-01\n // NOT after Z (Z and numeric offsets are mutually exclusive per ISO 8601)\n // NOT after brackets (per RFC 9557/IXDTF: offset comes BEFORE brackets)\n const couldBeTzOffset = () => {\n if (out.length < 1) {\n return false;\n }\n const prev: AnyToken | undefined = out[out.length - 1];\n if (prev == null) {\n return false;\n }\n\n // After Z - NOT valid (Z and numeric offsets are mutually exclusive)\n // Per ISO 8601: use either Z OR +HH:MM, never both\n if (prev.type === TokType.Z) {\n return false;\n }\n\n // After bracket - NOT valid (offset must come BEFORE brackets per RFC 9557)\n // Valid: Time+08:00[Asia/Tokyo]\n // Invalid: [Asia/Tokyo]+08:00\n if (prev.type === TokType.RBracket) {\n return false;\n }\n\n // After a number, check context\n if (prev.type === TokType.Number) {\n if (out.length < 2) {\n return false;\n }\n const prevPrev = out[out.length - 2];\n\n // After a number with colon before it (time component)\n // e.g., ...10:00:00+08:00 - the number before + has a colon before it\n if (prevPrev?.type === TokType.Colon) {\n return true;\n }\n\n // After a number with dot before it (milliseconds)\n // e.g., ...10:00:00.999+08:00 - the number before + has a dot before it\n if (prevPrev?.type === TokType.Dot) {\n return true;\n }\n }\n\n return false;\n };\n\n while (i < tokens.length) {\n const t = tokens[i]!;\n const isSign = t.type === TokType.Plus || t.type === TokType.Dash;\n\n // Only try to combine if context suggests timezone offset\n if (isSign && couldBeTzOffset()) {\n // (+|-) Number Colon Number => +08:00\n if (\n peek(1)?.type === TokType.Number &&\n peek(2)?.type === TokType.Colon &&\n peek(3)?.type === TokType.Number\n ) {\n const originalTokens = [t, peek(1)!, peek(2)!, peek(3)!];\n const start = t.start;\n const end = peek(3)!.end;\n const value = `${t.value}${peek(1)!.value}:${peek(3)!.value}`;\n out.push({ type: CombinedTokType.TZOffset, value, tokens: originalTokens, start, end });\n i += 4;\n continue;\n }\n\n // (+|-) Number where Number is 2 or 4 digits => +09 or -0530\n if (peek(1)?.type === TokType.Number) {\n const num = peek(1)!.value;\n if (num.length === 2 || num.length === 4) {\n const originalTokens = [t, peek(1)!];\n const start = t.start;\n const end = peek(1)!.end;\n const value = `${t.value}${num}`;\n out.push({ type: CombinedTokType.TZOffset, value, tokens: originalTokens, start, end });\n i += 2;\n continue;\n }\n }\n }\n\n out.push(t);\n i++;\n }\n\n return out;\n}\n", "// errors.ts\n// Error classes for lexer and parser\n\nexport class LexError extends Error {\n public readonly position: number;\n constructor(message: string, position: number) {\n super(`${message} at position ${position}`);\n this.name = 'LexError';\n this.position = position;\n }\n}\n\nexport class ParseError extends Error {\n public readonly position: number;\n constructor(message: string, position: number) {\n super(`${message} at token index ${position}`);\n this.name = 'ParseError';\n this.position = position;\n }\n}\n", "/**\n * Check if character is alphabetic (A-Z or a-z)\n * This defines what can be part of an IDENT token in default mode\n * @param c - the character to check\n * @returns true if character is alphabetic, false otherwise\n */\nexport function isAlpha(c: string): boolean {\n return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');\n}\n", "/**\n * Check if character is a digit (0-9)\n * @param c - the character to check\n * @returns true if character is a digit, false otherwise\n */\nexport function isDigit(c: string) {\n return c >= '0' && c <= '9';\n}\n", "import { isAlpha } from './isAlpha.js';\nimport { isDigit } from './isDigit.js';\n\n/**\n * Check if character is valid inside bracket annotations\n * In default mode, IDENT is letters only (keeps date/time parsing clean).\n * In bracket mode, we allow broader \"words\" like:\n * - u-ca (has dash)\n * - Asia/Singapore (has slash)\n * - Etc/GMT+8 (has plus)\n * - iso8601 (has digits)\n * @param c - the character to check\n * @returns true if character is valid in bracket context, false otherwise\n */\nexport function isBracketWordChar(c: string): boolean {\n return (\n isAlpha(c) || isDigit(c) || c === '-' || c === '/' || c === '+' || c === '.' // allow dots in some TZ-ish values\n );\n}\n", "// lexer.ts\n// Temporal (IXDTF / ISO-8601-ish) lexer with support for *extensions/annotations* like calendar.\n//\n// Supports:\n// - Basic date/time tokens: numbers, identifiers, T, Z, punctuation\n// - Ranges: '/'\n// - Durations: 'P' (as IDENT; parser enforces rules)\n// - Time zone forms:\n// - 'Z'\n// - numeric offsets: +08:00 / -0530 / +09 (optionally combined via helper)\n// - bracketed IANA TZ: [Asia/Singapore]\n// - IXDTF-style annotations/extensions in brackets, e.g.:\n// - [u-ca=gregory] (calendar)\n// - [u-tz=Asia/Singapore] (timeZone)\n// - multiple bracket groups: 2025-01-01T00:00Z[u-ca=iso8601][u-tz=UTC]\n//\n// The lexer is permissive; your parser should validate semantics.\n\nimport { LexError } from './errors.js';\nimport { isAlpha } from './helpers/isAlpha.js';\nimport { isBracketWordChar } from './helpers/isBracketWordChar.js';\nimport { isDigit } from './helpers/isDigit.js';\nimport type { Token } from './lexer-types.js';\nimport { TokType } from './lexer-types.js';\n\nexport function lexTemporal(src: string): Token[] {\n const tokens: Token[] = [];\n let i = 0;\n\n const emit = (type: TokType, start: number, end: number, value?: string) => {\n tokens.push({ type, value: value ?? src.slice(start, end), start, end });\n };\n\n // Lex a number: consume consecutive digits\n // Examples: \"2025\", \"01\", \"59\"\n const lexNumber = () => {\n const start = i;\n i++;\n while (i < src.length && isDigit(src[i]!)) {\n i++;\n }\n emit(TokType.Number, start, i);\n };\n\n // Lex an identifier: consume consecutive alphabetic characters\n // This is where IDENT tokens are created!\n //\n // Examples of what becomes IDENT:\n // - Duration designators: P, Y, M, W, D, H, S\n // - Calendar names: gregory, hebrew, iso8601\n // - Any letter sequence: P, YMD, UTC, America\n //\n // Special cases:\n // - \"T\" gets its own token type (datetime separator)\n // - \"Z\" gets its own token type (UTC marker)\n // - Everything else becomes Ident\n //\n // IMPORTANT: We only consume a single letter at a time to allow T and Z\n // to be recognized individually (e.g., \"DT\" should be \"D\" then \"T\", not \"DT\")\n const lexIdent = () => {\n const start = i;\n const ch = src[i]!;\n i++;\n\n // Special handling for T and Z - emit them as special tokens\n if (ch === 'T') {\n emit(TokType.T, start, i, 'T');\n return;\n }\n if (ch === 'Z') {\n emit(TokType.Z, start, i, 'Z');\n return;\n }\n\n // For other letters, consume consecutive letters that are NOT T or Z\n // This allows multi-letter identifiers like \"gregory\" but keeps T and Z separate\n while (i < src.length && isAlpha(src[i]!) && src[i] !== 'T' && src[i] !== 'Z') {\n i++;\n }\n\n emit(TokType.Ident, start, i); // Generic identifier\n };\n\n const lexBracketContent = () => {\n // We are positioned after '['. Emit content tokens until ']'.\n while (i < src.length) {\n const ch = src[i]!;\n\n if (ch === ']') {\n return;\n }\n\n // Skip whitespace inside brackets (rare but harmless)\n if (/\\s/.test(ch)) {\n i++;\n continue;\n }\n\n // Single-char tokens inside brackets\n if (ch === '=') {\n emit(TokType.Equals, i, i + 1);\n i++;\n continue;\n }\n if (ch === '_') {\n emit(TokType.Underscore, i, i + 1);\n i++;\n continue;\n }\n if (ch === ':') {\n emit(TokType.Colon, i, i + 1);\n i++;\n continue;\n }\n if (ch === '-') {\n emit(TokType.Dash, i, i + 1);\n i++;\n continue;\n }\n if (ch === '+') {\n emit(TokType.Plus, i, i + 1);\n i++;\n continue;\n }\n if (ch === '/') {\n emit(TokType.Slash, i, i + 1);\n i++;\n continue;\n }\n if (ch === '.') {\n emit(TokType.Dot, i, i + 1);\n i++;\n continue;\n }\n if (ch === ',') {\n emit(TokType.Comma, i, i + 1);\n i++;\n continue;\n }\n if (ch === '!') {\n emit(TokType.Exclamation, i, i + 1);\n i++;\n continue;\n }\n\n // Words inside brackets (u-ca, gregory, Asia/Singapore, iso8601, Etc/GMT+8, etc.)\n if (isBracketWordChar(ch)) {\n const start = i;\n i++;\n while (i < src.length && isBracketWordChar(src[i]!)) {\n i++;\n }\n const text = src.slice(start, i);\n\n // Decision: Ident vs BracketText\n // - If purely letters (A-Z, a-z): emit as Ident\n // Examples: \"gregory\", \"hebrew\", \"u\", \"ca\"\n // - If contains other chars (digits, -, /, +, .): emit as BracketText\n // Examples: \"u-ca\", \"Asia/Singapore\", \"iso8601\", \"Etc/GMT+8\"\n if (/^[A-Za-z]+$/.test(text)) {\n emit(TokType.Ident, start, i, text);\n } else {\n emit(TokType.BracketText, start, i, text);\n }\n\n continue;\n }\n\n throw new LexError(`Unexpected character ${JSON.stringify(ch)} inside brackets`, i);\n }\n\n throw new LexError(\"Unterminated bracket annotation, expected ']'\", src.length);\n };\n\n while (i < src.length) {\n const ch = src[i]!;\n\n // Skip whitespace\n if (/\\s/.test(ch)) {\n i++;\n continue;\n }\n\n // Bracketed extensions/annotations/timezone IDs\n if (ch === '[') {\n emit(TokType.LBracket, i, i + 1);\n i++;\n\n lexBracketContent();\n\n if (i >= src.length || src[i] !== ']') {\n throw new LexError(\"Unterminated bracket annotation, expected ']'\", i);\n }\n emit(TokType.RBracket, i, i + 1);\n i++;\n continue;\n }\n\n // Numbers: sequences of digits\n if (isDigit(ch)) {\n lexNumber();\n continue;\n }\n\n // Letters: this is where IDENT tokens are created!\n // Any alphabetic character triggers identifier lexing.\n // Examples: P (duration), Y (year), M (month), gregory, UTC\n if (isAlpha(ch)) {\n lexIdent();\n continue;\n }\n\n // Punctuation (default mode)\n switch (ch) {\n case '-':\n emit(TokType.Dash, i, i + 1);\n i++;\n continue;\n case ':':\n emit(TokType.Colon, i, i + 1);\n i++;\n continue;\n case '.':\n emit(TokType.Dot, i, i + 1);\n i++;\n continue;\n case '+':\n emit(TokType.Plus, i, i + 1);\n i++;\n continue;\n case '/':\n emit(TokType.Slash, i, i + 1);\n i++;\n continue;\n case ',':\n emit(TokType.Comma, i, i + 1);\n i++;\n continue;\n case '=':\n emit(TokType.Equals, i, i + 1);\n i++;\n continue;\n case '_':\n emit(TokType.Underscore, i, i + 1);\n i++;\n continue;\n default:\n throw new LexError(`Unexpected character ${JSON.stringify(ch)}`, i);\n }\n }\n\n emit(TokType.EOF, src.length, src.length, '');\n return tokens;\n}\n", "import { ParseError } from '../errors.js';\n\n/**\n * Convert a string to an integer, throwing a ParseError if invalid\n * @param s - the string to convert\n * @param label - a descriptive label for error messages (e.g., \"year\", \"month\")\n * @param position - the position in the token stream for error reporting\n * @returns the parsed integer value\n * @throws {ParseError} if the string is not a valid integer\n */\nexport function toInt(s: string, label: string, position: number): number {\n const n = Number(s);\n if (!Number.isFinite(n) || !Number.isInteger(n)) {\n throw new ParseError(`Invalid integer for ${label}: ${JSON.stringify(s)}`, position);\n }\n return n;\n}\n", "// parseOffset.ts\n// Parse timezone offset strings into structured components\n\nimport { ParseError } from './errors.js';\nimport { toInt } from './helpers/toInt.js';\nimport type { OffsetAst } from './parser-types.js';\n\n// Timezone offset validation constants\n// Per ISO 8601 and real-world usage:\n// - Westernmost: UTC-12:00 (Baker Island, Howland Island)\n// - Easternmost: UTC+14:00 (Line Islands, Kiritimati)\nconst MAX_OFFSET_HOURS = 14;\nconst MAX_MINUTES = 59;\n\n/**\n * Parse a timezone offset string into structured components.\n *\n * Supports three formats:\n * - Standard: +08:00, -05:30\n * - Compact: +0530, -0800\n * - Short: +09, -05\n *\n * Valid timezone offset ranges (per ISO 8601):\n * - Hours: 0-14 (UTC-12:00 to UTC+14:00)\n * - Minutes: 0-59\n *\n * Note: This function only parses numeric offsets (e.g., +08:00).\n * For UTC, use 'Z' (UtcOffset kind) which is semantically different from +00:00.\n * Per RFC 3339 section 4.3, -00:00 indicates \"local time with unknown offset\".\n *\n * @param offsetString - The offset string to parse (e.g., '+08:00', '-0530', '+09')\n * @param position - Position in token stream for error reporting (optional)\n * @returns Parsed offset with sign, hours, and minutes\n * @throws {ParseError} If the offset format is invalid or out of range\n *\n * @example\n * ```typescript\n * parseOffset('+08:00') // { kind: 'NumericOffset', sign: '+', hours: 8, minutes: 0, raw: '+08:00' }\n * parseOffset('-05:30') // { kind: 'NumericOffset', sign: '-', hours: 5, minutes: 30, raw: '-05:30' }\n * parseOffset('+0530') // { kind: 'NumericOffset', sign: '+', hours: 5, minutes: 30, raw: '+0530' }\n * parseOffset('+09') // { kind: 'NumericOffset', sign: '+', hours: 9, minutes: 0, raw: '+09' }\n * parseOffset('+14:00') // { kind: 'NumericOffset', sign: '+', hours: 14, minutes: 0, raw: '+14:00' }\n * ```\n */\nexport function parseOffset(offsetString: string, position = 0): OffsetAst {\n // Validate input is not empty\n if (offsetString.length === 0) {\n throw new ParseError('Offset string cannot be empty', position);\n }\n\n const raw = offsetString;\n\n // Validate first character is sign and assert type\n const sign = raw[0];\n if (sign !== '+' && sign !== '-') {\n throw new ParseError(`Offset must start with + or -, got ${JSON.stringify(sign)}`, position);\n }\n // Type assertion: TypeScript now knows sign is '+' | '-'\n const validSign: '+' | '-' = sign;\n\n const rest = raw.slice(1);\n\n // Validate rest is not empty\n if (rest.length === 0) {\n throw new ParseError(`Offset sign must be followed by digits`, position);\n }\n\n let hours: number;\n let minutes: number;\n\n if (rest.includes(':')) {\n // Format: +08:00 or -05:30\n const parts = rest.split(':');\n if (parts.length !== 2) {\n throw new ParseError(`Invalid offset format: ${raw} (expected HH:MM)`, position);\n }\n // After length check, we know parts[0] and parts[1] exist\n const [h, m] = parts;\n hours = toInt(h!, 'offset hours', position);\n minutes = toInt(m!, 'offset minutes', position);\n } else if (rest.length === 4) {\n // Format: +0530 or -0800\n hours = toInt(rest.slice(0, 2), 'offset hours', position);\n minutes = toInt(rest.slice(2, 4), 'offset minutes', position);\n } else if (rest.length === 2) {\n // Format: +08 or -05\n hours = toInt(rest, 'offset hours', position);\n minutes = 0;\n } else {\n throw new ParseError(\n `Invalid offset format: ${raw} (expected +HH:MM, +HHMM, or +HH)`,\n position,\n );\n }\n\n // Validate ranges\n if (hours < 0 || hours > MAX_OFFSET_HOURS) {\n throw new ParseError(`Offset hours must be 0-${MAX_OFFSET_HOURS}, got ${hours}`, position);\n }\n if (minutes < 0 || minutes > MAX_MINUTES) {\n throw new ParseError(`Offset minutes must be 0-${MAX_MINUTES}, got ${minutes}`, position);\n }\n\n return { kind: 'NumericOffset', sign: validSign, hours, minutes, raw };\n}\n", "// parser.ts\n// A practical recursive-descent parser that works with the lexer we wrote.\n// It builds an AST for a useful Temporal subset:\n// - date/time (with optional fractional seconds)\n// - timezone: Z | numeric offset (+08:00, -0530, +09) | bracket TZ id [Asia/Singapore]\n// - duration: P... with optional T part\n// - range: <expr> \"/\" <expr> where each side can be dateTime/date/duration, and either side may be empty (open range)\n// - bracket extensions like [u-ca=gregory], [u-tz=Asia/Singapore], etc. (stored as key/value pairs)\n\nimport { combineTimezoneOffsets } from './combineTimezoneOffsets.js';\nimport { ParseError } from './errors.js';\nimport { toInt } from './helpers/toInt.js';\nimport { lexTemporal } from './lexer.js';\nimport { type AnyToken, CombinedTokType, TokType } from './lexer-types.js';\nimport { parseOffset } from './parseOffset.js';\nimport type {\n AnnotationAst,\n DateAst,\n DateTimeAst,\n DurationAst,\n OffsetAst,\n TemporalAst,\n TimeAst,\n TimeZoneAst,\n ValueAst,\n} from './parser-types.js';\n\n// ---------- Parser ----------\n\nclass Parser {\n private i = 0;\n constructor(private toks: AnyToken[]) {}\n\n private peek(k = 0): AnyToken {\n return this.toks[Math.min(this.i + k, this.toks.length - 1)]!;\n }\n private at(type: TokType | CombinedTokType): boolean {\n return this.peek().type === type;\n }\n private eat(type: TokType | CombinedTokType): AnyToken {\n const t = this.peek();\n if (t.type !== type) {\n throw new ParseError(`Expected ${type} but got ${t.type}`, this.i);\n }\n this.i++;\n return t;\n }\n private tryEat(type: TokType | CombinedTokType): AnyToken | null {\n if (this.at(type)) {\n return this.eat(type);\n }\n return null;\n }\n private eof(): boolean {\n return this.at(TokType.EOF);\n }\n\n // Top-level: parse a range if we see '/', otherwise a single value.\n parseTemporal(): TemporalAst {\n // Open-start range: \"/...\"\n if (this.tryEat(TokType.Slash)) {\n const end = this.parseValueOrNull(); // allow empty end too\n this.eat(TokType.EOF);\n return { kind: 'Range', start: null, end };\n }\n\n const first = this.parseValueOrNull();\n // If first is null, only valid if it's \"/\" (handled above). So error:\n if (!first) {\n throw new ParseError('Expected a value', this.i);\n }\n\n if (this.tryEat(TokType.Slash)) {\n const second = this.parseValueOrNull(); // open-ended allowed\n this.eat(TokType.EOF);\n return { kind: 'Range', start: first, end: second };\n }\n\n this.eat(TokType.EOF);\n return first;\n }\n\n private parseValueOrNull(): ValueAst | null {\n // Empty side for open range:\n if (this.at(TokType.EOF) || this.at(TokType.Slash) || this.at(TokType.RBracket)) {\n return null;\n }\n\n // Duration starts with IDENT \"P\" (as produced by lexer)\n if (this.at(TokType.Ident) && String(this.peek().value) === 'P') {\n return this.parseDuration();\n }\n\n // Otherwise parse a date/datetime (common case)\n return this.parseDateTime();\n }\n\n // -------- DateTime parsing --------\n // Supports:\n // - YYYY\n // - YYYY-MM\n // - YYYY-MM-DD\n // - + time part with T: YYYY-MM-DDTHH:MM[:SS[.fff]]\n // - offset: Z | +08:00 | -0530\n // - timezone: [Asia/Singapore] (first bracketed IANA timezone)\n // - annotations: [u-ca=...][u-tz=...] (other bracketed content)\n private parseDateTime(): DateTimeAst {\n const date = this.parseDate();\n\n let time: DateTimeAst['time'] | undefined;\n\n // time separator is TokType.T (from lexer special-casing \"T\")\n if (this.tryEat(TokType.T)) {\n time = this.parseTime();\n }\n\n // Parse numeric offset (Z or +HH:MM)\n const offset = this.parseOffsetIfPresent();\n\n // Parse bracket groups: first IANA timezone becomes timeZone, rest are annotations\n const annotations: AnnotationAst[] = [];\n let timeZone: TimeZoneAst | undefined;\n\n while (this.at(TokType.LBracket)) {\n const { raw, critical, isLikelyTzId, pairs } = this.parseBracketGroup();\n\n // First timezone-like bracket becomes the timeZone field\n // Note: Even though IXDTF doesn't typically use critical flags for timezones,\n // we preserve it in case the input has it\n if (timeZone === undefined && isLikelyTzId) {\n // Strip leading ! from id (it's stored in the critical flag)\n const id = critical ? raw.slice(1) : raw;\n timeZone = { kind: 'IanaTimeZone', id, critical };\n } else {\n annotations.push({ kind: 'Annotation', raw, critical, pairs });\n }\n }\n\n return { kind: 'DateTime', date, time, offset, timeZone, annotations };\n }\n\n private parseDate(): DateAst {\n const yTok = this.eat(TokType.Number);\n const year = toInt(yTok.value, 'year', this.i);\n\n // Optional -MM\n if (!this.tryEat(TokType.Dash)) {\n return { kind: 'Date', year };\n }\n const mTok = this.eat(TokType.Number);\n const month = toInt(mTok.value, 'month', this.i);\n\n // Optional -DD\n if (!this.tryEat(TokType.Dash)) {\n return { kind: 'Date', year, month };\n }\n const dTok = this.eat(TokType.Number);\n const day = toInt(dTok.value, 'day', this.i);\n\n return { kind: 'Date', year, month, day };\n }\n\n private parseTime(): TimeAst {\n const hTok = this.eat(TokType.Number);\n const hour = toInt(hTok.value, 'hour', this.i);\n\n this.eat(TokType.Colon);\n const minTok = this.eat(TokType.Number);\n const minute = toInt(minTok.value, 'minute', this.i);\n\n let second: number | undefined;\n let fraction: string | undefined;\n\n if (this.tryEat(TokType.Colon)) {\n const sTok = this.eat(TokType.Number);\n second = toInt(sTok.value, 'second', this.i);\n\n if (this.tryEat(TokType.Dot)) {\n const fracTok = this.eat(TokType.Number);\n fraction = fracTok.value; // keep raw digits\n }\n }\n\n return { kind: 'Time', hour, minute, second, fraction };\n }\n\n /**\n * Parse timezone offset: Z | +HH:MM | -HH:MM\n *\n * Important semantic distinction per RFC 3339 and ISO 8601:\n *\n * - Z (Zulu time):\n * Explicitly indicates UTC. Used when the time is known to be in UTC.\n * Has no sign ambiguity - it's always UTC, never local time.\n *\n * - +00:00 (zero positive offset):\n * Indicates local time at the prime meridian (UTC+0 timezone).\n * Semantically different from Z - this is a local time that happens to match UTC.\n *\n * - -00:00 (zero negative offset):\n * Per RFC 3339 section 4.3: indicates local time with unknown UTC offset.\n * Used when the time is known to be local but the offset is not known.\n * This is semantically very different from both Z and +00:00.\n * https://www.rfc-editor.org/rfc/rfc3339.html#section-4.3\n *\n * We preserve this distinction by treating Z as UtcOffset (no sign, no components)\n * while +00:00 and -00:00 are NumericOffset (with sign and parsed components).\n */\n private parseOffsetIfPresent(): OffsetAst | undefined {\n if (this.tryEat(TokType.Z)) {\n return { kind: 'UtcOffset' };\n }\n\n // combined numeric offset token if you used combineTimezoneOffsets()\n if (this.at(CombinedTokType.TZOffset)) {\n const t = this.eat(CombinedTokType.TZOffset);\n return parseOffset(t.value, this.i);\n }\n\n return undefined;\n }\n\n // -------- Bracket group parsing --------\n // Consumes: [ ... ]\n // Returns:\n // - raw inner text (reconstructed)\n // - critical flag (true if starts with !)\n // - pairs if it looks like key=value (or flags)\n // - isLikelyTzId for things like [Asia/Singapore]\n private parseBracketGroup(): {\n raw: string;\n critical: boolean;\n pairs: Record<string, string | true>;\n isLikelyTzId: boolean;\n } {\n this.eat(TokType.LBracket);\n\n const parts: string[] = [];\n const pairs: Record<string, string | true> = {};\n\n // Check for critical flag (IXDTF: [!u-ca=gregory])\n const critical = this.tryEat(TokType.Exclamation) != null;\n if (critical) {\n parts.push('!');\n }\n\n // Gather tokens until RBracket\n while (!this.at(TokType.RBracket)) {\n if (this.eof()) {\n throw new ParseError('Unterminated bracket group', this.i);\n }\n\n const t = this.peek();\n this.i++;\n\n parts.push(String(t.value));\n }\n\n this.eat(TokType.RBracket);\n\n const raw = parts.join('');\n\n // Best-effort parse into key/value pairs:\n // Support \"key=value\" and \"flag\" and \"k=v,k2=v2\"\n // Strip leading ! if present for parsing\n const contentToParse = critical ? raw.slice(1) : raw;\n const segments = contentToParse\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean);\n for (const seg of segments) {\n const eq = seg.indexOf('=');\n if (eq === -1) {\n pairs[seg] = true;\n } else {\n const k = seg.slice(0, eq).trim();\n const v = seg.slice(eq + 1).trim();\n if (k) {\n pairs[k] = v || true;\n }\n }\n }\n\n // Heuristic: likely TZ ID if no '=' present and contains '/' (e.g. Asia/Singapore) or starts with Etc/\n // We now preserve critical flag even for timezones, so don't exclude them\n const isLikelyTzId =\n contentToParse.indexOf('=') === -1 &&\n (contentToParse.includes('/') || contentToParse.startsWith('Etc/'));\n\n // Note: If you want STRICTLY separate tz-id brackets vs extension brackets, do it here.\n\n return { raw, critical, pairs, isLikelyTzId };\n }\n\n // -------- Duration parsing --------\n // Grammar (subset):\n // P (Y M W D)* (T (H M S)*)?\n // Examples:\n // P1Y2M\n // P3W\n // P10DT2H30M\n // PT1.5S\n private parseDuration(): DurationAst {\n // Collect raw string as we parse\n const rawParts: string[] = [];\n const annotations: AnnotationAst[] = [];\n\n const pTok = this.eat(TokType.Ident);\n if (String(pTok.value) !== 'P') {\n throw new ParseError(\"Duration must start with 'P'\", this.i);\n }\n rawParts.push('P');\n\n let inTime = false;\n\n const dur: Omit<DurationAst, 'kind' | 'raw' | 'annotations'> = {};\n\n // Parse designators until we hit bracket annotations, slash, or EOF\n while (!this.eof() && !this.at(TokType.Slash) && !this.at(TokType.LBracket)) {\n // Time marker T\n if (this.tryEat(TokType.T)) {\n rawParts.push('T');\n inTime = true;\n continue;\n }\n\n // Expect a number (possibly with fraction for seconds)\n const numTok = this.tryEat(TokType.Number);\n if (!numTok) {\n break;\n } // be permissive; let parser user decide if this is error-worthy\n rawParts.push(numTok.value);\n\n let fraction: string | undefined;\n if (this.tryEat(TokType.Dot)) {\n rawParts.push('.');\n const fracTok = this.eat(TokType.Number);\n rawParts.push(fracTok.value);\n fraction = fracTok.value;\n }\n\n // Unit is IDENT (Y,M,W,D,H,M,S). Note M is months if !inTime else minutes.\n const unitTok = this.eat(TokType.Ident);\n const unit = String(unitTok.value);\n rawParts.push(unit);\n\n const n = toInt(numTok.value, 'duration number', this.i);\n\n if (!inTime) {\n if (unit === 'Y') {\n dur.years = (dur.years ?? 0) + n;\n } else if (unit === 'M') {\n dur.months = (dur.months ?? 0) + n;\n } else if (unit === 'W') {\n dur.weeks = (dur.weeks ?? 0) + n;\n } else if (unit === 'D') {\n dur.days = (dur.days ?? 0) + n;\n } else {\n throw new ParseError(`Invalid duration unit '${unit}' in date part`, this.i);\n }\n } else {\n if (unit === 'H') {\n dur.hours = (dur.hours ?? 0) + n;\n } else if (unit === 'M') {\n dur.minutes = (dur.minutes ?? 0) + n;\n } else if (unit === 'S') {\n dur.seconds = (dur.seconds ?? 0) + n;\n if (fraction !== undefined && fraction.length > 0) {\n dur.secondsFraction = fraction;\n }\n } else {\n throw new ParseError(`Invalid duration unit '${unit}' in time part`, this.i);\n }\n }\n }\n\n // bracket annotations after duration\n while (this.at(TokType.LBracket)) {\n const { raw, critical, pairs } = this.parseBracketGroup();\n annotations.push({ kind: 'Annotation', raw, critical, pairs });\n }\n\n return { kind: 'Duration', ...dur, raw: rawParts.join(''), annotations };\n }\n}\n\n// ---------- Public API ----------\nexport function parseTemporal(src: string): TemporalAst {\n const raw = lexTemporal(src);\n const toks = combineTimezoneOffsets(raw);\n const p = new Parser(toks);\n return p.parseTemporal();\n}\n"], | ||
| "mappings": "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", | ||
| "sources": ["../../src/lexer-types.ts", "../../src/combineTimezoneOffsets.ts", "../../src/errors.ts", "../../src/helpers/isAlpha.ts", "../../src/helpers/isDigit.ts", "../../src/helpers/isBracketWordChar.ts", "../../src/lexer.ts", "../../src/helpers/toInt.ts", "../../src/parseOffset.ts", "../../src/parser.ts", "../../src/stringify.ts"], | ||
| "sourcesContent": ["// lexer-types.ts\n// Token types and interfaces for the lexer\n\nexport enum TokType {\n // Numbers: sequences of digits (0-9)\n // Examples: \"2025\", \"01\", \"12\", \"59\"\n Number = 'Number',\n\n // Ident (Identifier): sequences of alphabetic characters (A-Z, a-z)\n // This is the generic \"text\" token for letter-based designators.\n // Examples: \"P\" (duration), \"Y\" (year), \"M\" (month), \"gregory\" (calendar)\n // The lexer keeps it simple; the parser interprets the meaning.\n Ident = 'Ident',\n\n // Single-character punctuation tokens\n Dash = 'Dash', // -\n Colon = 'Colon', // :\n Dot = 'Dot', // .\n Plus = 'Plus', // +\n Slash = 'Slash', // /\n Comma = 'Comma', // ,\n Equals = 'Equals', // =\n Underscore = 'Underscore', // _\n Exclamation = 'Exclamation', // ! (critical flag in IXDTF)\n\n // Special letter tokens with dedicated meaning in ISO 8601\n T = 'T', // Date/time separator: 2025-01-01T10:00:00\n Z = 'Z', // UTC timezone marker: 2025-01-01T10:00:00Z\n\n // Bracket tokens for IXDTF annotations/extensions\n LBracket = 'LBracket', // [\n RBracket = 'RBracket', // ]\n\n // BracketText: complex text inside brackets that contains non-letter chars\n // Example: in [u-ca=gregory], \"u-ca\" is BracketText (has dash), \"gregory\" is Ident (letters only)\n // Example: in [Asia/Singapore], \"Asia/Singapore\" is BracketText (has slash)\n BracketText = 'BracketText',\n\n EOF = 'EOF',\n}\n\nexport type Token = {\n type: TokType;\n value: string;\n start: number; // inclusive\n end: number; // exclusive\n};\n\n// Combined token types for timezone offsets\nexport enum CombinedTokType {\n TZOffset = 'TZOffset', // +08:00, -0530, +09\n}\n\nexport type CombinedToken = {\n type: CombinedTokType;\n value: string;\n tokens: Token[];\n start: number;\n end: number;\n};\n\nexport type AnyToken = Token | CombinedToken;\n", "// combineTimezoneOffsets.ts\n// Combines numeric timezone offset tokens into single tokens for easier parsing\n\nimport { type AnyToken, CombinedTokType, type Token, TokType } from './lexer-types.js';\n\n/**\n * Combine numeric timezone offsets into single tokens for easier parsing.\n * This is an optional post-processing step for the lexer output.\n *\n * Combines patterns like:\n * - (+|-) Number Colon Number => +08:00, -05:30\n * - (+|-) Number (2 or 4 digits) => +09, -0530\n *\n * Only combines when context suggests a timezone offset (after time components),\n * NOT after:\n * - Z (mutually exclusive per ISO 8601)\n * - brackets (offset comes BEFORE brackets per RFC 9557/IXDTF)\n * - standalone dates (e.g., 2025-01 where - is a date separator)\n *\n * @param tokens - The raw token array from lexTemporal\n * @returns Array of tokens (either original Token or combined CombinedToken with TZOffset type where applicable)\n *\n * @example\n * ```typescript\n * const raw = lexTemporal('10:00:00+08:00');\n * const combined = combineTimezoneOffsets(raw);\n * // Result includes: { type: 'TZOffset', value: '+08:00' }\n * ```\n */\nexport function combineTimezoneOffsets(tokens: Token[]): AnyToken[] {\n const out: AnyToken[] = [];\n let i = 0;\n\n const peek = (k: number) => tokens[i + k];\n\n // Helper to check if previous tokens suggest this could be a timezone offset\n // Timezone offsets come after time components (HH:MM:SS)\n // NOT after just a date like 2025-01\n // NOT after Z (Z and numeric offsets are mutually exclusive per ISO 8601)\n // NOT after brackets (per RFC 9557/IXDTF: offset comes BEFORE brackets)\n const couldBeTzOffset = () => {\n if (out.length < 1) {\n return false;\n }\n const prev: AnyToken | undefined = out[out.length - 1];\n if (prev == null) {\n return false;\n }\n\n // After Z - NOT valid (Z and numeric offsets are mutually exclusive)\n // Per ISO 8601: use either Z OR +HH:MM, never both\n if (prev.type === TokType.Z) {\n return false;\n }\n\n // After bracket - NOT valid (offset must come BEFORE brackets per RFC 9557)\n // Valid: Time+08:00[Asia/Tokyo]\n // Invalid: [Asia/Tokyo]+08:00\n if (prev.type === TokType.RBracket) {\n return false;\n }\n\n // After a number, check context\n if (prev.type === TokType.Number) {\n if (out.length < 2) {\n return false;\n }\n const prevPrev = out[out.length - 2];\n\n // After a number with colon before it (time component)\n // e.g., ...10:00:00+08:00 - the number before + has a colon before it\n if (prevPrev?.type === TokType.Colon) {\n return true;\n }\n\n // After a number with dot before it (milliseconds)\n // e.g., ...10:00:00.999+08:00 - the number before + has a dot before it\n if (prevPrev?.type === TokType.Dot) {\n return true;\n }\n }\n\n return false;\n };\n\n while (i < tokens.length) {\n const t = tokens[i]!;\n const isSign = t.type === TokType.Plus || t.type === TokType.Dash;\n\n // Only try to combine if context suggests timezone offset\n if (isSign && couldBeTzOffset()) {\n // (+|-) Number Colon Number => +08:00\n if (\n peek(1)?.type === TokType.Number &&\n peek(2)?.type === TokType.Colon &&\n peek(3)?.type === TokType.Number\n ) {\n const originalTokens = [t, peek(1)!, peek(2)!, peek(3)!];\n const start = t.start;\n const end = peek(3)!.end;\n const value = `${t.value}${peek(1)!.value}:${peek(3)!.value}`;\n out.push({ type: CombinedTokType.TZOffset, value, tokens: originalTokens, start, end });\n i += 4;\n continue;\n }\n\n // (+|-) Number where Number is 2 or 4 digits => +09 or -0530\n if (peek(1)?.type === TokType.Number) {\n const num = peek(1)!.value;\n if (num.length === 2 || num.length === 4) {\n const originalTokens = [t, peek(1)!];\n const start = t.start;\n const end = peek(1)!.end;\n const value = `${t.value}${num}`;\n out.push({ type: CombinedTokType.TZOffset, value, tokens: originalTokens, start, end });\n i += 2;\n continue;\n }\n }\n }\n\n out.push(t);\n i++;\n }\n\n return out;\n}\n", "// errors.ts\n// Error classes for lexer and parser\n\nexport class LexError extends Error {\n public readonly position: number;\n constructor(message: string, position: number) {\n super(`${message} at position ${position}`);\n this.name = 'LexError';\n this.position = position;\n }\n}\n\nexport class ParseError extends Error {\n public readonly position: number;\n constructor(message: string, position: number) {\n super(`${message} at token index ${position}`);\n this.name = 'ParseError';\n this.position = position;\n }\n}\n", "/**\n * Check if character is alphabetic (A-Z or a-z)\n * This defines what can be part of an IDENT token in default mode\n * @param c - the character to check\n * @returns true if character is alphabetic, false otherwise\n */\nexport function isAlpha(c: string): boolean {\n return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');\n}\n", "/**\n * Check if character is a digit (0-9)\n * @param c - the character to check\n * @returns true if character is a digit, false otherwise\n */\nexport function isDigit(c: string) {\n return c >= '0' && c <= '9';\n}\n", "import { isAlpha } from './isAlpha.js';\nimport { isDigit } from './isDigit.js';\n\n/**\n * Check if character is valid inside bracket annotations\n * In default mode, IDENT is letters only (keeps date/time parsing clean).\n * In bracket mode, we allow broader \"words\" like:\n * - u-ca (has dash)\n * - Asia/Singapore (has slash)\n * - Etc/GMT+8 (has plus)\n * - iso8601 (has digits)\n * @param c - the character to check\n * @returns true if character is valid in bracket context, false otherwise\n */\nexport function isBracketWordChar(c: string): boolean {\n return (\n isAlpha(c) || isDigit(c) || c === '-' || c === '/' || c === '+' || c === '.' // allow dots in some TZ-ish values\n );\n}\n", "// lexer.ts\n// Temporal (IXDTF / ISO-8601-ish) lexer with support for *extensions/annotations* like calendar.\n//\n// Supports:\n// - Basic date/time tokens: numbers, identifiers, T, Z, punctuation\n// - Ranges: '/'\n// - Durations: 'P' (as IDENT; parser enforces rules)\n// - Time zone forms:\n// - 'Z'\n// - numeric offsets: +08:00 / -0530 / +09 (optionally combined via helper)\n// - bracketed IANA TZ: [Asia/Singapore]\n// - IXDTF-style annotations/extensions in brackets, e.g.:\n// - [u-ca=gregory] (calendar)\n// - [u-tz=Asia/Singapore] (timeZone)\n// - multiple bracket groups: 2025-01-01T00:00Z[u-ca=iso8601][u-tz=UTC]\n//\n// The lexer is permissive; your parser should validate semantics.\n\nimport { LexError } from './errors.js';\nimport { isAlpha } from './helpers/isAlpha.js';\nimport { isBracketWordChar } from './helpers/isBracketWordChar.js';\nimport { isDigit } from './helpers/isDigit.js';\nimport type { Token } from './lexer-types.js';\nimport { TokType } from './lexer-types.js';\n\nexport function lexTemporal(src: string): Token[] {\n const tokens: Token[] = [];\n let i = 0;\n\n const emit = (type: TokType, start: number, end: number, value?: string) => {\n tokens.push({ type, value: value ?? src.slice(start, end), start, end });\n };\n\n // Lex a number: consume consecutive digits\n // Examples: \"2025\", \"01\", \"59\"\n const lexNumber = () => {\n const start = i;\n i++;\n while (i < src.length && isDigit(src[i]!)) {\n i++;\n }\n emit(TokType.Number, start, i);\n };\n\n // Lex an identifier: consume consecutive alphabetic characters\n // This is where IDENT tokens are created!\n //\n // Examples of what becomes IDENT:\n // - Duration designators: P, Y, M, W, D, H, S\n // - Calendar names: gregory, hebrew, iso8601\n // - Any letter sequence: P, YMD, UTC, America\n //\n // Special cases:\n // - \"T\" gets its own token type (datetime separator)\n // - \"Z\" gets its own token type (UTC marker)\n // - Everything else becomes Ident\n //\n // IMPORTANT: We only consume a single letter at a time to allow T and Z\n // to be recognized individually (e.g., \"DT\" should be \"D\" then \"T\", not \"DT\")\n const lexIdent = () => {\n const start = i;\n const ch = src[i]!;\n i++;\n\n // Special handling for T and Z - emit them as special tokens\n if (ch === 'T') {\n emit(TokType.T, start, i, 'T');\n return;\n }\n if (ch === 'Z') {\n emit(TokType.Z, start, i, 'Z');\n return;\n }\n\n // For other letters, consume consecutive letters that are NOT T or Z\n // This allows multi-letter identifiers like \"gregory\" but keeps T and Z separate\n while (i < src.length && isAlpha(src[i]!) && src[i] !== 'T' && src[i] !== 'Z') {\n i++;\n }\n\n emit(TokType.Ident, start, i); // Generic identifier\n };\n\n const lexBracketContent = () => {\n // We are positioned after '['. Emit content tokens until ']'.\n while (i < src.length) {\n const ch = src[i]!;\n\n if (ch === ']') {\n return;\n }\n\n // Skip whitespace inside brackets (rare but harmless)\n if (/\\s/.test(ch)) {\n i++;\n continue;\n }\n\n // Single-char tokens inside brackets\n if (ch === '=') {\n emit(TokType.Equals, i, i + 1);\n i++;\n continue;\n }\n if (ch === '_') {\n emit(TokType.Underscore, i, i + 1);\n i++;\n continue;\n }\n if (ch === ':') {\n emit(TokType.Colon, i, i + 1);\n i++;\n continue;\n }\n if (ch === '-') {\n emit(TokType.Dash, i, i + 1);\n i++;\n continue;\n }\n if (ch === '+') {\n emit(TokType.Plus, i, i + 1);\n i++;\n continue;\n }\n if (ch === '/') {\n emit(TokType.Slash, i, i + 1);\n i++;\n continue;\n }\n if (ch === '.') {\n emit(TokType.Dot, i, i + 1);\n i++;\n continue;\n }\n if (ch === ',') {\n emit(TokType.Comma, i, i + 1);\n i++;\n continue;\n }\n if (ch === '!') {\n emit(TokType.Exclamation, i, i + 1);\n i++;\n continue;\n }\n\n // Words inside brackets (u-ca, gregory, Asia/Singapore, iso8601, Etc/GMT+8, etc.)\n if (isBracketWordChar(ch)) {\n const start = i;\n i++;\n while (i < src.length && isBracketWordChar(src[i]!)) {\n i++;\n }\n const text = src.slice(start, i);\n\n // Decision: Ident vs BracketText\n // - If purely letters (A-Z, a-z): emit as Ident\n // Examples: \"gregory\", \"hebrew\", \"u\", \"ca\"\n // - If contains other chars (digits, -, /, +, .): emit as BracketText\n // Examples: \"u-ca\", \"Asia/Singapore\", \"iso8601\", \"Etc/GMT+8\"\n if (/^[A-Za-z]+$/.test(text)) {\n emit(TokType.Ident, start, i, text);\n } else {\n emit(TokType.BracketText, start, i, text);\n }\n\n continue;\n }\n\n throw new LexError(`Unexpected character ${JSON.stringify(ch)} inside brackets`, i);\n }\n\n throw new LexError(\"Unterminated bracket annotation, expected ']'\", src.length);\n };\n\n while (i < src.length) {\n const ch = src[i]!;\n\n // Skip whitespace\n if (/\\s/.test(ch)) {\n i++;\n continue;\n }\n\n // Bracketed extensions/annotations/timezone IDs\n if (ch === '[') {\n emit(TokType.LBracket, i, i + 1);\n i++;\n\n lexBracketContent();\n\n if (i >= src.length || src[i] !== ']') {\n throw new LexError(\"Unterminated bracket annotation, expected ']'\", i);\n }\n emit(TokType.RBracket, i, i + 1);\n i++;\n continue;\n }\n\n // Numbers: sequences of digits\n if (isDigit(ch)) {\n lexNumber();\n continue;\n }\n\n // Letters: this is where IDENT tokens are created!\n // Any alphabetic character triggers identifier lexing.\n // Examples: P (duration), Y (year), M (month), gregory, UTC\n if (isAlpha(ch)) {\n lexIdent();\n continue;\n }\n\n // Punctuation (default mode)\n switch (ch) {\n case '-':\n emit(TokType.Dash, i, i + 1);\n i++;\n continue;\n case ':':\n emit(TokType.Colon, i, i + 1);\n i++;\n continue;\n case '.':\n emit(TokType.Dot, i, i + 1);\n i++;\n continue;\n case '+':\n emit(TokType.Plus, i, i + 1);\n i++;\n continue;\n case '/':\n emit(TokType.Slash, i, i + 1);\n i++;\n continue;\n case ',':\n emit(TokType.Comma, i, i + 1);\n i++;\n continue;\n case '=':\n emit(TokType.Equals, i, i + 1);\n i++;\n continue;\n case '_':\n emit(TokType.Underscore, i, i + 1);\n i++;\n continue;\n default:\n throw new LexError(`Unexpected character ${JSON.stringify(ch)}`, i);\n }\n }\n\n emit(TokType.EOF, src.length, src.length, '');\n return tokens;\n}\n", "import { ParseError } from '../errors.js';\n\n/**\n * Convert a string to an integer, throwing a ParseError if invalid\n * @param s - the string to convert\n * @param label - a descriptive label for error messages (e.g., \"year\", \"month\")\n * @param position - the position in the token stream for error reporting\n * @returns the parsed integer value\n * @throws {ParseError} if the string is not a valid integer\n */\nexport function toInt(s: string, label: string, position: number): number {\n const n = Number(s);\n if (!Number.isFinite(n) || !Number.isInteger(n)) {\n throw new ParseError(`Invalid integer for ${label}: ${JSON.stringify(s)}`, position);\n }\n return n;\n}\n", "// parseOffset.ts\n// Parse timezone offset strings into structured components\n\nimport { ParseError } from './errors.js';\nimport { toInt } from './helpers/toInt.js';\nimport type { OffsetAst } from './parser-types.js';\n\n// Timezone offset validation constants\n// Per ISO 8601 and real-world usage:\n// - Westernmost: UTC-12:00 (Baker Island, Howland Island)\n// - Easternmost: UTC+14:00 (Line Islands, Kiritimati)\nconst MAX_OFFSET_HOURS = 14;\nconst MAX_MINUTES = 59;\n\n/**\n * Parse a timezone offset string into structured components.\n *\n * Supports three formats:\n * - Standard: +08:00, -05:30\n * - Compact: +0530, -0800\n * - Short: +09, -05\n *\n * Valid timezone offset ranges (per ISO 8601):\n * - Hours: 0-14 (UTC-12:00 to UTC+14:00)\n * - Minutes: 0-59\n *\n * Note: This function only parses numeric offsets (e.g., +08:00).\n * For UTC, use 'Z' (UtcOffset kind) which is semantically different from +00:00.\n * Per RFC 3339 section 4.3, -00:00 indicates \"local time with unknown offset\".\n *\n * @param offsetString - The offset string to parse (e.g., '+08:00', '-0530', '+09')\n * @param position - Position in token stream for error reporting (optional)\n * @returns Parsed offset with sign, hours, and minutes\n * @throws {ParseError} If the offset format is invalid or out of range\n *\n * @example\n * ```typescript\n * parseOffset('+08:00') // { kind: 'NumericOffset', sign: '+', hours: 8, minutes: 0, raw: '+08:00' }\n * parseOffset('-05:30') // { kind: 'NumericOffset', sign: '-', hours: 5, minutes: 30, raw: '-05:30' }\n * parseOffset('+0530') // { kind: 'NumericOffset', sign: '+', hours: 5, minutes: 30, raw: '+0530' }\n * parseOffset('+09') // { kind: 'NumericOffset', sign: '+', hours: 9, minutes: 0, raw: '+09' }\n * parseOffset('+14:00') // { kind: 'NumericOffset', sign: '+', hours: 14, minutes: 0, raw: '+14:00' }\n * ```\n */\nexport function parseOffset(offsetString: string, position = 0): OffsetAst {\n // Validate input is not empty\n if (offsetString.length === 0) {\n throw new ParseError('Offset string cannot be empty', position);\n }\n\n const raw = offsetString;\n\n // Validate first character is sign and assert type\n const sign = raw[0];\n if (sign !== '+' && sign !== '-') {\n throw new ParseError(`Offset must start with + or -, got ${JSON.stringify(sign)}`, position);\n }\n // Type assertion: TypeScript now knows sign is '+' | '-'\n const validSign: '+' | '-' = sign;\n\n const rest = raw.slice(1);\n\n // Validate rest is not empty\n if (rest.length === 0) {\n throw new ParseError(`Offset sign must be followed by digits`, position);\n }\n\n let hours: number;\n let minutes: number;\n\n if (rest.includes(':')) {\n // Format: +08:00 or -05:30\n const parts = rest.split(':');\n if (parts.length !== 2) {\n throw new ParseError(`Invalid offset format: ${raw} (expected HH:MM)`, position);\n }\n // After length check, we know parts[0] and parts[1] exist\n const [h, m] = parts;\n hours = toInt(h!, 'offset hours', position);\n minutes = toInt(m!, 'offset minutes', position);\n } else if (rest.length === 4) {\n // Format: +0530 or -0800\n hours = toInt(rest.slice(0, 2), 'offset hours', position);\n minutes = toInt(rest.slice(2, 4), 'offset minutes', position);\n } else if (rest.length === 2) {\n // Format: +08 or -05\n hours = toInt(rest, 'offset hours', position);\n minutes = 0;\n } else {\n throw new ParseError(\n `Invalid offset format: ${raw} (expected +HH:MM, +HHMM, or +HH)`,\n position,\n );\n }\n\n // Validate ranges\n if (hours < 0 || hours > MAX_OFFSET_HOURS) {\n throw new ParseError(`Offset hours must be 0-${MAX_OFFSET_HOURS}, got ${hours}`, position);\n }\n if (minutes < 0 || minutes > MAX_MINUTES) {\n throw new ParseError(`Offset minutes must be 0-${MAX_MINUTES}, got ${minutes}`, position);\n }\n\n return { kind: 'NumericOffset', sign: validSign, hours, minutes, raw };\n}\n", "// parser.ts\n// A practical recursive-descent parser that works with the lexer we wrote.\n// It builds an AST for a useful Temporal subset:\n// - date/time (with optional fractional seconds)\n// - timezone: Z | numeric offset (+08:00, -0530, +09) | bracket TZ id [Asia/Singapore]\n// - duration: P... with optional T part\n// - range: <expr> \"/\" <expr> where each side can be dateTime/date/duration, and either side may be empty (open range)\n// - bracket extensions like [u-ca=gregory], [u-tz=Asia/Singapore], etc. (stored as key/value pairs)\n\nimport { combineTimezoneOffsets } from './combineTimezoneOffsets.js';\nimport { ParseError } from './errors.js';\nimport { toInt } from './helpers/toInt.js';\nimport { lexTemporal } from './lexer.js';\nimport { type AnyToken, CombinedTokType, TokType } from './lexer-types.js';\nimport { parseOffset } from './parseOffset.js';\nimport type {\n AnnotationAst,\n DateAst,\n DateTimeAst,\n DurationAst,\n OffsetAst,\n TemporalAst,\n TimeAst,\n TimeZoneAst,\n ValueAst,\n} from './parser-types.js';\n\n// ---------- Parser ----------\n\nclass Parser {\n private i = 0;\n constructor(private toks: AnyToken[]) {}\n\n private peek(k = 0): AnyToken {\n return this.toks[Math.min(this.i + k, this.toks.length - 1)]!;\n }\n private at(type: TokType | CombinedTokType): boolean {\n return this.peek().type === type;\n }\n private eat(type: TokType | CombinedTokType): AnyToken {\n const t = this.peek();\n if (t.type !== type) {\n throw new ParseError(`Expected ${type} but got ${t.type}`, this.i);\n }\n this.i++;\n return t;\n }\n private tryEat(type: TokType | CombinedTokType): AnyToken | null {\n if (this.at(type)) {\n return this.eat(type);\n }\n return null;\n }\n private eof(): boolean {\n return this.at(TokType.EOF);\n }\n\n // Top-level: parse a range if we see '/', otherwise a single value.\n parseTemporal(): TemporalAst {\n // Open-start range: \"/...\"\n if (this.tryEat(TokType.Slash)) {\n const end = this.parseValueOrNull(); // allow empty end too\n this.eat(TokType.EOF);\n return { kind: 'Range', start: null, end };\n }\n\n const first = this.parseValueOrNull();\n // If first is null, only valid if it's \"/\" (handled above). So error:\n if (!first) {\n throw new ParseError('Expected a value', this.i);\n }\n\n if (this.tryEat(TokType.Slash)) {\n const second = this.parseValueOrNull(); // open-ended allowed\n this.eat(TokType.EOF);\n return { kind: 'Range', start: first, end: second };\n }\n\n this.eat(TokType.EOF);\n return first;\n }\n\n private parseValueOrNull(): ValueAst | null {\n // Empty side for open range:\n if (this.at(TokType.EOF) || this.at(TokType.Slash) || this.at(TokType.RBracket)) {\n return null;\n }\n\n // Duration starts with IDENT \"P\" (as produced by lexer)\n if (this.at(TokType.Ident) && String(this.peek().value) === 'P') {\n return this.parseDuration();\n }\n\n // Otherwise parse a date/datetime (common case)\n return this.parseDateTime();\n }\n\n // -------- DateTime parsing --------\n // Supports:\n // - YYYY\n // - YYYY-MM\n // - YYYY-MM-DD\n // - + time part with T: YYYY-MM-DDTHH:MM[:SS[.fff]]\n // - offset: Z | +08:00 | -0530\n // - timezone: [Asia/Singapore] (first bracketed IANA timezone)\n // - annotations: [u-ca=...][u-tz=...] (other bracketed content)\n private parseDateTime(): DateTimeAst {\n const date = this.parseDate();\n\n let time: DateTimeAst['time'] | undefined;\n\n // time separator is TokType.T (from lexer special-casing \"T\")\n if (this.tryEat(TokType.T)) {\n time = this.parseTime();\n }\n\n // Parse numeric offset (Z or +HH:MM)\n const offset = this.parseOffsetIfPresent();\n\n // Parse bracket groups: first IANA timezone becomes timeZone, rest are annotations\n const annotations: AnnotationAst[] = [];\n let timeZone: TimeZoneAst | undefined;\n\n while (this.at(TokType.LBracket)) {\n const { raw, critical, isLikelyTzId, pairs } = this.parseBracketGroup();\n\n // First timezone-like bracket becomes the timeZone field\n // Note: Even though IXDTF doesn't typically use critical flags for timezones,\n // we preserve it in case the input has it\n if (timeZone === undefined && isLikelyTzId) {\n // Strip leading ! from id (it's stored in the critical flag)\n const id = critical ? raw.slice(1) : raw;\n timeZone = { kind: 'IanaTimeZone', id, critical };\n } else {\n annotations.push({ kind: 'Annotation', raw, critical, pairs });\n }\n }\n\n return { kind: 'DateTime', date, time, offset, timeZone, annotations };\n }\n\n private parseDate(): DateAst {\n // Check for optional leading dash (negative year / BC date)\n // ISO 8601: Year 0 = 1 BC, Year -1 = 2 BC, etc.\n const isNegative = this.tryEat(TokType.Dash);\n \n const yTok = this.eat(TokType.Number);\n let year = toInt(yTok.value, 'year', this.i);\n if (isNegative) {\n year = -year;\n }\n\n // Optional -MM\n if (!this.tryEat(TokType.Dash)) {\n return { kind: 'Date', year };\n }\n const mTok = this.eat(TokType.Number);\n const month = toInt(mTok.value, 'month', this.i);\n\n // Optional -DD\n if (!this.tryEat(TokType.Dash)) {\n return { kind: 'Date', year, month };\n }\n const dTok = this.eat(TokType.Number);\n const day = toInt(dTok.value, 'day', this.i);\n\n return { kind: 'Date', year, month, day };\n }\n\n private parseTime(): TimeAst {\n const hTok = this.eat(TokType.Number);\n const hour = toInt(hTok.value, 'hour', this.i);\n\n this.eat(TokType.Colon);\n const minTok = this.eat(TokType.Number);\n const minute = toInt(minTok.value, 'minute', this.i);\n\n let second: number | undefined;\n let fraction: string | undefined;\n\n if (this.tryEat(TokType.Colon)) {\n const sTok = this.eat(TokType.Number);\n second = toInt(sTok.value, 'second', this.i);\n\n // ISO 8601 allows both . and , as decimal separators for fractional seconds\n if (this.tryEat(TokType.Dot) || this.tryEat(TokType.Comma)) {\n const fracTok = this.eat(TokType.Number);\n fraction = fracTok.value; // keep raw digits\n }\n }\n\n return { kind: 'Time', hour, minute, second, fraction };\n }\n\n /**\n * Parse timezone offset: Z | +HH:MM | -HH:MM\n *\n * Important semantic distinction per RFC 3339 and ISO 8601:\n *\n * - Z (Zulu time):\n * Explicitly indicates UTC. Used when the time is known to be in UTC.\n * Has no sign ambiguity - it's always UTC, never local time.\n *\n * - +00:00 (zero positive offset):\n * Indicates local time at the prime meridian (UTC+0 timezone).\n * Semantically different from Z - this is a local time that happens to match UTC.\n *\n * - -00:00 (zero negative offset):\n * Per RFC 3339 section 4.3: indicates local time with unknown UTC offset.\n * Used when the time is known to be local but the offset is not known.\n * This is semantically very different from both Z and +00:00.\n * https://www.rfc-editor.org/rfc/rfc3339.html#section-4.3\n *\n * We preserve this distinction by treating Z as UtcOffset (no sign, no components)\n * while +00:00 and -00:00 are NumericOffset (with sign and parsed components).\n */\n private parseOffsetIfPresent(): OffsetAst | undefined {\n if (this.tryEat(TokType.Z)) {\n return { kind: 'UtcOffset' };\n }\n\n // combined numeric offset token if you used combineTimezoneOffsets()\n if (this.at(CombinedTokType.TZOffset)) {\n const t = this.eat(CombinedTokType.TZOffset);\n return parseOffset(t.value, this.i);\n }\n\n return undefined;\n }\n\n // -------- Bracket group parsing --------\n // Consumes: [ ... ]\n // Returns:\n // - raw inner text (reconstructed)\n // - critical flag (true if starts with !)\n // - pairs if it looks like key=value (or flags)\n // - isLikelyTzId for things like [Asia/Singapore]\n private parseBracketGroup(): {\n raw: string;\n critical: boolean;\n pairs: Record<string, string | true>;\n isLikelyTzId: boolean;\n } {\n this.eat(TokType.LBracket);\n\n const parts: string[] = [];\n const pairs: Record<string, string | true> = {};\n\n // Check for critical flag (IXDTF: [!u-ca=gregory])\n const critical = this.tryEat(TokType.Exclamation) != null;\n if (critical) {\n parts.push('!');\n }\n\n // Gather tokens until RBracket\n while (!this.at(TokType.RBracket)) {\n if (this.eof()) {\n throw new ParseError('Unterminated bracket group', this.i);\n }\n\n const t = this.peek();\n this.i++;\n\n parts.push(String(t.value));\n }\n\n this.eat(TokType.RBracket);\n\n const raw = parts.join('');\n\n // Best-effort parse into key/value pairs:\n // Support \"key=value\" and \"flag\" and \"k=v,k2=v2\"\n // Strip leading ! if present for parsing\n const contentToParse = critical ? raw.slice(1) : raw;\n const segments = contentToParse\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean);\n for (const seg of segments) {\n const eq = seg.indexOf('=');\n if (eq === -1) {\n pairs[seg] = true;\n } else {\n const k = seg.slice(0, eq).trim();\n const v = seg.slice(eq + 1).trim();\n if (k) {\n pairs[k] = v || true;\n }\n }\n }\n\n // Heuristic: likely TZ ID if no '=' present and contains '/' (e.g. Asia/Singapore) or starts with Etc/\n // We now preserve critical flag even for timezones, so don't exclude them\n const isLikelyTzId =\n contentToParse.indexOf('=') === -1 &&\n (contentToParse.includes('/') || contentToParse.startsWith('Etc/'));\n\n // Note: If you want STRICTLY separate tz-id brackets vs extension brackets, do it here.\n\n return { raw, critical, pairs, isLikelyTzId };\n }\n\n // -------- Duration parsing --------\n // Grammar (subset):\n // P (Y M W D)* (T (H M S)*)?\n // Examples:\n // P1Y2M\n // P3W\n // P10DT2H30M\n // PT1.5S\n private parseDuration(): DurationAst {\n // Collect raw string as we parse\n const rawParts: string[] = [];\n const annotations: AnnotationAst[] = [];\n\n const pTok = this.eat(TokType.Ident);\n if (String(pTok.value) !== 'P') {\n throw new ParseError(\"Duration must start with 'P'\", this.i);\n }\n rawParts.push('P');\n\n let inTime = false;\n\n const dur: Omit<DurationAst, 'kind' | 'raw' | 'annotations'> = {};\n\n // Parse designators until we hit bracket annotations, slash, or EOF\n while (!this.eof() && !this.at(TokType.Slash) && !this.at(TokType.LBracket)) {\n // Time marker T\n if (this.tryEat(TokType.T)) {\n rawParts.push('T');\n inTime = true;\n continue;\n }\n\n // Expect a number (possibly with fraction for seconds)\n const numTok = this.tryEat(TokType.Number);\n if (!numTok) {\n break;\n } // be permissive; let parser user decide if this is error-worthy\n rawParts.push(numTok.value);\n\n let fraction: string | undefined;\n // ISO 8601 allows both . and , as decimal separators\n const dotOrComma = this.tryEat(TokType.Dot) || this.tryEat(TokType.Comma);\n if (dotOrComma) {\n rawParts.push(dotOrComma.value); // Preserve the actual separator (. or ,)\n const fracTok = this.eat(TokType.Number);\n rawParts.push(fracTok.value);\n fraction = fracTok.value;\n }\n\n // Unit is IDENT (Y,M,W,D,H,M,S). Note M is months if !inTime else minutes.\n const unitTok = this.eat(TokType.Ident);\n const unit = String(unitTok.value);\n rawParts.push(unit);\n\n const n = toInt(numTok.value, 'duration number', this.i);\n\n if (!inTime) {\n if (unit === 'Y') {\n dur.years = (dur.years ?? 0) + n;\n } else if (unit === 'M') {\n dur.months = (dur.months ?? 0) + n;\n } else if (unit === 'W') {\n dur.weeks = (dur.weeks ?? 0) + n;\n } else if (unit === 'D') {\n dur.days = (dur.days ?? 0) + n;\n } else {\n throw new ParseError(`Invalid duration unit '${unit}' in date part`, this.i);\n }\n } else {\n if (unit === 'H') {\n dur.hours = (dur.hours ?? 0) + n;\n } else if (unit === 'M') {\n dur.minutes = (dur.minutes ?? 0) + n;\n } else if (unit === 'S') {\n dur.seconds = (dur.seconds ?? 0) + n;\n if (fraction !== undefined && fraction.length > 0) {\n dur.secondsFraction = fraction;\n }\n } else {\n throw new ParseError(`Invalid duration unit '${unit}' in time part`, this.i);\n }\n }\n }\n\n // bracket annotations after duration\n while (this.at(TokType.LBracket)) {\n const { raw, critical, pairs } = this.parseBracketGroup();\n annotations.push({ kind: 'Annotation', raw, critical, pairs });\n }\n\n return { kind: 'Duration', ...dur, raw: rawParts.join(''), annotations };\n }\n}\n\n// ---------- Public API ----------\nexport function parseTemporal(src: string): TemporalAst {\n const raw = lexTemporal(src);\n const toks = combineTimezoneOffsets(raw);\n const p = new Parser(toks);\n return p.parseTemporal();\n}\n", "// stringify.ts\n// Convert AST back to temporal string representation\n\nimport type {\n AnnotationAst,\n DateAst,\n DateTimeAst,\n DurationAst,\n OffsetAst,\n RangeAst,\n TemporalAst,\n TimeAst,\n TimeZoneAst,\n} from './parser-types.js';\n\n/**\n * Convert a temporal AST back to its string representation.\n *\n * @param ast - The AST to stringify\n * @returns ISO 8601 / IXDTF formatted string\n *\n * @example\n * ```typescript\n * const ast = parseTemporal('2025-01-12T10:00:00+08:00');\n * const str = stringifyTemporal(ast);\n * // '2025-01-12T10:00:00+08:00'\n * ```\n */\nexport function stringifyTemporal(ast: TemporalAst): string {\n if (ast.kind === 'Range') {\n return stringifyRange(ast);\n }\n if (ast.kind === 'Duration') {\n return stringifyDuration(ast);\n }\n return stringifyDateTime(ast);\n}\n\n/**\n * Stringify a date AST to ISO 8601 format.\n *\n * @param date - The date AST\n * @returns Date string (YYYY, YYYY-MM, or YYYY-MM-DD)\n */\nexport function stringifyDate(date: DateAst): string {\n // Handle negative years (BC dates) - ISO 8601 uses negative years\n // Year 0 = 1 BC, Year -1 = 2 BC, etc.\n let yearStr: string;\n if (date.year < 0) {\n // For negative years, pad the absolute value and prepend the minus sign\n yearStr = '-' + Math.abs(date.year).toString().padStart(4, '0');\n } else {\n yearStr = date.year.toString().padStart(4, '0');\n }\n\n const parts: string[] = [yearStr];\n\n if (date.month != null) {\n parts.push(date.month.toString().padStart(2, '0'));\n\n if (date.day != null) {\n parts.push(date.day.toString().padStart(2, '0'));\n }\n }\n\n return parts.join('-');\n}\n\n/**\n * Stringify a time AST to ISO 8601 format.\n *\n * @param time - The time AST\n * @returns Time string (HH:MM, HH:MM:SS, or HH:MM:SS.fff)\n */\nexport function stringifyTime(time: TimeAst): string {\n const parts: string[] = [\n time.hour.toString().padStart(2, '0'),\n time.minute.toString().padStart(2, '0'),\n ];\n\n if (time.second != null) {\n let secondStr = time.second.toString().padStart(2, '0');\n if (time.fraction != null) {\n secondStr += `.${time.fraction}`;\n }\n parts.push(secondStr);\n }\n\n return parts.join(':');\n}\n\n/**\n * Stringify a timezone offset AST to canonical format.\n *\n * @param offset - The offset AST\n * @returns Offset string in canonical format (Z or \u00B1HH:MM)\n */\nexport function stringifyOffset(offset: OffsetAst): string {\n if (offset.kind === 'UtcOffset') {\n return 'Z';\n }\n // Return canonical format: +HH:MM or -HH:MM\n const hours = offset.hours.toString().padStart(2, '0');\n const minutes = offset.minutes.toString().padStart(2, '0');\n return `${offset.sign}${hours}:${minutes}`;\n}\n\n/**\n * Stringify a timezone AST.\n *\n * @param timeZone - The timezone AST\n * @returns Timezone string [Asia/Singapore] or [!Asia/Singapore]\n */\nexport function stringifyTimeZone(timeZone: TimeZoneAst): string {\n const id = timeZone.critical ? `!${timeZone.id}` : timeZone.id;\n return `[${id}]`;\n}\n\n/**\n * Stringify an annotation AST.\n *\n * @param annotation - The annotation AST\n * @returns Annotation string [u-ca=gregory] or [!u-ca=gregory]\n */\nexport function stringifyAnnotation(annotation: AnnotationAst): string {\n return `[${annotation.raw}]`;\n}\n\n/**\n * Stringify a datetime AST to ISO 8601 / IXDTF format.\n *\n * @param dateTime - The datetime AST\n * @returns DateTime string with optional time, offset, timezone, and annotations\n */\nexport function stringifyDateTime(dateTime: DateTimeAst): string {\n let result = stringifyDate(dateTime.date);\n\n if (dateTime.time) {\n result += `T${stringifyTime(dateTime.time)}`;\n }\n\n if (dateTime.offset) {\n result += stringifyOffset(dateTime.offset);\n }\n\n if (dateTime.timeZone) {\n result += stringifyTimeZone(dateTime.timeZone);\n }\n\n for (const annotation of dateTime.annotations) {\n result += stringifyAnnotation(annotation);\n }\n\n return result;\n}\n\n/**\n * Stringify a duration AST to ISO 8601 format.\n *\n * @param duration - The duration AST\n * @returns Duration string (P1Y2M3DT4H5M6S)\n */\nexport function stringifyDuration(duration: DurationAst): string {\n // Always reconstruct from components to ensure normalization\n let result = 'P';\n\n // Date part - include component if defined (even if zero)\n if (duration.years != null) {\n result += `${duration.years}Y`;\n }\n if (duration.months != null) {\n result += `${duration.months}M`;\n }\n if (duration.weeks != null) {\n result += `${duration.weeks}W`;\n }\n if (duration.days != null) {\n result += `${duration.days}D`;\n }\n\n // Time part - add T separator if any time component is defined\n const hasTimePart =\n duration.hours != null || duration.minutes != null || duration.seconds != null;\n if (hasTimePart) {\n result += 'T';\n if (duration.hours != null) {\n result += `${duration.hours}H`;\n }\n if (duration.minutes != null) {\n result += `${duration.minutes}M`;\n }\n if (duration.seconds != null) {\n result += `${duration.seconds}`;\n if (duration.secondsFraction != null && duration.secondsFraction.length > 0) {\n result += `.${duration.secondsFraction}`;\n }\n result += 'S';\n }\n }\n\n // Annotations\n for (const annotation of duration.annotations) {\n result += stringifyAnnotation(annotation);\n }\n\n return result;\n}\n\n/**\n * Stringify a range AST to ISO 8601 format.\n *\n * @param range - The range AST\n * @returns Range string (start/end, /end, or start/)\n */\nexport function stringifyRange(range: RangeAst): string {\n const start = range.start\n ? range.start.kind === 'Duration'\n ? stringifyDuration(range.start)\n : stringifyDateTime(range.start)\n : '';\n\n const end = range.end\n ? range.end.kind === 'Duration'\n ? stringifyDuration(range.end)\n : stringifyDateTime(range.end)\n : '';\n\n return `${start}/${end}`;\n}\n"], | ||
| "mappings": "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", | ||
| "names": ["TokType", "CombinedTokType"] | ||
| } |
@@ -7,4 +7,5 @@ export { combineTimezoneOffsets } from './combineTimezoneOffsets.js'; | ||
| export { parseOffset } from './parseOffset.js'; | ||
| export { stringifyAnnotation, stringifyDate, stringifyDateTime, stringifyDuration, stringifyOffset, stringifyRange, stringifyTemporal, stringifyTime, stringifyTimeZone, } from './stringify.js'; | ||
| export type { AnnotationAst, DateAst, DateTimeAst, DurationAst, OffsetAst, RangeAst, TemporalAst, TimeAst, TimeZoneAst, ValueAst, } from './parser-types.js'; | ||
| export { LexError, ParseError } from './errors.js'; | ||
| //# sourceMappingURL=index.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAGzC,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACvE,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAG5D,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAG5C,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAG/C,YAAY,EACV,aAAa,EACb,OAAO,EACP,WAAW,EACX,WAAW,EACX,SAAS,EACT,QAAQ,EACR,WAAW,EACX,OAAO,EACP,WAAW,EACX,QAAQ,GACT,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC"} | ||
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAGzC,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACvE,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAG5D,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAG5C,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAG/C,OAAO,EACL,mBAAmB,EACnB,aAAa,EACb,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,iBAAiB,EACjB,aAAa,EACb,iBAAiB,GAClB,MAAM,gBAAgB,CAAC;AAGxB,YAAY,EACV,aAAa,EACb,OAAO,EACP,WAAW,EACX,WAAW,EACX,SAAS,EACT,QAAQ,EACR,WAAW,EACX,OAAO,EACP,WAAW,EACX,QAAQ,GACT,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC"} |
@@ -18,2 +18,6 @@ export type TemporalAst = RangeAst | ValueAst; | ||
| kind: 'Date'; | ||
| /** | ||
| * Components are receivable by Temporal.PlainDate.from(). | ||
| * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainDate/from | ||
| */ | ||
| year: number; | ||
@@ -25,2 +29,6 @@ month?: number; | ||
| kind: 'Time'; | ||
| /** | ||
| * Components are receivable by Temporal.PlainTime.from(). | ||
| * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainTime/from | ||
| */ | ||
| hour: number; | ||
@@ -27,0 +35,0 @@ minute: number; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"parser-types.d.ts","sourceRoot":"","sources":["../../src/parser-types.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE9C,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,QAAQ,GAAG,IAAI,CAAC;IACvB,GAAG,EAAE,QAAQ,GAAG,IAAI,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG,WAAW,GAAG,WAAW,CAAC;AAEjD,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,UAAU,CAAC;IACjB,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,QAAQ,CAAC,EAAE,WAAW,CAAC;IACvB,WAAW,EAAE,aAAa,EAAE,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,SAAS,GACjB;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,GACrB;IACE,IAAI,EAAE,eAAe,CAAC;IACtB,IAAI,EAAE,GAAG,GAAG,GAAG,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAEN,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,cAAc,CAAC;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,YAAY,CAAC;IAEnB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;CACtC,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,UAAU,CAAC;IAGjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,aAAa,EAAE,CAAC;CAC9B,CAAC"} | ||
| {"version":3,"file":"parser-types.d.ts","sourceRoot":"","sources":["../../src/parser-types.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE9C,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,QAAQ,GAAG,IAAI,CAAC;IACvB,GAAG,EAAE,QAAQ,GAAG,IAAI,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG,WAAW,GAAG,WAAW,CAAC;AAEjD,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,UAAU,CAAC;IACjB,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,QAAQ,CAAC,EAAE,WAAW,CAAC;IACvB,WAAW,EAAE,aAAa,EAAE,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG;IACpB,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG;IACpB,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,SAAS,GACjB;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,GACrB;IACE,IAAI,EAAE,eAAe,CAAC;IACtB,IAAI,EAAE,GAAG,GAAG,GAAG,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAEN,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,cAAc,CAAC;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,YAAY,CAAC;IAEnB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;CACtC,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,UAAU,CAAC;IAKjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,aAAa,EAAE,CAAC;CAC9B,CAAC"} |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/parser.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAMV,WAAW,EAIZ,MAAM,mBAAmB,CAAC;AA0W3B,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,CAKtD"} | ||
| {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/parser.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAMV,WAAW,EAIZ,MAAM,mBAAmB,CAAC;AAoX3B,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,CAKtD"} |
+1
-1
| { | ||
| "name": "@taskade/temporal-parser", | ||
| "version": "1.0.5", | ||
| "version": "1.1.0", | ||
| "description": "A lexer/parser based temporal expression parser for ISO 8601 and IXDTF formats", | ||
@@ -5,0 +5,0 @@ "type": "module", |
+51
-0
@@ -45,2 +45,6 @@ # Temporal Parser | ||
| // { kind: 'Range', start: {...}, end: {...} } | ||
| // Parse BC dates (negative years in ISO 8601) | ||
| const bcDate = parseTemporal('-0044-03-15'); | ||
| // { kind: 'DateTime', date: { year: -44, month: 3, day: 15 }, ... } | ||
| ``` | ||
@@ -54,2 +58,3 @@ | ||
| - Full date: `2025-01-12` | ||
| - BC dates (negative years): `-0044-03-15` (44 BC), `0000-01-01` (1 BC) | ||
@@ -60,2 +65,3 @@ ### Times | ||
| - With fractional seconds: `T10:30:45.123456789` | ||
| - European format (comma): `T10:30:45,123` (normalized to dot in output) | ||
@@ -71,2 +77,3 @@ ### Timezones | ||
| - Combined: `P1Y2M3DT4H5M6S` | ||
| - Fractional seconds: `PT1.5S` or `PT1,5S` (comma normalized to dot) | ||
@@ -107,2 +114,30 @@ ### IXDTF Annotations | ||
| ### Stringify AST Back to String | ||
| ```typescript | ||
| import { parseTemporal, stringifyTemporal } from '@taskade/temporal-parser'; | ||
| // Parse and stringify | ||
| const ast = parseTemporal('2025-01-12T10:00:00+08:00[Asia/Singapore]'); | ||
| const str = stringifyTemporal(ast); | ||
| // '2025-01-12T10:00:00+08:00[Asia/Singapore]' | ||
| // Offsets are normalized to canonical format (±HH:MM) | ||
| const ast2 = parseTemporal('2025-01-12T10:00:00+0530'); // Compact format | ||
| const str2 = stringifyTemporal(ast2); | ||
| // '2025-01-12T10:00:00+05:30' (normalized) | ||
| // Stringify individual components | ||
| import { stringifyDate, stringifyTime, stringifyDuration } from '@taskade/temporal-parser'; | ||
| stringifyDate({ kind: 'Date', year: 2025, month: 1, day: 12 }); | ||
| // '2025-01-12' | ||
| stringifyTime({ kind: 'Time', hour: 10, minute: 30, second: 45 }); | ||
| // '10:30:45' | ||
| stringifyDuration({ kind: 'Duration', years: 1, months: 2, raw: 'P1Y2M', annotations: [] }); | ||
| // 'P1Y2M' | ||
| ``` | ||
| ## Motivation | ||
@@ -177,2 +212,18 @@ | ||
| ### `stringifyTemporal(ast: TemporalAst): string` | ||
| Converts a temporal AST back to its string representation. | ||
| **Returns:** ISO 8601 / IXDTF formatted string | ||
| **Also available:** | ||
| - `stringifyDate(date: DateAst): string` | ||
| - `stringifyTime(time: TimeAst): string` | ||
| - `stringifyDateTime(dateTime: DateTimeAst): string` | ||
| - `stringifyDuration(duration: DurationAst): string` | ||
| - `stringifyRange(range: RangeAst): string` | ||
| - `stringifyOffset(offset: OffsetAst): string` | ||
| - `stringifyTimeZone(timeZone: TimeZoneAst): string` | ||
| - `stringifyAnnotation(annotation: AnnotationAst): string` | ||
| ## TypeScript Support | ||
@@ -179,0 +230,0 @@ |
192351
23.01%34
6.25%1836
22.81%252
25.37%