cron

package
v0.49.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 14, 2026 License: MIT Imports: 6 Imported by: 0

Documentation

Overview

Package cron 提供 Cron 表达式解析和调度功能。

支持标准 5 字段 Cron 表达式:分 时 日 月 周

⚠️ 与标准 cron 的方言差异([#186]):dow 支持 7≡Sunday;不支持 MON/JAN 名字、不支持 L/W/# 扩展;不可满足表达式(2 月 31 日)Next 返回零值 time.Time 而非报错——从系统 crontab 迁移时注意。

夏令时

Next 的返回值**保证严格晚于入参**(零值除外)。调度器最常见的消费形态

for { next := sched.Next(prev); time.Sleep(time.Until(next)); prev = next }

一旦 Next 返回不晚于入参的时刻就变成原地热转、任务被疯狂重复执行且永不 前进——所以这不是「精度」问题而是**活性**问题([#214])。内部推进全部走 绝对时间加法、不按墙钟重建,因此:

  • **回拨**(同一墙钟出现两次):两次出现都会各自被推进到,不会倒退;
  • **前跳**(某个墙钟不存在,如 America/Santiago 2026-09-06 的 00:00): 目标日/月的起点取该日**最早存在的时刻**;若表达式要求的墙钟时刻当天 确实不存在(如「仅在 9/6 00:00」),则该次不触发——与「按墙钟语义 该时刻当天不存在」一致,不会无限搜索。

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func NextSchedule

func NextSchedule(s Schedule, from time.Time, n int) []time.Time

NextSchedule 返回最近的 N 个调度时间。

n <= 0 返回空切片(不做任何调度计算)——负数此前会传给 make([]time.Time, 0, n) 直接 panic(makeslice: cap out of range)。

func RoundDuration

func RoundDuration(d time.Duration, precision time.Duration) time.Duration

RoundDuration 将时间向上取整到指定间隔。

precision <= 0:0 视为「不取整」原样返回;负值 panic(#470,对齐 mathx.toFixed 与 NewDelaySchedule 的 fail-fast 口径——负精度让 Ceil 方向反转,结果可**小于入参**,「向上取整」被静默改成向下)。

整数算术而非 float64 中转(#470):float64 对 >2^53 纳秒(约 104 天)的 duration 不可精确表示,Ceil 后结果可小于入参;整数除法/取余在 int64 域 内无精度损失。负 d 沿用 Ceil 语义(向 +∞ 取整):商为负时向零截断恰为 ceil,不进补位分支。

func SortEntries

func SortEntries(entries []Entry)

SortEntries 按下次执行时间排序(零值 Next 排最后——「尚未计算」不是 「最早」,与既有 sort.Interface 实现同口径)。

Types

type ConstantDelaySchedule

type ConstantDelaySchedule struct {
	Delay time.Duration
}

ConstantDelaySchedule 以固定时间间隔调度。

func Every

func Every(duration time.Duration) ConstantDelaySchedule

Every 返回一个以固定间隔调度的 Schedule。

⚠️ 语义三件套([#184](https://github.com/oekit/pkg/issues/184),此前零声明):

  • duration < 1s **静默钳到 1s**(本调度器秒级粒度,ConstantDelaySchedule 沿用 robfig 形态)——配 Every(500ms) 期望半秒节奏会得到 1s,调度密度 减半无任何信号;
  • 亚秒部分**截断**(1500ms ⇒ 1s);
  • Next 按传入时间的秒对齐,实际触发间隔 = delay 但不保证与「上次执行 完成」挂钩(固定时刻表,非完成后间隔;后者用 oekit/concurrency/loop 的 IntervalAfterCompletion)。

需要亚秒级高频节奏不该用 cron——直接 go-timer/loop。

func NewConstantDelaySchedule added in v0.49.0

func NewConstantDelaySchedule(d time.Duration) ConstantDelaySchedule

NewConstantDelaySchedule 创建固定间隔调度器。d <= 0 panic(#468,对齐 NewDelaySchedule #227 的 fail-fast 口径):零延迟让 Next 返回**等于**入参 的时刻,亚秒延迟在秒对齐公式下会返回**更早**时刻——都落在包级「严格晚于 入参」契约(doc.go)禁止的范围,轮询循环原地热转。亚秒级高频节奏不用 cron,直接 timer/loop(同 Every 的口径)。

func (ConstantDelaySchedule) Next

Next 返回下一个调度时间。

直构兜底(#468):ConstantDelaySchedule 是导出结构体,零值或亚秒 Delay 的 字面量绕过构造器时,秒对齐公式会产出**不晚于入参**的时刻(Delay=0 返回 入参本身;Delay=500ms 且入参纳秒位 >500ms 时返回更早时刻)——包级契约 「严格晚于入参」(doc.go)被打破,调度器原地热转。这里兜底钳制:公式 结果不晚于入参时退回「入参 + Delay」;仍不晚于(Delay<=0)时前进到下一 个整秒界。构造器路径(Every/NewConstantDelaySchedule)不会触发。

type DelaySchedule

type DelaySchedule struct {
	Delay time.Duration
}

DelaySchedule 在固定延迟后触发。

func NewDelaySchedule

func NewDelaySchedule(delay time.Duration) DelaySchedule

NewDelaySchedule 创建一个延迟调度器。

delay <= 0 panic(#227/#469):负延迟让 Next 返回**过去**时刻;零延迟让 Next 返回**等于**入参的时刻——调度器收到不晚于当前时间的触发点(自家 注释的危险判据本就包含「等于」),可能原地热转或跳过任务,是非法配置 而非可钳制的边界(对比 Every 对 <1s 的静默钳制:亚秒只是精度损失,非正 值是语义错误)。fail-fast 在构造期,不静默。

func (DelaySchedule) Next

func (d DelaySchedule) Next(t time.Time) time.Time

Next 返回当前时间加上延迟后的时间。

直构兜底(#469):Delay<=0 的字面量绕过构造器校验时返回**不晚于入参**的 时刻——退回下一整秒界,维持包级「严格晚于」契约(doc.go)。

type Entry

type Entry struct {
	Schedule Schedule
	Next     time.Time
	Prev     time.Time
	Job      func()
}

Entry 表示一个 cron 条目。

type RandomDelaySchedule

type RandomDelaySchedule struct {
	Min, Max time.Duration
}

RandomDelaySchedule 在随机延迟后触发。

func NewRandomDelaySchedule

func NewRandomDelaySchedule(min, max time.Duration) RandomDelaySchedule

NewRandomDelaySchedule 创建一个随机延迟调度器。

构造期校验 0 <= min <= max 且区间非 (0,0),非法 panic(#227/#469,fail-fast):

  • min > max:delta 为负会命中 Next 的 delta<=0 分支,随机延迟**静默 退化**为固定延迟 min——配置错误无任何信号;
  • min < 0 或 max < 0:Next 产生负值延迟 ⇒ 返回过去时刻——调度器 收到不晚于当前时间的触发点,可能原地热转或跳过任务;
  • min == max == 0:Next 恒返回入参本身(delta=0 走固定延迟 min=0), 恰落在「不晚于当前时间」的危险判据里——构造合法但契约被打破。

签名不返回 error:非法区间是调用方的常量配置错误(同 NewDelaySchedule 的 panic 口径;直接构造 RandomDelaySchedule{...} 字面量绕过校验属自担 行为,Next 保留 delta<=0 的防御分支)。

func (RandomDelaySchedule) Next

Next 返回当前时间加上 [Min, Max] 范围内随机延迟后的时间。

返回值**保证严格晚于入参**(doc.go 契约,#469):delta>0 且 min=0 时 jitter 可能抽到 0(Next 恰等于入参的唯一形态),至少前进 1ns;min == max 时确定性地返回 t + min(delta == 0 无随机空间)。

type Schedule

type Schedule interface {
	Next(time.Time) time.Time
}

Schedule 调度接口。

func Parse

func Parse(spec string) (Schedule, error)

Parse 解析 cron 表达式并返回 Schedule。 支持标准 5 字段格式:分 时 日 月 周 也支持 6 字段格式:秒 分 时 日 月 周

type SpecSchedule

type SpecSchedule struct {
	Second, Minute, Hour, Dom, Month, Dow uint64
	Location                              *time.Location
}

SpecSchedule 根据 cron 表达式调度任务。 Location 为 nil 时使用 time.Local;零值没有可匹配的月份,Next 返回零值时间。

func (*SpecSchedule) Next

func (s *SpecSchedule) Next(t time.Time) time.Time

Next 返回下一个调度时间。

⚠️ 返回值**保证严格晚于入参**(除非返回零值 time.Time 表示放弃)。调度器的 标准消费形态是 `for { next := sched.Next(prev); time.Sleep(next.Sub(now)); prev = next }`, 一旦 Next 返回不晚于入参的时刻,该循环就变成**原地热转**且永不前进——任务在 一个窗口里被疯狂重复执行,进程忙等([#214] 的失效形态)。内部各循环已全部 改成绝对时间推进(见各循环注释),这里再留一道兜底:真出现不前进时从入参 往后挪一秒重算,最多 3 次。

type WrapSchedule

type WrapSchedule struct {
	Schedule Schedule
	Delay    time.Duration
}

WrapSchedule 包装一个 Schedule,在每次触发后添加延迟。 零值不可直接使用(Schedule 为 nil 时 Next panic);经 NewWrapSchedule 创建(Delay<0 fail-fast,#471)。

func NewWrapSchedule added in v0.49.0

func NewWrapSchedule(s Schedule, d time.Duration) WrapSchedule

NewWrapSchedule 创建带延迟包装的调度器。d < 0 panic(#471,对齐 NewDelaySchedule #227 的 fail-fast 口径):负延迟让 Next 返回**过去** 时刻——即便内层 Next 严格晚于入参,减掉更大的负 Delay 后仍可 ≤t,调度 器热转或跳过。同一 package 对「负延迟」一处构造期 panic、一处裸字段 放行的两套哲学就此收敛。字面量直构绕过校验属自担行为。

func (WrapSchedule) Next

func (w WrapSchedule) Next(t time.Time) time.Time

Next 返回内层 Schedule 的下一次触发时间加 Delay。

⚠️ 零值哨兵穿透(#383):内层 Next 返回零值 time.Time(不可满足表达式 放弃,如 `0 0 31 2 *`——2 月永无 31 日)时**原样返回零值**,不加 Delay。 此前对零值直接 Add——time.Time{}.Add(delay) 是公元 1 年 + delay 的非零 具体时刻,内层的「放弃」哨兵经包装后失效:调用方 IsZero 判定落空、 time.Until 为极大负值 ⇒ 立即触发、prev 停在远古 ⇒ 内层 SpecSchedule 从 远古重扫 5 年 ⇒ 调度器热转。哨兵语义必须穿透组合子,Next 的 godoc 契约 「返回值严格晚于入参,除非返回零值表示放弃」对包装器同样成立。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL