@studnicky/time-conversion
Allocation-free epoch/ISO date conversion using integer civil-calendar arithmetic.
Converts between UTC epoch milliseconds and common textual date formats — ISO-8601 instants and dates, SQL datetimes, and US slash dates — using integer civil-calendar arithmetic (Howard Hinnant's algorithm). It never constructs a JS Date object, avoiding the allocation and locale/timezone ambiguity that come with it.
Install
bash
pnpm add @studnicky/time-conversionRequires @studnicky:registry=https://npm.pkg.github.com in .npmrc.
Parsing mixed formats
epochMs auto-detects the format — ISO instant, ISO date, SQL datetime, or US slash-date. Try it live — edit the code and press Execute:
Auto-detecting mixed date formats
/** parseMixedFormats — demonstrates epochMs parsing ISO, SQL, and slash-date formats. Run: npx tsx examples/parseMixedFormats.ts */
import assert from 'node:assert/strict';
// #region usage
import { CanonicalDate } from '../src/index.js';
function demonstrateParseMixedFormats(): { 'invalid': number | null; 'isoDate': number | null; 'isoInstant': number | null; 'sqlDateTime': number | null; 'usSlashDate': number | null } {
// epochMs auto-detects the format: ISO instant, ISO date, SQL datetime, or US slash-date
const isoInstant = CanonicalDate.epochMs('2024-02-29T12:34:56.789Z');
console.log('ISO instant ->', isoInstant);
const isoDate = CanonicalDate.epochMs('2024-01-01');
console.log('ISO date ->', isoDate);
const sqlDateTime = CanonicalDate.epochMs('2023-03-15 09:08:07');
console.log('SQL datetime ->', sqlDateTime);
const usSlashDate = CanonicalDate.epochMs('3/15/2023');
console.log('US slash date ->', usSlashDate);
// unparseable or empty input returns null instead of throwing
const invalid = CanonicalDate.epochMs('not a date');
console.log('invalid input ->', invalid);
return { 'invalid': invalid, 'isoDate': isoDate, 'isoInstant': isoInstant, 'sqlDateTime': sqlDateTime, 'usSlashDate': usSlashDate };
}
// #endregion usage
const results = demonstrateParseMixedFormats();
assert.equal(results.isoInstant, 1_709_210_096_789);
assert.equal(results.isoDate, 1_704_067_200_000);
assert.equal(results.sqlDateTime, 1_678_871_287_000);
assert.equal(results.usSlashDate, 1_678_838_400_000);
assert.equal(results.invalid, null);
console.log('parseMixedFormats: all assertions passed');
Output
Press Execute to run this example against the real library.Round-tripping through ISO
Formatting epoch milliseconds as an ISO-8601 instant string, including pre-1970 (negative) epoch values:
ts
import { CanonicalDate } from '../src/index.js';
function demonstrateRoundTripIso(): { 'formatted': string | null; 'notFinite': string | null; 'original': number; 'preEpoch': string | null; 'roundTripped': number | null } {
// format epoch milliseconds as an ISO-8601 instant string
const formatted = CanonicalDate.isoFromEpochMs(0);
console.log('epoch 0 ->', formatted);
// negative epoch values (pre-1970) format correctly too
const preEpoch = CanonicalDate.isoFromEpochMs(-2_208_988_800_000);
console.log('pre-1970 epoch ->', preEpoch);
// parsing the formatted string returns the original epoch milliseconds
const original = 1_709_210_096_789;
const roundTripped = CanonicalDate.isoInstantEpochMs(CanonicalDate.isoFromEpochMs(original)!);
console.log('round-tripped epoch ->', roundTripped);
// non-finite input formats to null instead of throwing
const notFinite = CanonicalDate.isoFromEpochMs(Number.NaN);
console.log('NaN input ->', notFinite);
return { 'formatted': formatted, 'notFinite': notFinite, 'original': original, 'preEpoch': preEpoch, 'roundTripped': roundTripped };
}