/** * 时段判定 —— 纯函数,零依赖(只用原生 Intl + Date)。 * * 全部计算都按 profile 自带的 timeZone 进行,与用户本地时区无关。 * 灵感来源:future007s/dsh-peak-indicator(MIT)的 currentPeriod 思路, * 本实现将其从「DeepSeek 硬编码」泛化为「任意时区 + 任意窗口列表」。 */ /** 一个时段窗口,"HH:mm" 于 profile 自己的时区;start 含、end 不含。 */ export interface PeakWindow { start: string; end: string; } /** 时段规则(profile 的子集,与数据源 schedule 字段同构)。 */ export interface Schedule { /** IANA 时区名,如 "UTC" / "Asia/Shanghai"。 */ timeZone: string; /** 0=周日 … 6=周六。 */ peakDays: number[]; peakWindows: PeakWindow[]; offDayName?: string; /** * 上游标注的**法定节假日**(`YYYY-MM-DD`,按本 schedule 的时区)。 * * 语义(2026-09-30 补):当天**全天谷价** —— 依据上游价目页 * 「All other hours are off-peak, including weekends and Chinese public holidays **in full**」。 * 数据源当前只在 4 个 DeepSeek 系 profile 上带此字段(`deepseek-v4` / * `alibaba-model-studio-deepseek-v4` / `bai-deepseek-v4` / `bai-deepseek-v4-pro`), * 且这 4 个的窗口都**不跨午夜**;跨午夜的交互仍按下述规则处理。 */ publicHolidayDates?: string[]; /** 节假日的展示名(如 "Chinese public holiday");目前仅透传,UI 未消费(同 `offDayName`)。 */ publicHolidayName?: string; /** * 节假日所依据的**日历时区**(如 `deepseek-v4` 峰价按 `UTC`、节假日按 `Asia/Shanghai`)。 * * 声明它是因为数据源明确带此字段;不消费会让节假日日界按 profile 时区错判 * (当前数据窗口小时在两套解释下重合,故无实害,但边界小时会差 8 小时)。 */ publicHolidayTimeZone?: string; /** 优先级高于常规峰谷的覆盖段(活动窗口等)。 */ overrides?: ScheduleOverride[]; } /** * 时段状态。`campaign` 是**第三态**:限时活动窗口(来自 profile 的 * `schedule.overrides`,带日期区间),优先级高于常规峰谷。 */ export type Period = 'peak' | 'offPeak' | 'campaign'; /** 带日期区间与星期过滤的时段覆盖(活动/促销窗口)。 */ export interface ScheduleOverride { /** 归一化后的三态:`peak` / `offPeak` / `campaign`(一切促销类 period 都落到 `campaign`)。 */ period: Period; /** 数据源里的**原始 period 名**(如 `promotion` / `campaign10`),仅用于展示与排查。 */ periodName?: string; /** * 该活动**自己的**倍率徽章(取自数据源 `periods[].badge`)。 * * 必须有:同一 profile 里可能有多个促销态(如 zai 的 `campaign` 与 `promotion`), * 各有各的倍率 —— 只用 profile 级的单一 `campaignBadge` 槽位会显示错。 */ badge?: string; /** 该活动的显示名(取自 `periods[].name`)。 */ name?: string; /** 该活动的补充说明(取自 `periods[].detail`)。 */ detail?: string; /** 起止日期(`YYYY-MM-DD`,按 profile 自身时区,闭区间);缺省表示不限日期。 */ startDate?: string; endDate?: string; /** 0=周日 … 6=周六;空/缺省表示每天。 */ days: number[]; /** 生效窗口;全天生效写作 `{start:'00:00', end:'00:00'}`(`normalizedWindowsOf` 会展开为 1440)。 */ windows: PeakWindow[]; } export interface PeriodResult { period: Period; /** 距下一个状态翻转点的分钟数;总是 > 0。 */ minutesUntilSwitch: number; /** * 翻转后的状态 —— 让调用方能回答「之后是变贵还是变便宜」。 * * 单看 `minutesUntilSwitch` 只知道**何时**变,不知道**变成什么**: * `2× → 1×`(降价)与 `1× → 2×`(涨价)的倒计时数字没区别。 * 无下一个翻转点(如永不切换的规则)时为 undefined。 */ nextPeriod?: Period; /** * 当前生效的活动/促销的展示信息(仅在 `period === 'campaign'` 时有值)。 * * 为什么不复用 profile 级的 `campaignBadge`:同一 profile 可有多个促销态 * (如 zai 的 `campaign` 与 `promotion`),各有各的倍率 —— 必须给出**当前生效那条** * 自己的 badge/name,否则 UI 会显示另一个活动的倍率。 */ activePromo?: { periodName?: string; badge?: string; name?: string; detail?: string; }; /** * 翻转点之后生效的活动信息(仅当那一刻确实落进另一条 override 时有值)。 * * 与 `activePromo` 同理:倒计时说"多久之后变成什么倍率",那个倍率也必须是**那一刻那条活动 * 自己的**。否则两个促销态相邻时(如 zai 的 campaign 23:00-09:00 与 promotion 全天), * 倒计时会显示另一个活动的倍率。 */ nextActivePromo?: { periodName?: string; badge?: string; name?: string; detail?: string; }; } /** * 判定某时刻处于峰时还是谷时,并给出距切换的分钟数。 * * @param schedule - profile 的时段规则(自带时区)。 * @param now - 判定基准时刻。 * @returns 当前时段与距切换分钟数(总为正)。 */ export declare function currentPeriod(schedule: Schedule, now: Date): PeriodResult; /** * 格式化倒计时:<1h 用 "Xm";<24h 用 "Xh Ym";≥24h 用 "Xd Yh"。 * * @param minutes - 分钟数。 * @returns 供 UI 直接展示的字符串。 */ export declare function formatCountdown(minutes: number): string;