在运维和开发工作中,Cron 表达式无处不在——从 Linux 定时任务到 GitHub Actions 调度,再到各种云服务的定时触发器。但你有没有遇到过"*/15 9-17 * * 1-5 到底什么时候执行?"这样的困惑?
今天我们就用 OpenCode 快速实现一个命令行 Cron 表达式解析器。它能解析标准 5 字段 Cron 表达式,用中文告诉你表达式含义,并列出未来 N 次执行时间。全程用自然语言指挥 OpenCode,一行代码都不用自己写。
最终效果:
$ python cronparse.py "*/15 9-17 * * 1-5" 5 表达式: */15 9-17 * * 1-5 含义: 在分钟 0,15,...,45 时 在 9,10,...,17 点 在 1,2,...,31 号 在 1月, 2月, ..., 12月 (周一, 周二, 周三, 周四, 周五) 接下来 5 次执行时间: 2026-08-06 09:00:00 周三 2026-08-06 09:15:00 周三 2026-08-06 09:30:00 周三 2026-08-06 09:45:00 周三 2026-08-06 10:00:00 周三
npm install -g @anthropic/opencode)打开终端,进入项目目录,启动 OpenCode:
mkdir cronparse && cd cronparse opencode
不需要考虑 Cron 字段规则、不需要查 Python 日期 API——全部交给 OpenCode。我直接给出产品级的提示词:
> 我对 OpenCode 说:
>
> "帮我用 Python 写一个命令行 Cron 表达式解析器 cronparse.py。要求:
> 1. 接收一个 5 字段的标准 cron 表达式作为参数
> 2. 解析每个字段:支持 * 通配符、逗号分隔列表(1,15)、范围(9-17)、步长(*/15 或 1-10/2)
> 3. 用中文描述表达式含义,例如 '在分钟 0,15,30,45 时 在 9 到 17 点'
> 4. 从当前时间开始,计算未来 N 次执行时间(默认为 10 次)
> 5. 纯标准库实现,不用第三方依赖
> 6. 不带参数运行时显示帮助信息"
OpenCode 收到指令后,自动完成了以下工作:
定义了 5 个字段的名称、合法范围和中英文对照表
实现了 parse_field() 函数,处理 *、-、/、, 四种语法
实现了 describe() 函数,将解析结果转为中文描述
实现了 next_times() 函数,用逐分钟扫描的方式计算未来执行时间
添加了帮助信息和参数校验
关键要点:提示词要同时包含"做什么"(解析 cron)和"怎么呈现"(中文描述 + 时间列表)。越具体的需求描述,OpenCode 生成的代码就越接近预期。
> 我对 OpenCode 说:
>
> "运行 python cronparse.py '*/15 9-17 * * 1-5' 5 测试一下"
OpenCode 执行后输出:
表达式: */15 9-17 * * 1-5
含义: 在分钟 0,15,30,45 时 在 9,10,...,17 点 在 1,2,...,31 号
在 1月, 2月, ..., 12月 (周一, 周二, 周三, 周四, 周五)
接下来 5 次执行时间:
2026-08-06 09:00:00 周三
...
一次通过!解析正确,说明清晰。"工作日每 15 分钟执行一次"这个常见场景被准确翻译为了中文。
关键要点:OpenCode 的优势在于它能理解"自然语言 → 代码逻辑"的映射。当你用自然语言描述"周一至周五每 15 分钟"时,它会自动映射到 */15 9-17 * * 1-5 的解析逻辑。
初版中,当某个字段匹配所有值时(如 * 代表 1-31 号),会显示为"在 1,2,3,4,...,31 号"——全部展开太冗长。我让它做一个智能压缩:
> 我对 OpenCode 说:
>
> "优化中文描述:如果匹配值超过 3 个,只显示前 2 个和最后一个,用 '...' 省略中间部分。比如 [0,1,2,...,59] 显示为 '0,1,...,59'"
OpenCode 修改了 _compact() 函数的逻辑:
def _compact(values):
if len(values) <= 3:
return ','.join(str(v) for v in values)
return f'{values[0]},{values[1]},...,{values[-1]}'
再跑一次,输出简洁多了:
含义: 在分钟 0,15,...,45 时 在 9,10,...,17 点 在 1,2,...,31 号 …
关键要点:OpenCode 擅长"局部优化"——你只需要描述希望达到的效果,它会精确修改对应的那个函数,不会影响其他已经工作良好的部分。
作为一个命令行工具,用户可能输错参数。我让 OpenCode 加上完善的错误处理:
> 我对 OpenCode 说:
>
> "加上参数校验:如果字段数不是 5 个要报错;如果字段值超出合法范围要报错并指出是哪个字段有问题"
OpenCode 在 parse_field() 中加入了范围检查,在 main() 中加入了字段数量校验:
if start < lo or end > hi:
raise ValueError(f'值 {start}-{end} 超出范围 [{lo}, {hi}]')
现在输入错误的表达式也会得到清晰的提示:
$ python cronparse.py "60 * * * *" 解析错误: 值 60-60 超出范围 [0, 59]
以下是最终生成的完整代码(共 88 行):
#!/usr/bin/env python3
import sys
from datetime import datetime, timedelta
FIELD_NAMES = ['分钟', '小时', '日', '月份', '星期']
FIELD_RANGES = [(0, 59), (0, 23), (1, 31), (1, 12), (0, 6)]
WEEKDAY_MAP = {0: '周日', 1: '周一', 2: '周二', 3: '周三', 4: '周四', 5: '周五', 6: '周六'}
MONTH_MAP = {1: '1月', 2: '2月', 3: '3月', 4: '4月', 5: '5月', 6: '6月',
7: '7月', 8: '8月', 9: '9月', 10: '10月', 11: '11月', 12: '12月'}
def parse_field(field, lo, hi):
values = set()
for part in field.split(','):
step = 1
if '/' in part:
part, step = part.split('/')
step = int(step)
if '-' in part:
start, end = part.split('-')
start, end = int(start), int(end)
elif part == '*':
start, end = lo, hi
else:
start = end = int(part)
if start < lo or end > hi:
raise ValueError(f'值 {start}-{end} 超出范围 [{lo}, {hi}]')
values.update(range(start, end + 1, step))
return sorted(values)
def describe(fields):
minute_vals, hour_vals, day_vals, month_vals, weekday_vals = fields
parts = []
if len(minute_vals) == 60:
parts.append('每分钟')
else:
parts.append(f"在分钟 {_compact(minute_vals)} 时")
if len(hour_vals) == 24:
parts.append('每小时')
else:
parts.append(f"在 {_compact(hour_vals)} 点")
parts.append(f"在 {_compact(day_vals)} 号")
parts.append(f"在 {', '.join(MONTH_MAP[m] for m in month_vals)}")
if len(weekday_vals) < 7:
parts.append(f"({', '.join(WEEKDAY_MAP[w] for w in weekday_vals)})")
return ' '.join(parts)
def _compact(values):
if len(values) <= 3:
return ','.join(str(v) for v in values)
return f'{values[0]},{values[1]},...,{values[-1]}'
def next_times(fields, count=10):
minute_vals, hour_vals, day_vals, month_vals, weekday_vals = fields
now = datetime.now().replace(second=0, microsecond=0) + timedelta(minutes=1)
results = []
while len(results) < count:
if (now.minute in minute_vals and now.hour in hour_vals
and now.day in day_vals and now.month in month_vals
and now.weekday() in weekday_vals):
results.append(now)
now += timedelta(minutes=1)
return results
def main():
if len(sys.argv) < 2:
print('用法: python cronparse.py "<cron表达式>" [次数]')
print('示例: python cronparse.py "*/15 9-17 * * 1-5" 5')
print()
print('字段说明: 分钟(0-59) 小时(0-23) 日(1-31) 月份(1-12) 星期(0-6)')
sys.exit(0)
expr = sys.argv[1]
parts = expr.strip().split()
if len(parts) != 5:
print(f'错误: cron 表达式需要 5 个字段,当前提供了 {len(parts)} 个')
sys.exit(1)
try:
fields = [parse_field(p, *r) for p, r in zip(parts, FIELD_RANGES)]
except ValueError as e:
print(f'解析错误: {e}')
sys.exit(1)
print(f'表达式: {expr}')
print(f'含义: {describe(fields)}')
print()
count = int(sys.argv[2]) if len(sys.argv) > 2 else 10
times = next_times(fields, count)
print(f'接下来 {count} 次执行时间:')
for t in times:
print(f' {t.strftime("%Y-%m-%d %H:%M:%S")} {WEEKDAY_MAP[t.weekday()]}')
if __name__ == '__main__':
main()
这次实践一共用了 4 次自然语言对话、约 4 分钟,完成了从零到可用的 Cron 表达式解析器。
对比一下传统开发方式:
用 OpenCode 的最大感受是思维负担的转移。你不必关心 range() 的起始偏移、datetime 的 weekday 对应关系、字符串拆分的优先级——你只需用自然语言定义"输入什么、输出什么",OpenCode 会完成从语义到代码的翻译。
而当你发现某个细节不满意时(比如"列表显示太冗长"),只需一句话就能精准修改,不用在近百行代码中定位那段逻辑。
效率提升:从 20 分钟手写 → 4 分钟对话,约 5 倍加速。