For AI agents: the complete documentation index is available at https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/llms.txt, the full documentation bundle is available at https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/llms-full.txt, and this page is available as Markdown at https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/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 預設。模型與排程策略的組合方式見架構。