| 149 | * - week :每周一次 |
| 150 | */ |
| 151 | export const nextTimeInfo = (crontab: string, date = new Date()): NextTimeResult => { |
| 152 | const { cronExpr, oncePos } = extractCronExpr(crontab); |
| 153 | |
| 154 | let cron: CronTime; |
| 155 | try { |
| 156 | // 使用标准 cron 表达式进行解析 |
| 157 | cron = new CronTime(cronExpr); |
| 158 | } catch { |
| 159 | /** |
| 160 | * 不支持多个 once |
| 161 | * 示例:"* once once * *" |
| 162 | */ |
| 163 | throw new Error(t("cron_invalid_expr")); |
| 164 | } |
| 165 | |
| 166 | let luxonDate = (DateTime as any).fromJSDate(date) as LuxonDate; |
| 167 | let format = "yyyy-MM-dd HH:mm:ss"; |
| 168 | let onceLabel = ""; |
| 169 | |
| 170 | /** |
| 171 | * 若存在 once: |
| 172 | * |
| 173 | * 处理思路: |
| 174 | * 1. 先跳转到下一个周期的起始时间 |
| 175 | * 2. 再从该时间点开始计算 cron 的下一次命中 |
| 176 | */ |
| 177 | if (oncePos >= 1 && oncePos <= 5) { |
| 178 | const cfg = ONCE_MAP[oncePos as keyof typeof ONCE_MAP]; |
| 179 | onceLabel = cfg.label; |
| 180 | format = cfg.format; |
| 181 | |
| 182 | /** |
| 183 | * 示例: |
| 184 | * 当前时间:2026-01-02 10:23 |
| 185 | * once 位于 hour |
| 186 | * |
| 187 | * → 跳转到 11:00:00 |
| 188 | */ |
| 189 | luxonDate = luxonDate.plus({ [cfg.unit]: 1 }).startOf(cfg.unit as any); |
| 190 | |
| 191 | /** |
| 192 | * 再回退 1 毫秒, |
| 193 | * 以确保 getNextDateFrom 能命中 |
| 194 | * 「等于周期起点」的 cron 时间 |
| 195 | */ |
| 196 | luxonDate = luxonDate.minus({ milliseconds: 1 }); |
| 197 | } |
| 198 | |
| 199 | const next = cron.getNextDateFrom(luxonDate); |
| 200 | |
| 201 | return { |
| 202 | next: next, |
| 203 | format: format, |
| 204 | once: onceLabel, |
| 205 | }; |
| 206 | }; |