Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .changeset/kind-donkeys-bake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
'@taskade/temporal-parser': minor
---

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)
```
44 changes: 44 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,34 @@ const offset = parseOffset('+08:00');
// { kind: 'NumericOffset', sign: '+', hours: 8, minutes: 0, raw: '+08:00' }
```

### 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

Time is one of the most complex human inventions.
Expand Down Expand Up @@ -169,6 +197,22 @@ Parses a numeric timezone offset string.
- Hours: 0-14 (UTC-12:00 to UTC+14:00)
- Minutes: 0-59

### `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

Full TypeScript definitions are included. All AST types are exported:
Expand Down
13 changes: 13 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,19 @@ export { parseTemporal } from './parser.js';
// Export offset parser (useful standalone utility)
export { parseOffset } from './parseOffset.js';

// Export stringify functionality
export {
stringifyAnnotation,
stringifyDate,
stringifyDateTime,
stringifyDuration,
stringifyOffset,
stringifyRange,
stringifyTemporal,
stringifyTime,
stringifyTimeZone,
} from './stringify.js';

// Export parser types
export type {
AnnotationAst,
Expand Down
14 changes: 12 additions & 2 deletions src/parser-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,21 @@ export type DateTimeAst = {

export type DateAst = {
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;
month?: number;
day?: number;
};

export type TimeAst = {
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;
minute: number;
second?: number;
Expand Down Expand Up @@ -61,8 +69,10 @@ export type AnnotationAst = {

export type DurationAst = {
kind: 'Duration';
// Keep both parsed fields and the original string form.
// Months vs minutes ambiguity is handled by position (date part vs time part).
// ISO 8601 duration components (P1Y2M3DT4H5M6S)
// Note: 'M' is disambiguated by position - months in date part, minutes in time part
// Components are compatible with Temporal.Duration.from()
// See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Duration/from
years?: number;
months?: number;
weeks?: number;
Expand Down
Loading