Skip to content

feat: Temporal dates and times#4759

Draft
kerams wants to merge 2 commits into
fable-compiler:mainfrom
kerams:temporal
Draft

feat: Temporal dates and times#4759
kerams wants to merge 2 commits into
fable-compiler:mainfrom
kerams:temporal

Conversation

@kerams

@kerams kerams commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Implements #4751.

Opt-in with --test:js-temporal, which defines FABLE_COMPILER_JAVASCRIPT_TEMPORAL.

All these types have nanosecond precision, whereas the old representation truncates everything below milliseconds (partly due to Date), which is something that bit me in the past. However, the precision in .NET is a tick (100ns), so the current implementation is truncating nanosecond values on construction and value access.

Comment on lines +7 to +33
declare global {
namespace Temporal {
class PlainDate {
constructor(isoYear: number, isoMonth: number, isoDay: number);
static compare(one: PlainDate, two: PlainDate): number;
readonly year: number;
readonly month: number;
readonly day: number;
readonly dayOfWeek: number;
readonly dayOfYear: number;
add(duration: { years?: number, months?: number, days?: number }): PlainDate;
until(other: PlainDate): Duration;
equals(other: PlainDate): boolean;
}
class Duration {
readonly days: number;
}
namespace Now {
function plainDateISO(): PlainDate;
}
}
}

export type DateOnly = Temporal.PlainDate;

export const PlainDate = Temporal.PlainDate;
export type PlainDate = Temporal.PlainDate;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will go (likewise in the other file) once we upgrade to Typescript 6 or 7.

Comment on lines +130 to +196
export function parse(str: string): DateOnly {
function fail(): DateOnly {
throw new Exception(`String '${str}' was not recognized as a valid DateOnly.`);
}

// Allowed separators: . , / -
// TODO whitespace alone as the separator
//
// Whitespace around separators
//
// Allowed format types:
// yyyy/mm/dd
// mm/dd/yyyy
// mm/dd
// mm/yyyy
// yyyy/mm
const r = /^\s*(\d{1,4})(?:\s*[.,-\/]\s*(\d{1,2}))?\s*[.,-\/]\s*(\d{1,4})\s*$/.exec(str);
if (r != null) {
let y = 0;
let m = 0;
let d = 1;

if (r[2] == null) {
if (r[1].length < 3) {
if (r[3].length < 3) {
// 12/30 = December 30, {CurrentYear}
y = Temporal.Now.plainDateISO().year;
m = +r[1];
d = +r[3];
} else {
// 12/2000 = December 1, 2000
m = +r[1];
y = +r[3];
}
} else {
if (r[3].length > 2)
fail();

// 2000/12 = December 1, 2000
y = +r[1];
m = +r[3];
}
} else {
// 2000/1/30 or 1/30/2000
const yearFirst = r[1].length > 2;
const yTmp = r[yearFirst ? 1 : 3];
y = +yTmp;

// year 0-29 is 2000-2029, 30-99 is 1930-1999
if (yTmp.length < 3)
y += y >= 30 ? 1900 : 2000;

m = +r[yearFirst ? 2 : 1];
d = +r[yearFirst ? 3 : 2];
}

if (y > 0) {
try {
return new Temporal.PlainDate(y, m, d);
} catch {
return fail();
}
}
}

return fail();
}

@kerams kerams Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This parse (likewise in the other file) is only necessary if we want to closely follow .NET's parse behaviour. We could decide to remove it and instead delegate to from, which should still be close enough and acceptable in the vast majority of cases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant