做一个计算起息日的引擎
目标
把受理时刻与日历日期分开,做出一个根据休业日日历和截止时间来计算结算日的引擎。要正确处理对方机构的时区,以及 1987 年和 1988 年首尔的夏令时区间,并且不覆盖账本,对全部笔数重新计算被错误标记的结算日。
为什么重要
时刻只有一个,但这个时刻是几号,要定下时区才有答案。在银行,再加上截止时间和营业日历,资金实际划转的日期就不再是存储下来的事实,而成了规则的产物。如果这个计算散落在代码的许多地方,“昨天的交易”的定义就会因系统而异,这种偏差不会被当作故障,而是一直留到结算对账。
休业日和截止时间逐年变化,所以应当放在数据里,而不是代码里。因此本实验的引擎要从通过 --db 接收的 SQLite 中读取日历和配置。
评分器不会相信你写出来的文字。它会放入由它自己做出的休业日日历和截止时间,挂上一个临时 DB 来运行你的引擎,并把判定与它自己算出的值对照。把答案写死的引擎无法通过。
步骤
- 创建并运行
/root/bizday/gen_bizday.py,生成/root/bizday/txn.db。其中包含 300 笔转账受理、48 笔迁移交易、6 天休业日和 3 行配置。 - 把 UTC 日期与 KST 日期出现分歧的交易写入
/root/bizday/kst_shift.csv,并把三个数字写入/root/bizday/bucket.txt。 - 在
/root/bizday/bizday.py中实现is-bizday、next-bizday和add-bizdays。日历从--db的 holiday 表中读取。 - 给
bizday.py增加value-date。截止时间和时区从 sys_param 中读取。 - 给
bizday.py增加localize,并把在首尔和伦敦日期出现分歧的交易写入/root/bizday/counterparty.csv。 - 把 48 笔迁移交易连同本地时间和偏移量写入
/root/bizday/legacy_offsets.csv,并把三个数字写入/root/bizday/dst_seoul.txt。 - 不要覆盖账本的 value_date,而是在
txn.db中创建value_date表,重新计算 300 笔,并只把发生变化的交易写入/root/bizday/redate.csv。 - 把重新计算后按结算日汇总的结果写入
/root/bizday/valuedate_summary.csv,把报告写入/root/bizday/bizday_report.md。
参考
- 运行约定:引擎以
python3 /root/bizday/bizday.py [--db 경로] <하위명령> …(占位符依次为路径、子命令)的方式调用,并在标准输出中输出一行 JSON 对象。--db的默认值是/root/bizday/txn.db。 is-bizday --date 2026-07-06→{"date":…, "is_bizday": true|false, "reason": "bizday"|"weekend"|"holiday"}next-bizday --date 2026-07-03→{"date":…, "next_bizday": "YYYY-MM-DD"}(不把这一天本身计算在内)add-bizdays --date 2026-07-03 --n -2→{"date":…, "n":…, "result": "YYYY-MM-DD"}(正数向后,负数向前,0 只有在这一天不是营业日时,才顺延到下一个营业日)value-date --at 2026-07-02T07:30:00Z→{"at":…, "local":…, "cutoff":…, "after_cutoff": true|false, "value_date": "YYYY-MM-DD"}localize --at 1987-05-15T03:00:00Z --tz Asia/Seoul→{"at":…, "tz":…, "local":…, "local_date":…, "utc_offset": "+10:00"}- 结算日规则:把受理时刻转换成
sys_param.value_date_tz的本地时间,如果这个时间大于等于sys_param.value_date_cutoff,就加一天,再从那个日期开始寻找营业日并向后顺延。 - 亲自测试:
python3 /root/bizday/bizday.py value-date --at 2026-07-02T07:30:00Z - 用其他日历测试:只在临时 DB 里创建 holiday 和 sys_param 两张表,用
--db挂上去试试。评分器做的就是这件事。 - 从 Python 3.11 起,
datetime.fromisoformat可以直接读取Z后缀。这个镜像里的 Python 是 3.12.3。 - 常见错误:固定加 9 小时,把截止时间与 UTC 时间比较,把休业日写死在代码里,在
astimezone()之前调用.date()。 - 本实验的休业日日历是合成的,并不是真实机构的休业日。
生成受理账本快照
创建并运行 /root/bizday/gen_bizday.py,生成 /root/bizday/txn.db。其中包含 300 笔转账受理、48 笔迁移交易、6 天休业日和 3 行配置。
共有四张表。txn 的 received_utc 形如 2026-06-29T01:02:03Z,而 value_date 按照现在核心系统的规则,直接使用 received_utc 的前 10 个字符。这条规则是错的,这就是本实验的出发点。
把时刻与日历日期分开
把 UTC 日期与 KST 日期互不相同的交易,连同 txn_id,received_utc,utc_date,kst_date 表头写入 /root/bizday/kst_shift.csv,并把 shift_count=、utc_days= 和 kst_days= 写入 /root/bizday/bucket.txt。
用 datetime.fromisoformat 读取时刻,用 astimezone(ZoneInfo("Asia/Seoul")) 转换后再取 .date()。顺序不能颠倒。utc_days 和 kst_days 分别是分桶后得到的互不相同的日期的个数。
把营业日历移进引擎
在 /root/bizday/bizday.py 中实现 is-bizday、next-bizday 和 add-bizdays。休业日要从通过 --db 接收的 DB 的 holiday 表中读取。
周末是 weekday() >= 5,休业日来自表。reason 是 bizday、weekend、holiday 三者之一。评分器会挂上一个装着与学习者 DB 不同的休业日的临时 DB 来运行,所以把日历写死在代码里就会失败。
把截止时间之后的请求顺延到下一个营业日
给 bizday.py 增加 value-date。时区和截止时间要从 sys_param 中读取,大于等于截止时间就加一天,再向后顺延到营业日。
截止时间的比较要在转换成本地时间之后进行。如果与 UTC 时间字符串比较,在曾经实行夏令时的区间会逐条差一小时。边界是 >=——整点收到的请求会被顺延。评分器每次运行都会抽取不同的截止时间。
转换成对方机构的日历
给 bizday.py 增加 localize,并把在首尔和伦敦日期出现分歧的交易,连同 txn_id,received_utc,seoul_date,london_date 表头写入 /root/bizday/counterparty.csv。
utc_offset 写成 +09:00 这样,带符号的两位小时和分钟。local 必须是带偏移量的 RFC 3339 字符串——去掉偏移量,这个字符串就不再是一个时刻了。伦敦在夏天是 UTC+1。
找出 1987 年夏天的那一小时
把 48 笔迁移交易按 txn_id,received_utc,seoul_local,utc_offset 写入 /root/bizday/legacy_offsets.csv,并把 kdt_rows=、kst_rows= 和 date_shift_rows= 写入 /root/bizday/dst_seoul.txt。
Asia/Seoul 现在是 UTC+9,但在 1987 年和 1988 年的夏天实行过夏令时。zoneinfo 知道这一事实,所以不要自己计算,直接问它。date_shift_rows 是 KST 日期与 UTC 日期不同的迁移交易的笔数。
不覆盖账本,重新计算结算日
在 txn.db 中创建 value_date(txn_id, value_date) 表,重新计算 300 笔的结算日并放进去,只把与旧结算日不同的交易,按 txn_id,received_utc,old_value_date,new_value_date 写入 /root/bizday/redate.csv。不要动 txn.value_date。
不要重新实现规则,而是把 bizday.py 作为模块导入使用(把 /root/bizday 加入 sys.path 再 import)。如果把账本记录的旧结算日抹掉,就无法知道它是按哪条规则标出来的值。重新计算的结果放进新表里。
转成汇总和报告
把重新计算后按结算日统计的笔数和金额,按 value_date,txn_count,amount_sum 升序写入 /root/bizday/valuedate_summary.csv,并在 /root/bizday/bizday_report.md 中写下 ## 무엇이 틀렸나、## 결제일을 어떻게 계산하나、## 상대방 표준시、## 과거 서머타임、## 재발 방지 五个小节。
汇总可以通过把 value_date 表与 txn 连接得到。报告中要用数字和名称写出变化的笔数、截止时间、按哪个时区计算、对方机构的时区,以及 1987 年和 1988 年的偏移量。夹着休业日的日期在汇总里是缺失的,这一点也值得确认。