For AI agents: the complete documentation index is available at https://open-spaced-repetition.github.io/ts-fsrs/zh-CN/llms.txt, the full documentation bundle is available at https://open-spaced-repetition.github.io/ts-fsrs/zh-CN/llms-full.txt, and this page is available as Markdown at https://open-spaced-repetition.github.io/ts-fsrs/zh-CN/guide/chrono/index.md.
  • 简体中文
  • Chrono 概览

    Chrono 决定调度器如何表示时间、计算经过的天数,以及确定下次到期时间。模型使用以天为单位的间隔,Chrono 负责把这些间隔与应用中的时间值连接起来。

    选择预设

    从 ts-fsrs 导入 dateChrono、numericChrono、temporalInstantChrono 和 defineChrono。

    预设时间值天数运算
    dateChronoJavaScript Datedifference 默认计算 UTC 日期差;fractionalDays: true 时按固定 24 小时计算经过的小数天数。add 始终按固定 24 小时增加天数,支持小数。
    numericChrono以天为单位的数字difference(from, to) 为 to - from;add(from, days) 为 from + days。
    temporalInstantChronoTemporal.Instant支持配置时区,并可选择是否保留时间差的小数天数。

    各预设的详细说明: Date、Numeric、Temporal Instant。

    定义与运行时操作

    Chrono 定义包含 schema、normalize、defaultValue 和 create。将定义传给 defineScheduler({ model, chrono });需要单独进行时间运算时,调用定义的 create({ config })。调度器也通过 scheduler.chrono 暴露配置完成的运行时对象。

    Date 和 Temporal 需要向 create({ config }) 传入完整配置;只有 Numeric 等不需要配置的预设可以无参调用 create()。通过调度器创建实例时,会校验配置并补齐 schema 默认值。

    操作含义
    now()调用方省略 now 时提供时间值。
    difference(from, to)返回模型使用的经过天数。
    add(from, days)将最终的天数间隔转换为到期时间。
    compare(left, right)可选的精确时间比较,返回负数、零或正数;Date 和 Temporal 预设均提供此操作。

    normalize 的作用

    normalize 是一个 ChronoTimeNormalizer Standard Schema,输出类型为 ChronoNormalizedTime<Time>:{ previous: Time | null, current: Time }。defineChrono 既接受兼容的 schema,也接受返回 { value } 或 { issues } 的校验回调。previous: null 表示没有上一次事件。

    normalize 将 Chrono 的具体时间字段转换为 { previous, current },让调度器无需知道 card 或 revlog 的字段名,就能计算经过天数并在撤销时恢复时间字段。复习时,它接收 { card, time: now }(没有 card 字段的 Chrono 只接收 { time });撤销时,它接收 { revlog }。

    对于 Date 和 Temporal,previous 是 lastReviewAt,current 是 dueAt,而非本次复习时间 now。Numeric 不存储时间字段,返回 { previous: 0, current: time }。具体映射由 Chrono 实现决定;复习与撤销流程见 Date。

    normalize 负责读取时间表示,defaultValue 负责将调度器给出的时间值写回具体的 card 和 revlog 字段。两者共同使调度器与存储字段名解耦。

    用 defineChrono 自定义经过天数

    下面的示例保留 Date 预设的 schema、normalize、默认值和精确比较,只将经过时间改为按固定 24 小时计算的小数天数。复用这些部分,可以保留输入校验以及 card/revlog 的字段约定。如果需要改变存储字段,应一并定义对应的 schema、复习与撤销 normalize,以及默认值。

    如果只需要小数天数,内置的 dateChrono.create({ config: { fractionalDays: true } }) 已支持该行为。以下自定义示例用于演示如何通过 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 ) )

    同样的两小时时间差,UTC 日期预设得到 1,自定义 Chrono 得到 0.08333333333333333。调用 add(from, 0.5) 增加十二小时,得到 1 月 2 日 11:00 UTC。

    这个自定义 Chrono 计算固定时长,不处理当地日历天或夏令时。如果需要这些日历规则,选择 Temporal 预设。模型与调度策略的组合方式见架构。