For AI agents: the complete documentation index is available at https://open-spaced-repetition.github.io/ts-fsrs/llms.txt, the full documentation bundle is available at https://open-spaced-repetition.github.io/ts-fsrs/llms-full.txt, and this page is available as Markdown at https://open-spaced-repetition.github.io/ts-fsrs/guide/chrono/index.md.

Chrono overview

Chrono defines how a scheduler represents time, measures elapsed days, and calculates the next due time. The model works with intervals in days; Chrono connects those intervals to your application's time values.

Choose a preset

Import dateChrono, numericChrono, temporalInstantChrono, and defineChrono from ts-fsrs.

PresetTime valuesDay arithmetic
dateChronoJavaScript Datedifference counts UTC date boundaries by default; with fractionalDays: true, it measures elapsed time in fixed 24-hour days. add always adds fixed 24-hour days, including fractions.
numericChronoNumbers in daysdifference(from, to) is to - from; add(from, days) is from + days.
temporalInstantChronoTemporal.InstantSupports a configured timezone and optional fractional day differences.

See the preset guides for details: Date, Numeric, and Temporal Instant.

Definition and runtime operations

A Chrono definition contains schema, normalize, defaultValue, and create. Pass the definition to defineScheduler({ model, chrono }); call its create({ config }) when you need the time operations directly. The scheduler also exposes its configured runtime as scheduler.chrono.

For Date and Temporal, pass the complete configuration to create({ config }); only presets without configuration, such as Numeric, support create() without arguments. Scheduler creation validates configuration and supplies schema defaults.

OperationMeaning
now()Supplies a time value when the caller omits now.
difference(from, to)Returns elapsed days for the model.
add(from, days)Converts the final interval in days into a due time.
compare(left, right)Optional exact ordering: negative, zero, or positive. Date and Temporal presets provide it.

What normalize does

normalize is a ChronoTimeNormalizer Standard Schema whose output is ChronoNormalizedTime<Time>: { previous: Time | null, current: Time }. defineChrono accepts either a compatible schema or a validation callback returning { value } or { issues }. previous: null means there is no previous event.

normalize translates a Chrono's concrete time fields into { previous, current }, so the scheduler can calculate elapsed days and restore time fields during rollback without knowing the card or revlog field names. During review it receives { card, time: now } (or only { time } for a Chrono without card fields); during rollback it receives { revlog }.

For Date and Temporal, previous is lastReviewAt and current is dueAt, not the current review time now. Numeric has no stored time fields and returns { previous: 0, current: time }. The mapping belongs to the Chrono implementation; see Date for the review and rollback flow.

normalize reads the time representation; defaultValue writes the scheduler's time values back into concrete card and revlog fields. Together they keep the scheduler independent of the stored field names.

Customize elapsed days with defineChrono

This example keeps the Date preset's schemas, time normalizer, defaults, and precise comparison, but measures elapsed time in fixed 24-hour days with fractions. Reusing those parts preserves input validation and the card/revlog contract. If you change the stored fields, define matching schemas, review and rollback normalizers, and defaults together.

For fractional elapsed days, the built-in dateChrono.create({ config: { fractionalDays: true } }) already provides this behavior. The following custom definition demonstrates how to override an operation with defineChrono.

import { 
const dateChrono: Chrono<{
    readonly time: SRSSchema<{
        input: Date;
        output: Date;
    }>;
    readonly config: SRSSchema<{
        input: {
            readonly fractionalDays?: boolean | undefined;
        };
        output: {
            readonly fractionalDays: boolean;
        };
    }>;
    readonly fields: {
        readonly card: SRSSchema<{
            input: {
                dueAt: Date;
                lastReviewAt?: (Date | null) | undefined;
            };
            output: {
                dueAt: Date;
                lastReviewAt: Date | null;
            };
        }>;
        readonly revlog: SRSSchema<{
            input: {
                dueAt: Date;
                lastReviewAt?: (Date | null) | undefined;
                reviewTime: Date;
            };
            output: {
                dueAt: Date;
                lastReviewAt: Date | null;
                reviewTime: Date;
            };
        }>;
    };
}>
dateChrono
,
function defineChrono<const Schema extends AnyChronoSchema>(definition: ChronoDefinition<Schema>): Chrono<ChronoDefinitionEnv<Schema>>
defineChrono
} from 'ts-fsrs'
const millisecondsPerDay = 86_400_000 // Keep Date validation, card/revlog fields, and time normalization from the preset. const
const fractionalDateChrono: Chrono<ChronoDefinitionEnv<{
    readonly time: SRSSchema<{
        input: Date;
        output: Date;
    }>;
} & {
    readonly config: SRSSchema<{
        input: {
            readonly fractionalDays?: boolean | undefined;
        };
        output: {
            readonly fractionalDays: boolean;
        };
    }>;
} & {
    readonly card: SRSSchema<{
        input: {
            dueAt: Date;
            lastReviewAt?: (Date | null) | undefined;
        };
        output: {
            dueAt: Date;
            lastReviewAt: Date | null;
        };
    }>;
} & {
    readonly revlog: SRSSchema<{
        input: {
            dueAt: Date;
            lastReviewAt?: (Date | null) | undefined;
            reviewTime: Date;
        };
        output: {
            dueAt: Date;
            lastReviewAt: Date | null;
            reviewTime: Date;
        };
    }>;
}>>
fractionalDateChrono
=
defineChrono<{
    readonly time: SRSSchema<{
        input: Date;
        output: Date;
    }>;
} & {
    readonly config: SRSSchema<{
        input: {
            readonly fractionalDays?: boolean | undefined;
        };
        output: {
            readonly fractionalDays: boolean;
        };
    }>;
} & {
    readonly card: SRSSchema<{
        input: {
            dueAt: Date;
            lastReviewAt?: (Date | null) | undefined;
        };
        output: {
            dueAt: Date;
            lastReviewAt: Date | null;
        };
    }>;
} & {
    readonly revlog: SRSSchema<{
        input: {
            dueAt: Date;
            lastReviewAt?: (Date | null) | undefined;
            reviewTime: Date;
        };
        output: {
            dueAt: Date;
            lastReviewAt: Date | null;
            reviewTime: Date;
        };
    }>;
}>(definition: ChronoDefinition<...>): Chrono<...>
defineChrono
({
schema: {
    readonly time: SRSSchema<{
        input: Date;
        output: Date;
    }>;
} & {
    readonly config: SRSSchema<{
        input: {
            readonly fractionalDays?: boolean | undefined;
        };
        output: {
            readonly fractionalDays: boolean;
        };
    }>;
} & {
    readonly card: SRSSchema<{
        input: {
            dueAt: Date;
            lastReviewAt?: (Date | null) | undefined;
        };
        output: {
            dueAt: Date;
            lastReviewAt: Date | null;
        };
    }>;
} & {
    readonly revlog: SRSSchema<{
        input: {
            dueAt: Date;
            lastReviewAt?: (Date | null) | undefined;
            reviewTime: Date;
        };
        output: {
            dueAt: Date;
            lastReviewAt: Date | null;
            reviewTime: Date;
        };
    }>;
}
schema
:
const dateChrono: Chrono<{
    readonly time: SRSSchema<{
        input: Date;
        output: Date;
    }>;
    readonly config: SRSSchema<{
        input: {
            readonly fractionalDays?: boolean | undefined;
        };
        output: {
            readonly fractionalDays: boolean;
        };
    }>;
    readonly fields: {
        readonly card: SRSSchema<{
            input: {
                dueAt: Date;
                lastReviewAt?: (Date | null) | undefined;
            };
            output: {
                dueAt: Date;
                lastReviewAt: Date | null;
            };
        }>;
        readonly revlog: SRSSchema<{
            input: {
                dueAt: Date;
                lastReviewAt?: (Date | null) | undefined;
                reviewTime: Date;
            };
            output: {
                dueAt: Date;
                lastReviewAt: Date | null;
                reviewTime: Date;
            };
        }>;
    };
}>
dateChrono
.
Chrono<{ readonly time: SRSSchema<{ input: Date; output: Date; }>; readonly config: SRSSchema<{ input: { readonly fractionalDays?: boolean | undefined; }; output: { readonly fractionalDays: boolean; }; }>; readonly fields: { readonly card: SRSSchema<{ input: { dueAt: Date; lastReviewAt?: (Date | null) | undefined; }; output: { dueAt: Date; lastReviewAt: Date | null; }; }>; readonly revlog: SRSSchema<{ input: { dueAt: Date; lastReviewAt?: (Date | null) | undefined; reviewTime: Date; }; output: { dueAt: Date; lastReviewAt: Date | null; reviewTime: Date; }; }>; }; }>.schema: {
    readonly time: SRSSchema<{
        input: Date;
        output: Date;
    }>;
} & {
    readonly config: SRSSchema<{
        input: {
            readonly fractionalDays?: boolean | undefined;
        };
        output: {
            readonly fractionalDays: boolean;
        };
    }>;
} & {
    readonly card: SRSSchema<{
        input: {
            dueAt: Date;
            lastReviewAt?: (Date | null) | undefined;
        };
        output: {
            dueAt: Date;
            lastReviewAt: Date | null;
        };
    }>;
} & {
    readonly revlog: SRSSchema<{
        input: {
            dueAt: Date;
            lastReviewAt?: (Date | null) | undefined;
            reviewTime: Date;
        };
        output: {
            dueAt: Date;
            lastReviewAt: Date | null;
            reviewTime: Date;
        };
    }>;
}
schema
,
normalize: ChronoTimeNormalizerDefinition<SRSSchema<{
    input: Date;
    output: Date;
}>, SRSSchema<{
    input: {
        dueAt: Date;
        lastReviewAt?: (Date | null) | undefined;
    };
    output: {
        dueAt: Date;
        lastReviewAt: Date | null;
    };
}>, SRSSchema<{
    input: {
        dueAt: Date;
        lastReviewAt?: (Date | null) | undefined;
        reviewTime: Date;
    };
    output: {
        dueAt: Date;
        lastReviewAt: Date | null;
        reviewTime: Date;
    };
}>>
normalize
:
const dateChrono: Chrono<{
    readonly time: SRSSchema<{
        input: Date;
        output: Date;
    }>;
    readonly config: SRSSchema<{
        input: {
            readonly fractionalDays?: boolean | undefined;
        };
        output: {
            readonly fractionalDays: boolean;
        };
    }>;
    readonly fields: {
        readonly card: SRSSchema<{
            input: {
                dueAt: Date;
                lastReviewAt?: (Date | null) | undefined;
            };
            output: {
                dueAt: Date;
                lastReviewAt: Date | null;
            };
        }>;
        readonly revlog: SRSSchema<{
            input: {
                dueAt: Date;
                lastReviewAt?: (Date | null) | undefined;
                reviewTime: Date;
            };
            output: {
                dueAt: Date;
                lastReviewAt: Date | null;
                reviewTime: Date;
            };
        }>;
    };
}>
dateChrono
.
Chrono<{ readonly time: SRSSchema<{ input: Date; output: Date; }>; readonly config: SRSSchema<{ input: { readonly fractionalDays?: boolean | undefined; }; output: { readonly fractionalDays: boolean; }; }>; readonly fields: { readonly card: SRSSchema<{ input: { dueAt: Date; lastReviewAt?: (Date | null) | undefined; }; output: { dueAt: Date; lastReviewAt: Date | null; }; }>; readonly revlog: SRSSchema<{ input: { dueAt: Date; lastReviewAt?: (Date | null) | undefined; reviewTime: Date; }; output: { dueAt: Date; lastReviewAt: Date | null; reviewTime: Date; }; }>; }; }>.normalize: ChronoTimeNormalizer<{
    readonly time: SRSSchema<{
        input: Date;
        output: Date;
    }>;
    readonly config: SRSSchema<{
        input: {
            readonly fractionalDays?: boolean | undefined;
        };
        output: {
            readonly fractionalDays: boolean;
        };
    }>;
    readonly fields: {
        readonly card: SRSSchema<{
            input: {
                dueAt: Date;
                lastReviewAt?: (Date | null) | undefined;
            };
            output: {
                dueAt: Date;
                lastReviewAt: Date | null;
            };
        }>;
        readonly revlog: SRSSchema<{
            input: {
                dueAt: Date;
                lastReviewAt?: (Date | null) | undefined;
                reviewTime: Date;
            };
            output: {
                dueAt: Date;
                lastReviewAt: Date | null;
                reviewTime: Date;
            };
        }>;
    };
}>
normalize
,
defaultValue?: Readonly<{
    readonly card?: FieldDefault<SRSSchema<{
        input: {
            dueAt: Date;
            lastReviewAt?: (Date | null) | undefined;
        };
        output: {
            dueAt: Date;
            lastReviewAt: Date | null;
        };
    }>, ChronoDefaultCtx<{
        readonly fractionalDays: boolean;
    }, Date, "card">> | undefined;
} & {
    readonly revlog?: FieldDefault<SRSSchema<{
        input: {
            dueAt: Date;
            lastReviewAt?: (Date | null) | undefined;
            reviewTime: Date;
        };
        output: {
            dueAt: Date;
            lastReviewAt: Date | null;
            reviewTime: Date;
        };
    }>, ChronoDefaultCtx<...>> | undefined;
}> | undefined
defaultValue
:
const dateChrono: Chrono<{
    readonly time: SRSSchema<{
        input: Date;
        output: Date;
    }>;
    readonly config: SRSSchema<{
        input: {
            readonly fractionalDays?: boolean | undefined;
        };
        output: {
            readonly fractionalDays: boolean;
        };
    }>;
    readonly fields: {
        readonly card: SRSSchema<{
            input: {
                dueAt: Date;
                lastReviewAt?: (Date | null) | undefined;
            };
            output: {
                dueAt: Date;
                lastReviewAt: Date | null;
            };
        }>;
        readonly revlog: SRSSchema<{
            input: {
                dueAt: Date;
                lastReviewAt?: (Date | null) | undefined;
                reviewTime: Date;
            };
            output: {
                dueAt: Date;
                lastReviewAt: Date | null;
                reviewTime: Date;
            };
        }>;
    };
}>
dateChrono
.
Chrono<{ readonly time: SRSSchema<{ input: Date; output: Date; }>; readonly config: SRSSchema<{ input: { readonly fractionalDays?: boolean | undefined; }; output: { readonly fractionalDays: boolean; }; }>; readonly fields: { readonly card: SRSSchema<{ input: { dueAt: Date; lastReviewAt?: (Date | null) | undefined; }; output: { dueAt: Date; lastReviewAt: Date | null; }; }>; readonly revlog: SRSSchema<{ input: { dueAt: Date; lastReviewAt?: (Date | null) | undefined; reviewTime: Date; }; output: { dueAt: Date; lastReviewAt: Date | null; reviewTime: Date; }; }>; }; }>.defaultValue: Readonly<{
    readonly card?: FieldDefault<SRSSchema<{
        input: {
            dueAt: Date;
            lastReviewAt?: (Date | null) | undefined;
        };
        output: {
            dueAt: Date;
            lastReviewAt: Date | null;
        };
    }>, ChronoDefaultCtx<{
        readonly fractionalDays: boolean;
    }, Date, "card">> | undefined;
} & {
    readonly revlog?: FieldDefault<SRSSchema<{
        input: {
            dueAt: Date;
            lastReviewAt?: (Date | null) | undefined;
            reviewTime: Date;
        };
        output: {
            dueAt: Date;
            lastReviewAt: Date | null;
            reviewTime: Date;
        };
    }>, ChronoDefaultCtx<...>> | undefined;
}>
defaultValue
,
create: (ctx: {
    readonly config: {
        readonly fractionalDays: boolean;
    };
}) => ChronoCore<Date>
create
(ctx) {
return { ...
const dateChrono: Chrono<{
    readonly time: SRSSchema<{
        input: Date;
        output: Date;
    }>;
    readonly config: SRSSchema<{
        input: {
            readonly fractionalDays?: boolean | undefined;
        };
        output: {
            readonly fractionalDays: boolean;
        };
    }>;
    readonly fields: {
        readonly card: SRSSchema<{
            input: {
                dueAt: Date;
                lastReviewAt?: (Date | null) | undefined;
            };
            output: {
                dueAt: Date;
                lastReviewAt: Date | null;
            };
        }>;
        readonly revlog: SRSSchema<{
            input: {
                dueAt: Date;
                lastReviewAt?: (Date | null) | undefined;
                reviewTime: Date;
            };
            output: {
                dueAt: Date;
                lastReviewAt: Date | null;
                reviewTime: Date;
            };
        }>;
    };
}>
dateChrono
.
Chrono<{ readonly time: SRSSchema<{ input: Date; output: Date; }>; readonly config: SRSSchema<{ input: { readonly fractionalDays?: boolean | undefined; }; output: { readonly fractionalDays: boolean; }; }>; readonly fields: { readonly card: SRSSchema<{ input: { dueAt: Date; lastReviewAt?: (Date | null) | undefined; }; output: { dueAt: Date; lastReviewAt: Date | null; }; }>; readonly revlog: SRSSchema<{ input: { dueAt: Date; lastReviewAt?: (Date | null) | undefined; reviewTime: Date; }; output: { dueAt: Date; lastReviewAt: Date | null; reviewTime: Date; }; }>; }; }>.create: (ctx: {
    readonly config: {
        readonly fractionalDays: boolean;
    };
}) => ChronoCore<Date>
create
(ctx),
ChronoCore<Date>.difference: (from: Date, to: Date) => number

Returns the distance from the previous review time to the current time.

difference
: (from, to) =>
(to.getTime() - from.getTime()) / millisecondsPerDay, } }, }) const from = new Date('2026-01-01T23:00:00.000Z') const now = new Date('2026-01-02T01:00:00.000Z') const
const clock: ChronoCore<Date>
clock
=
const fractionalDateChrono: Chrono<ChronoDefinitionEnv<{
    readonly time: SRSSchema<{
        input: Date;
        output: Date;
    }>;
} & {
    readonly config: SRSSchema<{
        input: {
            readonly fractionalDays?: boolean | undefined;
        };
        output: {
            readonly fractionalDays: boolean;
        };
    }>;
} & {
    readonly card: SRSSchema<{
        input: {
            dueAt: Date;
            lastReviewAt?: (Date | null) | undefined;
        };
        output: {
            dueAt: Date;
            lastReviewAt: Date | null;
        };
    }>;
} & {
    readonly revlog: SRSSchema<{
        input: {
            dueAt: Date;
            lastReviewAt?: (Date | null) | undefined;
            reviewTime: Date;
        };
        output: {
            dueAt: Date;
            lastReviewAt: Date | null;
            reviewTime: Date;
        };
    }>;
}>>
fractionalDateChrono
.
Chrono<ChronoDefinitionEnv<{ readonly time: SRSSchema<{ input: Date; output: Date; }>; } & { readonly config: SRSSchema<{ input: { readonly fractionalDays?: boolean | undefined; }; output: { readonly fractionalDays: boolean; }; }>; } & { ...; } & { ...; }>>.create: (ctx: {
    readonly config: {
        readonly fractionalDays: boolean;
    };
}) => ChronoCore<Date>
create
({ config: { fractionalDays: false } })
console.log( JSON.stringify( { utcDateDifference:
const dateChrono: Chrono<{
    readonly time: SRSSchema<{
        input: Date;
        output: Date;
    }>;
    readonly config: SRSSchema<{
        input: {
            readonly fractionalDays?: boolean | undefined;
        };
        output: {
            readonly fractionalDays: boolean;
        };
    }>;
    readonly fields: {
        readonly card: SRSSchema<{
            input: {
                dueAt: Date;
                lastReviewAt?: (Date | null) | undefined;
            };
            output: {
                dueAt: Date;
                lastReviewAt: Date | null;
            };
        }>;
        readonly revlog: SRSSchema<{
            input: {
                dueAt: Date;
                lastReviewAt?: (Date | null) | undefined;
                reviewTime: Date;
            };
            output: {
                dueAt: Date;
                lastReviewAt: Date | null;
                reviewTime: Date;
            };
        }>;
    };
}>
dateChrono
.
Chrono<{ readonly time: SRSSchema<{ input: Date; output: Date; }>; readonly config: SRSSchema<{ input: { readonly fractionalDays?: boolean | undefined; }; output: { readonly fractionalDays: boolean; }; }>; readonly fields: { readonly card: SRSSchema<{ input: { dueAt: Date; lastReviewAt?: (Date | null) | undefined; }; output: { dueAt: Date; lastReviewAt: Date | null; }; }>; readonly revlog: SRSSchema<{ input: { dueAt: Date; lastReviewAt?: (Date | null) | undefined; reviewTime: Date; }; output: { dueAt: Date; lastReviewAt: Date | null; reviewTime: Date; }; }>; }; }>.create: (ctx: {
    readonly config: {
        readonly fractionalDays: boolean;
    };
}) => ChronoCore<Date>
create
({ config: { fractionalDays: false } })
.
ChronoCore<Date>.difference: (from: Date, to: Date) => number

Returns the distance from the previous review time to the current time.

difference
(from, now),
fractionalDifference:
const clock: ChronoCore<Date>
clock
.
ChronoCore<Date>.difference: (from: Date, to: Date) => number

Returns the distance from the previous review time to the current time.

difference
(from, now),
halfDayLater:
const clock: ChronoCore<Date>
clock
.
ChronoCore<Date>.add: (from: Date, days: number) => Date
add
(from, 0.5).toISOString(),
}, null, 2 ) )

The two-hour gap yields 1 with the UTC-date preset and 0.08333333333333333 with the custom Chrono. Calling add(from, 0.5) adds twelve hours, giving January 2 at 11:00 UTC.

This custom Chrono measures fixed durations; it does not account for local calendar days or daylight saving. Choose the Temporal preset when those calendar rules are needed. See Architecture for choosing the model and scheduling policies.