決済日を計算するエンジンをつくる
目標
受付の瞬間とカレンダー上の日付を切り分け、休業日カレンダーと締め時刻で決済日を計算するエンジンを作ります。相手機関の標準時と、1987・1988年のソウルのサマータイム期間まで正しく扱い、誤って付けられた決済日を、元帳を上書きせずに全件再計算します。
なぜ重要なのか
瞬間は1つですが、その瞬間が何日なのかは標準時を決めて初めて答えが出ます。銀行ではここに締め時刻と営業日カレンダーがさらに加わり、お金が実際にやり取りされる日付は、保存された事実ではなくルールの出力物になります。この計算がコードの複数の場所に散らばると、「昨日の取引」の定義がシステムごとに変わり、そのずれは障害には見えないので、清算の突合まで生き残ります。
休業日と締め時刻は毎年変わるので、コードではなくデータに置きます。そのため、このラボのエンジンは、カレンダーと設定を--dbで受け取ったSQLiteから読みます。
採点ツールは、書かれた文言を信用しません。自分で作った休業日カレンダーと締め時刻を入れた一時DBを渡してエンジンを実行し、判定を自分で計算した値と突き合わせます。答えを埋め込んだエンジンは通過できません。
ステップ
/root/bizday/gen_bizday.pyを作成して実行し、/root/bizday/txn.dbを作ってください。送金受付300件、移行取引48件、休業日6日、設定3行が入ります。- UTC日付とKST日付が分かれる取引を
/root/bizday/kst_shift.csvに、3つの数字を/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に書き、3つの数字を/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オブジェクト1行を標準出力に出します。--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以上なら1日足したうえで、その日付から営業日を探して前へ送ります。 - 自分で試す:
python3 /root/bizday/bizday.py value-date --at 2026-07-02T07:30:00Z - 別のカレンダーで試す: 一時DBにholidayとsys_paramの2つのテーブルだけを作り、
--dbで渡してみてください。採点ツールがすることがそれです。 datetime.fromisoformatは、Python 3.11からZ接尾辞をそのまま読めます。このイメージのPythonは3.12.3です。- よくある間違い: 固定で9時間を足す、締め時刻をUTC時刻と比較する、休業日をコードに埋め込む、
.date()をastimezone()の前に呼ぶ。 - このラボの休業日カレンダーは合成です。実在の機関の休業日ではありません。
受付元帳のスナップショットを作る
/root/bizday/gen_bizday.pyを作成して実行し、/root/bizday/txn.dbを作ってください。送金受付300件、移行取引48件、休業日6日、設定3行が入ります。
テーブルは4つです。txnのreceived_utcは2026-06-29T01:02:03Zの形で、value_dateは今の基幹系のルールどおり、received_utcの先頭10文字をそのまま使います。そのルールが間違っていることが、このラボの出発点です。
瞬間とカレンダー上の日付を切り分ける
UTC日付とKST日付が異なる取引を、/root/bizday/kst_shift.csvにtxn_id,received_utc,utc_date,kst_dateのヘッダー行とともに書き、/root/bizday/bucket.txtにshift_count=、utc_days=、kst_days=を書いてください。
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の3つのうちの1つです。採点ツールは、学習者のDBとは異なる休業日を入れた一時DBを渡して実行するので、カレンダーをコードに埋め込むと不合格になります。
締め時刻のあとのリクエストを翌営業日に送る
bizday.pyにvalue-dateを追加してください。標準時と締め時刻はsys_paramから読み、締め時刻以上なら1日足したうえで、営業日に送ってください。
締め時刻の比較は、ローカル時刻に移したあとで行います。UTC時刻の文字列と比較すると、サマータイムがあった区間で1時間ずつずれます。境界は>=です。ちょうどの時刻に入ったリクエストは、翌日に回ります。採点ツールは、締め時刻を実行のたびに別の値で選びます。
相手機関のカレンダーに移す
bizday.pyにlocalizeを追加し、ソウルとロンドンで日付が分かれる取引を、/root/bizday/counterparty.csvにtxn_id,received_utc,seoul_date,london_dateのヘッダー行とともに書いてください。
utc_offsetは、+09:00のように、符号と2桁の時・分で書きます。localは、オフセットが付いたRFC 3339文字列でなければなりません。オフセットを外すと、その文字列はもう瞬間ではありません。ロンドンは夏はUTC+1です。
1987年の夏の1時間を見つけ出す
移行取引48件を、/root/bizday/legacy_offsets.csvにtxn_id,received_utc,seoul_local,utc_offsetで書き、/root/bizday/dst_seoul.txtにkdt_rows=、kst_rows=、date_shift_rows=を書いてください。
Asia/Seoulは今はUTC+9ですが、1987・1988年の夏にはサマータイムがありました。zoneinfoはその事実を知っているので、自分で計算せず、尋ねてください。date_shift_rowsは、KST日付がUTC日付と異なる移行取引の数です。
決済日を、元帳を上書きせずに再計算する
txn.dbにvalue_date(txn_id, value_date)テーブルを作って300件の決済日を再計算して入れ、古い決済日と異なる取引だけを/root/bizday/redate.csvにtxn_id,received_utc,old_value_date,new_value_dateで書いてください。txn.value_dateには手を触れないでください。
ルールを再実装せず、bizday.pyをモジュールとして読み込んで使ってください(sys.pathに/root/bizdayを入れてimport)。元帳が記録した古い決済日を消すと、どのルールで付けた値だったかがわからなくなります。再計算の結果は、新しいテーブルに入れます。
集計とレポートにまとめる
再計算した決済日ごとの件数と金額を、/root/bizday/valuedate_summary.csvにvalue_date,txn_count,amount_sumで昇順に書き、/root/bizday/bizday_report.mdに## 무엇이 틀렸나、## 결제일을 어떻게 계산하나、## 상대방 표준시、## 과거 서머타임、## 재발 방지の5つの節を書いてください(見出しは韓国語で、順に「何が間違っていたか」「決済日をどう計算するか」「相手側の標準時」「過去のサマータイム」「再発防止」という意味です)。
集計は、value_dateテーブルとtxnを結合すれば出ます。レポートには、変わった件数、締め時刻、どの標準時で計算するか、相手機関の標準時、1987・1988年のオフセットを、数字と名前で書いてください。休業日にかかる日付が集計から抜けていることも、確認する価値があります。