AI活用

cron写不出"每月第1个星期六"?同时指定日期和星期会变成OR的陷阱

cron写不出"每月第1个星期六"?同时指定日期和星期会变成OR的陷阱

早上好,我是まさきん。

家里的提醒和AI定时任务,越来越多地用cron格式来设置了。其中在尝试写”每月第1个星期六”时,我才发现自己结结实实地踩了一个坑。用同样写法的人可能意外地不少,所以把规范和规避方法整理一下。

※本文为发布时的信息。最新条件请以官方网站为准。

POINT
  • 在cron中同时指定日期和星期,判断方式不是AND,而是OR
  • 所以”0 9 1-7 * 6”不是第1个星期六,而是1〜7日的每一天加上每个星期六都会执行
  • 规避方法是只用星期来触发,日期的判断交给脚本
  • 部分运行环境有表示”第几个星期几”的扩展语法,但能否使用取决于环境
  • 写完之后把接下来几次的执行日期列出来验算,并确认和UTC之间的时差

本想每月一次,结果月初每天都触发

起因是月初星期六发送的采购提醒。每月只要提醒一次就行,所以我是这样写的。

0 9 1-7 * 6

“1〜7日”并且”星期六”,应该就只会锁定每月第一个星期六这一天。按人的直觉,确实会这么理解吧。

但实际的行为并非如此。它变成了每月1〜7日的每一天、再加上每周的星期六都会触发。本想每月一次,结果成了月初前半段每天早上都会收到的提醒。

回顾一下5个字段

cron格式由空格分隔的5个字段组成。从左到右依次是下面的含义。

分 时 日 月 星期
0  9  1-7 *  6

”*“表示”任意”。也可以写”1-7”这样的范围,或”1,15”这样的列举。星期在很多环境中0是星期日,6是星期六。

到这里为止机制都很直观。问题出在”日”和”星期”这两个字段之间的关系上。

日期和星期按OR判断

POSIX的crontab规范中写有如下意思的内容,说的是日字段和星期字段都被具体指定的情况:此时,只要匹配其中任一个,那一天就成为执行对象。

Linux上常用的cronie的man页面也有同样的说明:当两个字段都不是”*“时,只要其中一个匹配就会执行。并举例说明,“30 4 1,15 * 5”会在每月1日和15日,再加上每周五的4点30分执行。

也就是说,一旦同时写了日期和星期,就不是”并且”而是”或者”了。个人觉得,这是cron里最反直觉的地方。

注意
  • 在日字段和星期字段都写了”*“以外的内容时,只要匹配其中一个就会执行
  • 即使本意是”1-7日并且星期六”,实际也会变成”1-7日或者星期六”
  • 只要其中一个是”*“,就只按另一个判断,不会出问题

实际列出来看看怎么执行

以2026年10月为例数一数。把”0 9 1-7 * 6”会执行的日期列出来,结果如下。

写的表达式意图2026年10月执行的日期次数
0 9 1-7 * 6只在第1个星期六1〜7日每天,以及10·17·24·31日11次
0 9 * * 6每个星期六3·10·17·24·31日5次
0 9 * * 6 + 日期判断只在第1个星期六3日1次

本意是每月一次,算下来却会收到11次。1〜7日中的星期六是重叠的,所以比简单相加要少一点。

提醒嘛,收到通知是理所当然的事。所以即使次数太多,可能也不太容易察觉到不对劲。

主要运行环境中的处理

cron格式在不同的运行环境中会有些许差异。我把在官方文档中能确认的内容整理成了表格。

运行环境星期的计数方式同时指定日期和星期扩展语法时间基准
POSIX的crontab0〜6(0为星期日)匹配任一即执行规范中没有规范中未明确
Linux的cronie0〜7(0和7为星期日)匹配任一即执行未明确可用CRON_TZ指定
Cloudflare Workers的Cron Triggers1〜7(1为星期日)未明确支持L·W·#UTC
GitHub Actions的schedule0〜6(0为星期日)写明使用POSIX格式不支持@daily等默认UTC,也可指定timezone

需要注意的是Cloudflare的星期。1是星期日、7是星期六,和其他环境错开了1位。官方也因为容易混淆,推荐使用SAT这样的3字母缩写。

GitHub Actions写明使用POSIX的cron格式,但页面上没有写日期和星期之间的关系。所以按照和POSIX一样是OR的前提来设计,应该比较稳妥。

规避方法是”按星期触发,再看日期”

最可靠的规避方法,是把其中一个字段设为”*“。只用星期来触发,日期的判断放在脚本里做。

  1. cron写成”0 9 * * 6”,每周六9点触发
  2. 触发后,获取今天的日期
  3. 如果日期是1〜7日,就发送提醒
  4. 如果是8日以后,什么都不做直接结束

第1个星期六一定落在1〜7日之间。所以只要判断”星期六并且日期在7以下”,就只会每月执行一次。相当于AND条件不交给cron,而是自己在代码里写。

这个思路可以举一反三。第2个星期六就是8〜14日,第3个星期六就是15〜21日。月末最后一个星期六,则用”7天后是否已到下个月”来判断。

用Shell写的例子

如果直接写在Linux的crontab里,例如可以写成下面这样。

0 9 * * 6 [ "$(date +\%d)" -le 7 ] && /path/to/remind.sh

这里的关键是”%“前面加了反斜杠。cronie的man页面写到,命令中的”%“会被当作换行处理。不转义的话,命令就会以意想不到的方式被截断。

如果一行太长,把判断整个放进脚本里可能更易读。crontab那边只保留”每周六触发”,以后回头看也不容易迷糊。

用在AI定时任务里的情况

把定时任务交给AI时,思路也一样。日程设为”每周六9点”,然后在指令开头写上”如果今天不是1〜7日,就什么都不做直接结束”。

如果用Python判断,例如可以这样写。

from datetime import datetime
from zoneinfo import ZoneInfo

today = datetime.now(ZoneInfo("Asia/Tokyo"))
if today.day > 7:
    raise SystemExit(0)  # 不是第1个星期六,什么都不做

这里明确指定日本时间很重要。运行环境的时钟很多时候是UTC,这样可以避免把日期弄错。

CHECK
  • cron这边一定要把其中一个字段设为”*”
  • 第几个星期几的判断,写在脚本或指令那一侧
  • 获取日期时要明确指定时区

也有能用扩展语法的环境

有些运行环境提供了扩展,可以用一个表达式写出”第几个星期几”。Cloudflare Workers的Cron Triggers在表格中列出了星期字段可以使用”#“。说明是支持接近Quartz这个调度器的扩展。

不过,官方页面上并没有给出使用”#“的具体示例。Cloudflare的星期数字从1开始,写法上尤其要小心。如果要用,最好在部署前确认它会在预想的日期执行。

另一方面,POSIX规范和cronie的man页面里都没有出现”#“。GitHub Actions的表格里也没有这个符号。考虑到迁移环境后可能无法运行,把日期判断放到代码里的方法或许更便于移植。

方法写起来的难度迁移环境时适合的场景
按星期触发并判断日期代码会稍微多一点通常可以直接运行家庭自动化的各种场景
”#“等扩展语法一行就能写完不支持的环境无法使用只在明确支持的环境中使用
同时写日期和星期看起来简单在哪里都容易变成OR最好不要用

列出下次执行日期来验算

写好cron表达式后,推荐把接下来几次的执行时间列出来确认一下。这次的坑,只要列出接下来几次,一眼就能看出来。

也有像crontab.guru这样,能把表达式转换成自然语言的网站。GitHub Actions的官方文档中,也把它介绍为可用于编写格式和确认执行时间的工具。不过它是第三方工具,显示结果不一定和运行环境的实际行为完全一致。扩展语法的处理等,最好结合各环境的官方文档一起看。

自己确认的话,把一个月的日期循环一遍、数出符合条件的日子就足够了。本文10月的表格,也是把日期一天天列出来数的。几行代码就能搞定,不妨养成每次修改表达式都跑一遍的习惯。

也要注意UTC时差

另一个陷阱是时区。Cloudflare的Cron Triggers明确写着按UTC运行。GitHub Actions默认也是UTC,同时提供了指定timezone的方法。

日本时间比UTC快9小时。所以日本时间早上9点,在UTC正好是0点。比这更早的时间,情况就不一样了。例如日本时间星期六8点,在UTC是星期五23点。

这种情况下,cron的星期字段就需要写星期五而不是星期六。而且脚本里的日期判断,如果不用日本时间来做也会出现偏差。最好记住:星期和日期都有可能偏差1天。

家庭财务自动化中容易踩坑的场景

我感觉,这个坑在设置每月一次的提醒时特别容易踩。比如下面这些场景。

这些都很容易变成”第几周的星期几”这种形式。通知太多的话,人会慢慢开始无视它们。如果连重要的账单确认通知都开始一扫而过,自动化的意义可能就打折扣了。

家庭财务相关的自动检查,通知频率本身就决定了好不好用。用自动化省下的时间,顺便把每月的固定支出也检查一下,效果可能会更大。

无需签约也能获得100积分的资费模拟 想一起优化通信费,就看看乐天移动

点击后会跳转到乐天登录页面,登录后即可看到活动内容。

我的最终做法,以及另一个绊脚石

最后改成了”每周六”

前面写了这么多规避方法,我自己其实选了稍微不同的做法。重新审视了使用方式后,我干脆把提醒本身改成了每周六9点。

0 9 * * 6

仔细想想坚持每月一次的理由,其实并没有那么充分。既然每周都会收到,那时候需不需要自己判断就行了。让机制保持简单,也是一种答案吧。

定时任务放得七零八落

还有一件这次让我卡住的事:想修改的定时任务,怎么也找不到。

定时任务分散在云端的日程和Mac本地应用两个地方。按名字搜也只在其中一边有,光是找它在哪儿就费了不少工夫。

如果按存放位置分别做一份清单,遇到这种情况时可能会轻松一点。

常见失误与陷阱

整理一下用cron设置定时任务时容易犯的错误。

我踩到的是第1条和第6条。这两种都属于只要知道一次就很容易预防的失误。

开始前的确认事项

常见问题

Q. 同时写日期和星期,一定会变成OR吗?

POSIX规范和cronie的man页面写的是只要匹配其中之一就会执行。另一方面,有些运行环境的页面上并没有写明如何处理。所以,设计成不同时写两者的形式是最安全的。

Q. 有没有用一行写出第1个星期六的方法?

像Cloudflare Workers的Cron Triggers这样,有的环境可以在星期字段使用”#“。不过这是标准cron中没有的扩展。最好先在所用环境的官方文档中确认支持情况后再用。

Q. 每月最后一个星期六该怎么写?

Cloudflare官方给出了用”L”表示最后一个星期几的例子。如果用标准cron来写,就是每周六触发后再判断。用”7天后如果是下个月就是最后一个星期六”来判断可能比较好理解。

Q. 日期判断用1〜7日真的没问题吗?

每月的第一个星期六一定落在1日到7日之间。所以在星期六触发后,只要看日期是否在7以下,就能只锁定第1个星期六。不过,日期如果不按日本时间获取可能会出现偏差,请注意。

Q. 怎么确认下次的执行日期?

可以用crontab.guru这样的网站确认表达式的含义。GitHub Actions的官方文档里也有介绍。不过它是第三方工具,最终最好结合运行环境的官方信息一起确认。

Q. GitHub Actions能指定日本时间吗?

官方文档中说默认是UTC。在此基础上,写明可以指定IANA格式的时区,例如Asia/Tokyo这样的写法。文档里也提到了有夏令时地区的注意事项,不过日本时间的话应该影响不大。

参考的官方信息

总结

在cron中同时指定日期和星期,判断方式不是AND而是OR。所以”0 9 1-7 * 6”不是第1个星期六,而是月初每天加上每个星期六都会执行。只按星期触发、用代码判断日期,就能准确地限定为每月1次。

写完后,推荐把接下来几次的执行日期列出来确认。也要留意和UTC的时差,以及不同运行环境对星期的计数方式,这样会更安心。

关于定时执行时间怎么定,我在自动检查学校通知的经历里也写过。家庭财务的自动检查,可以参考每晚自动检查信用卡消费的机制。

设置了每月一次提醒的朋友,不妨把表达式重新检查一遍。

无需签约也能获得100积分的资费模拟 配合家庭财务自动化,用乐天移动重新审视手机费

点击后会跳转到乐天登录页面,登录后即可看到活动内容。

本文包含联盟营销广告。如果您通过本站链接申请相关商品或服务,我们可能会从合作企业获得报酬。此外,运营者是乐天集团的员工,也可能通过员工推荐计划获得报酬。文章内容与评价均基于运营者的真实体验与调查完成,与是否存在广告无关,但请您在阅读前了解上述关系。详情请参阅免责声明与联盟营销说明。 免责声明与联盟营销说明

本文包含机器翻译内容。正式条件请以乐天移动官方网站的多语言页面为准。

如果您对乐天移动的通信区域·信号状况有疑问,可以通过官方的 电波改善调查申请表 进行咨询。

ABOUT THE AUTHOR
まさきん

乐天集团员工·数字营销人(持有FP资格)

40岁出头,双职工家庭、四个孩子的父亲。在乐天集团担任数字营销工作的同时,也活用理财规划师(FP)资格,专注于家庭理财与资产配置。

查看简介

相关文章

RELATED
Gemini(Google AI Pro)家庭共享最多5名家人|设置步骤与共享范围 AI活用

Gemini(Google AI Pro)家庭共享最多5名家人|设置步骤与共享范围

Google AI Pro除管理员外最多可与5名家人共享。Gemini App和存储空间可以共享,YouTube Premium Lite仅限管理员,Flow的上限由全家分摊。本文根据官方帮助整理了设置步骤和儿童账号的处理方式。

2026.10.13 · 阅读约9分钟
AI订阅扣款日与记账对不上的3个原因|Claude升级后扣款日会改变 AI活用

AI订阅扣款日与记账对不上的3个原因|Claude升级后扣款日会改变

Claude在月中升级到Max时,计费周期会重置,当天成为新的扣款日。美元换算日元的汇率在数据到达发卡公司时确定,账单和记账App的同步也会晚几天。本文整理了以收据邮件为准来确定月份的5个步骤。

2026.10.13 · 阅读约10分钟
把freee会计的未处理明细改成每月一次的核销班次:每月5日7:30、分3类、最多15条交给人处理的设计 AI活用

把freee会计的未处理明细改成每月一次的核销班次:每月5日7:30、分3类、最多15条交给人处理的设计

freee会计里有大约18条未处理明细,连续6次每周检查都原封不动地留着。这里整理一下每月5日早上7:30在家里的Mac上运行的核销班次的设计,以及“能确定的部分每月合并成1个任务、拿不准的明细逐条交给人、上限15条”的界线。

2026.10.12 · 阅读约11分钟
转到乐天移动,最高可获14,000积分 无需签约也能获得100积分的资费模拟 →
立即申请 →