Skip to content

EDTFDate

Represents a calendar date with optional precision, unspecified digits, and qualifications.

Type Definition

typescript
interface EDTFDate {
  readonly type: 'Date';
  readonly level: 0 | 1 | 2;
  readonly edtf: string;
  readonly precision: 'year' | 'month' | 'day';

  readonly year: number;
  readonly month?: number;  // 1-12
  readonly day?: number;    // 1-31

  // Unspecified digits
  readonly yearUnspecified?: string;   // e.g., "XXXX", "19XX", "198X"
  readonly monthUnspecified?: string;  // e.g., "XX"
  readonly dayUnspecified?: string;    // e.g., "XX"

  // Component-level qualifications (Level 2)
  readonly yearQualification?: Qualification;
  readonly monthQualification?: Qualification;
  readonly dayQualification?: Qualification;

  // Date-level qualification (Level 1)
  readonly qualification?: Qualification;

  // Calculated bounds (Date objects - may be clamped for extended years)
  readonly min: Date;
  readonly max: Date;

  // Calculated bounds (BigInt - always accurate)
  readonly minMs: bigint;
  readonly maxMs: bigint;

  // True if min/max Date values were clamped due to JavaScript Date limitations
  readonly isBoundsClamped?: boolean;

  // Serialization
  toJSON(): object;
}

interface Qualification {
  uncertain?: boolean;    // ? marker
  approximate?: boolean;  // ~ marker
  both?: boolean;         // % marker (uncertain AND approximate)
}

Properties

Core Properties

type

typescript
readonly type: 'Date'

Always 'Date' for EDTFDate objects. Use for type discrimination.

typescript
import { parse } from '@edtf-ts/core';

const result = parse('1985-04-12');
if (result.success && result.value.type === 'Date') {
  // TypeScript knows this is EDTFDate
  console.log(result.value.year);  // 1985
}

level

typescript
readonly level: 0 | 1 | 2

EDTF conformance level:

  • 0: ISO 8601 profile (basic dates)
  • 1: Adds uncertainty/approximation qualifications
  • 2: Adds component-level qualifications and unspecified digits
typescript
parse('1985-04-12').value.level;  // 0
parse('1985?').value.level;       // 1
parse('2004-06-~01').value.level; // 2

edtf

typescript
readonly edtf: string

Original EDTF string representation.

typescript
parse('1985-04-12').value.edtf;  // "1985-04-12"
parse('1985?').value.edtf;       // "1985?"

precision

typescript
readonly precision: 'year' | 'month' | 'day'

The precision level of the date.

typescript
parse('1985').value.precision;        // 'year'
parse('1985-04').value.precision;     // 'month'
parse('1985-04-12').value.precision;  // 'day'

Date Components

year

typescript
readonly year: number

The year value. Can be negative for historical dates.

typescript
parse('1985-04-12').value.year;   // 1985
parse('0000').value.year;         // 0 (1 BC in historical notation)
parse('-0100').value.year;        // -100 (101 BC)

Astronomical Year Numbering

EDTF uses astronomical year numbering where year 0 = 1 BC, year -1 = 2 BC, etc.

month

typescript
readonly month?: number  // 1-12

Month number (1-12). Only present if precision is 'month' or 'day'.

typescript
parse('1985').value.month;        // undefined
parse('1985-04').value.month;     // 4
parse('1985-04-12').value.month;  // 4

day

typescript
readonly day?: number  // 1-31

Day of month. Only present if precision is 'day'.

typescript
parse('1985').value.day;        // undefined
parse('1985-04').value.day;     // undefined
parse('1985-04-12').value.day;  // 12

Unspecified Digits

yearUnspecified

typescript
readonly yearUnspecified?: string

Pattern of unspecified year digits using 'X'.

typescript
parse('19XX').value.yearUnspecified;   // "19XX"
parse('198X').value.yearUnspecified;   // "198X"
parse('XXXX').value.yearUnspecified;   // "XXXX"

monthUnspecified

typescript
readonly monthUnspecified?: string

Pattern of unspecified month digits.

typescript
parse('1985-XX').value.monthUnspecified;  // "XX"

dayUnspecified

typescript
readonly dayUnspecified?: string

Pattern of unspecified day digits.

typescript
parse('1985-04-XX').value.dayUnspecified;  // "XX"
parse('1985-XX-XX').value.dayUnspecified;  // "XX"
parse('1985-XX-XX').value.monthUnspecified;  // "XX"

Unspecified Values

When digits are unspecified, the corresponding component (year, month, day) represents the first value in the range. Use min and max for the full range.

Qualifications

qualification (Level 1)

typescript
readonly qualification?: Qualification

Whole-date qualification applied to the entire date.

typescript
parse('1985?').value.qualification;   // { uncertain: true }
parse('1985~').value.qualification;   // { approximate: true }
parse('1985%').value.qualification;   // { both: true }

Component Qualifications (Level 2)

typescript
readonly yearQualification?: Qualification
readonly monthQualification?: Qualification
readonly dayQualification?: Qualification

Qualifications applied to specific components.

typescript
// Uncertain year
parse('?1985').value.yearQualification;  // { uncertain: true }

// Approximate month
parse('2004-~06').value.monthQualification;  // { approximate: true }

// Approximate day
parse('2004-06-~01').value.dayQualification;  // { approximate: true }

Level 1 vs Level 2 Qualifications

  • Level 1 (1985?): Qualification applies to the whole date
  • Level 2 (?1985 or 2004-~06): Qualifications can target specific components

Calculated Bounds

min

typescript
readonly min: Date

Earliest possible date represented by this EDTF value.

typescript
parse('1985').value.min;          // Date('1985-01-01T00:00:00.000Z')
parse('1985-04').value.min;       // Date('1985-04-01T00:00:00.000Z')
parse('198X').value.min;          // Date('1980-01-01T00:00:00.000Z')
parse('1985-XX').value.min;       // Date('1985-01-01T00:00:00.000Z')

max

typescript
readonly max: Date

Latest possible date represented by this EDTF value.

typescript
parse('1985').value.max;          // Date('1985-12-31T23:59:59.999Z')
parse('1985-04').value.max;       // Date('1985-04-30T23:59:59.999Z')
parse('198X').value.max;          // Date('1989-12-31T23:59:59.999Z')
parse('1985-XX').value.max;       // Date('1985-12-31T23:59:59.999Z')

Bounds Represent Ranges

min and max represent the full time range of the precision level. A year spans from January 1st 00:00:00.000 to December 31st 23:59:59.999.

Extended Years

For extended years (using Y prefix) that exceed JavaScript's Date range (~±270,000 years), min and max are clamped to the valid range. Use minMs and maxMs for accurate values. See Extended Years below.

minMs

typescript
readonly minMs: bigint

Earliest possible date as epoch milliseconds (BigInt). Always accurate, even for extended years.

typescript
parse('1985').value.minMs;       // 473385600000n
parse('Y2000000').value.minMs;   // Accurate bigint for year 2,000,000

maxMs

typescript
readonly maxMs: bigint

Latest possible date as epoch milliseconds (BigInt). Always accurate, even for extended years.

typescript
parse('1985').value.maxMs;       // 504921599999n
parse('Y2000000').value.maxMs;   // Accurate bigint for year 2,000,000

isBoundsClamped

typescript
readonly isBoundsClamped?: boolean

true if min/max Date values were clamped due to JavaScript Date limitations. When this is true, use minMs/maxMs for accurate bounds.

typescript
parse('1985').value.isBoundsClamped;      // undefined (not clamped)
parse('Y2000000').value.isBoundsClamped;  // true (clamped)

Methods

toJSON()

typescript
toJSON(): object

Serialize to a plain JavaScript object.

typescript
const date = parse('1985-04-12');
if (date.success) {
  const json = date.value.toJSON();
  // {
  //   type: 'Date',
  //   level: 0,
  //   edtf: '1985-04-12',
  //   precision: 'day',
  //   year: 1985,
  //   month: 4,
  //   day: 12,
  //   min: '1985-04-12T00:00:00.000Z',
  //   max: '1985-04-12T23:59:59.999Z'
  // }

  JSON.stringify(json);  // Safe for JSON serialization
}

Examples

Level 0: Basic Dates

typescript
import { parse } from '@edtf-ts/core';

// Year
const year = parse('1985').value;
// { type: 'Date', precision: 'year', year: 1985, level: 0 }

// Year-month
const month = parse('1985-04').value;
// { type: 'Date', precision: 'month', year: 1985, month: 4, level: 0 }

// Complete date
const day = parse('1985-04-12').value;
// { type: 'Date', precision: 'day', year: 1985, month: 4, day: 12, level: 0 }

// Historical dates (astronomical year numbering)
const bc = parse('-0100').value;  // 101 BC
// { year: -100 }

Level 1: Qualifications

typescript
// Uncertain
const uncertain = parse('1984?').value;
// { year: 1984, qualification: { uncertain: true }, level: 1 }

// Approximate
const approx = parse('1984~').value;
// { year: 1984, qualification: { approximate: true }, level: 1 }

// Both uncertain and approximate
const both = parse('1984%').value;
// { year: 1984, qualification: { both: true }, level: 1 }

Level 2: Partial Qualifications

typescript
// Uncertain year
const uncertainYear = parse('?1984').value;
// { year: 1984, yearQualification: { uncertain: true }, level: 2 }

// Approximate month
const approxMonth = parse('2004-~06').value;
// { year: 2004, month: 6, monthQualification: { approximate: true }, level: 2 }

// Approximate day
const approxDay = parse('2004-06-~11').value;
// { year: 2004, month: 6, day: 11, dayQualification: { approximate: true }, level: 2 }

// Qualifier after year, before month
const qualAfterYear = parse('2004?-06').value;
// { year: 2004, month: 6, yearQualification: { uncertain: true }, level: 2 }

Level 2: Unspecified Digits

typescript
// Unspecified decade
const decade = parse('198X').value;
// {
//   year: 1980,
//   yearUnspecified: '198X',
//   min: Date('1980-01-01'),
//   max: Date('1989-12-31'),
//   level: 2
// }

// Unspecified month
const anyMonth = parse('1985-XX').value;
// {
//   year: 1985,
//   month: 1,
//   monthUnspecified: 'XX',
//   min: Date('1985-01-01'),
//   max: Date('1985-12-31'),
//   level: 2
// }

// Unspecified day
const anyDay = parse('1985-04-XX').value;
// {
//   year: 1985,
//   month: 4,
//   day: 1,
//   dayUnspecified: 'XX',
//   min: Date('1985-04-01'),
//   max: Date('1985-04-30'),
//   level: 2
// }

// Multiple unspecified
const anyDayAnyMonth = parse('1985-XX-XX').value;
// {
//   year: 1985,
//   month: 1,
//   monthUnspecified: 'XX',
//   day: 1,
//   dayUnspecified: 'XX',
//   min: Date('1985-01-01'),
//   max: Date('1985-12-31'),
//   level: 2
// }

Type Guards

Use type guards to check if a value is an EDTFDate:

typescript
import { parse, isEDTFDate } from '@edtf-ts/core';

const result = parse('1985-04-12');

if (result.success && isEDTFDate(result.value)) {
  // TypeScript knows result.value is EDTFDate
  console.log(result.value.year);   // ✓ TypeScript knows this exists
  console.log(result.value.month);  // ✓ TypeScript knows this might be undefined
}

Working with Bounds

The min and max properties provide JavaScript Date objects representing the full range:

typescript
import { parse, isInRange } from '@edtf-ts/core';

const date = parse('1985-04').value;

// Check if a specific date falls within this range
const specific = new Date('1985-04-15');
const inRange = specific >= date.min && specific <= date.max;  // true

// Use with utilities
const target = parse('1985-04-15').value;
isInRange(target, date, date);  // true

Extended Years

EDTF supports extended years using the Y prefix for years with 5 or more digits. JavaScript's Date object can only represent dates within approximately ±270,000 years from the Unix epoch.

The Problem

typescript
// This year is ~2 billion years in the future
const result = parse('Y2000123456');

if (result.success) {
  // Date objects are clamped - these are NOT accurate!
  console.log(result.value.min);  // Clamped to max Date (~year 275760)
  console.log(result.value.max);  // Clamped to max Date (~year 275760)
}

The Solution

Use minMs and maxMs (BigInt values) for accurate epoch milliseconds:

typescript
const result = parse('Y2000123456');

if (result.success) {
  const value = result.value;

  // Check if clamping occurred
  if (value.isBoundsClamped) {
    console.log('Date values are clamped, use BigInt properties');
  }

  // BigInt values are always accurate
  console.log(value.minMs);  // 63117737727846912000n
  console.log(value.maxMs);  // 63117737759469311999n

  // Year is always accurate
  console.log(value.year);   // 2000123456
}

When to Use BigInt Values

ScenarioRecommendation
Normal dates (within ~±270,000 years)Use min/max Date properties
Extended yearsCheck isBoundsClamped, use minMs/maxMs
Database storageUse minMs/maxMs for precise storage
Temporal comparisonThe comparison functions handle this automatically

Example: Safe Bounds Access

typescript
import { parse, DATE_MIN_MS, DATE_MAX_MS } from '@edtf-ts/core';

function getAccurateBounds(edtfString: string) {
  const result = parse(edtfString);

  if (!result.success) {
    throw new Error('Invalid EDTF');
  }

  const value = result.value;

  if (value.isBoundsClamped) {
    // Use BigInt for accurate values
    return {
      minMs: value.minMs,
      maxMs: value.maxMs,
      clamped: true
    };
  }

  // Safe to use Date objects
  return {
    min: value.min,
    max: value.max,
    clamped: false
  };
}

See Also

Released under the MIT License.