【実務・中級編】DATEおよびDATETIME関数の世紀またぎ問題とシステム日付取得 – PL/Iの基本構文とデータ制御実践ガイド

お疲れ様です。今日も今日とて、延々と動き続ける基幹システムのログと格闘していることと思います。

メインフレームの世界では、何十年も前に先輩たちが組み上げたPL/Iの巨大なバッチプログラムが、今この瞬間も会社の心臓部を動かしています。「動いているものに手を触れるな」とはよく言ったものですが、ビジネスの要求や法改正、そして何より「20X0年問題」やクラウド移行といった避けて通れない現実が、私たちにコードの保守・改修を突きつけてきます。

今回は、そんなPL/Iプログラミングにおいて、幾度となくシステム障害を引き起こしてきた「DATEおよびDATETIME関数の世紀またぎ問題とシステム日付の取得」について、現場のノウハウを交えて徹底的に解説します。

レガシーな仕様の罠と、それを現代の基準でどう安全に書き換えるべきか、しっかりマスターしていきましょう。

1. なぜレガシーなDATE関数は「悪夢」なのか?

PL/Iを触り始めたばかりの頃、あるいは古いソースコードをメンテしていると、システム日付を取得するために以下のようなコードに出会うはずです。

1
DCL WK_DATE CHAR(6);
WK_DATE = DATE();

一見すると、現在の日付(例えば2023年10月25日であれば `231025`)をサクッと取得できる非常に便利な関数に見えます。しかし、ここに世紀またぎ(2000年問題およびその後の将来的な破綻)の最大の罠が潜んでいます。

2桁年号がもたらす時限爆弾

古い `DATE` 関数が返す値は、デフォルトで `YYMMDD` の6桁です。「世紀(CC)」の情報が含まれていません。
これが何を意味するか、お分かりですね?

  • `991231` の翌日が `000101` になったとき、プログラムはこれを「1999年12月31日」の翌日を「2000年」ではなく「1900年」の元旦と誤認する、あるいは大小比較のロジックが完全に崩壊します。

2000年問題(Y2K)の際には、世界中のエンジニアが血眼になってこの6桁の `DATE` を4桁年(`YYYY`)を扱うロジックや、後述する `DATETIME` 関数へ置き換えました。しかし、古い資産がそのまま残っているモジュールや、過去の修正漏れが、今もどこかの夜間バッチで眠っているのです。

2. 現代の標準:DATETIME関数と世紀(CC)の概念

IBMのEnterprise PL/Iコンパイラ環境では、こうしたレガシーな問題を完全に克服するための現代的なビルトイン関数が用意されています。それが `DATETIME` です。

`DATETIME` 関数は、引数に指定するピクチャ文字列(マスクパターン)によって、取得する日付や時刻のフォーマットを自由自在にコントロールできます。もちろん、世紀を含む4桁の年号(`YYYY`)を安全に取得することが可能です。

日付・時刻取得の基本パターン

  • 世紀を含む日付 (`YYYYMMDD`)

`DATETIME(‘YYYYMMDD’)`

  • タイムスタンプ(ミリ秒・マイクロ秒まで含む高精度な値)

`DATETIME(‘YYYYMMDDHHMISS999’)`

これにより、データの世代管理やVSAMファイルの更新日付の比較において、世紀抜けによる予期せぬロジック破綻を未然に防ぐことができます。

3. 【実践】VSAMレコードの更新とシステム日付取得のサンプルコード

それでは、実際のバッチプログラムを想定したコードを見てみましょう。
ここでは、VSAM(KSDS)ファイルからマスターレコードを読み込み、現代的な `DATETIME` 関数を使って取得した「更新年月日」をセットして書き戻す、という極めて実務的なシチュエーションを想定しています。

PL/Iの構文規則として、`DATE` や `DATETIME` は予約語ではなくコンテキスト依存のビルトイン関数であるため、適切に宣言(または組み込み関数として認識)して使用します。

1

  • 顧客マスター更新バッチプログラム


CUSTM: PROC OPTIONS(MAIN);

/ 宣言部 /
DCL CUST_FILE FILE RECORD SEQUENTIAL UPDATE
ENV(VSAM);

/ レコード構造体 /
DCL 1 CUST_REC,
5 CUST_ID CHAR(8), / 顧客ID /
5 CUST_NAME CHAR(40), / 顧客名 /
5 UPD_DATE CHAR(8), / 更新年月日(YYYYMMDD) /
5 FILLER CHAR(44); / 予備領域 /

DCL WK_STATUS CHAR(2);
DCL 99_EOF BIT(1) INIT(‘0’B);

/ 終了条件のONユニット定義 /
ON ENDFILE(CUST_FILE) 99_EOF = ‘1’B;

/ ファイルオープン /
OPEN FILE(CUST_FILE);

/ メインループ /
DO WHILE(99_EOF = ‘0’B);

/ VSAMレコードの読込 (UPDATE前提) /
READ FILE(CUST_FILE) INTO(CUST_REC);

IF 99_EOF = ‘1’B THEN LEAVE;

/ ——————————————————— /
/ [重要] 現代的なDATETIME関数による世紀を含むシステム日付取得 /
/ ——————————————————— /
CUST_REC.UPD_DATE = DATETIME(‘YYYYMMDD’);

/ レコードの書き戻し /
REWRITE FILE(CUST_FILE) FROM(CUST_REC);

END;

/ ファイルクローズ /
CLOSE FILE(CUST_FILE);

PUT SKIP LIST(‘CUSTM: NORMAL END.’);

END CUSTM;

コードのポイントと実務上の注意点

1. `DATETIME(‘YYYYMMDD’)` の活用
古いコードの `WK_DATE = DATE();` をそのまま残していると、コンパイル時に警告が出たり、将来的なデータ破損の温床になります。必ず `YYYYMMDD` の8桁フォーマットを明示するビルトイン関数に置き換えましょう。
2. ONユニットによる例外処理
VSAMの入出力やファイル終端(ENDFILE)の制御には `ONユニット` を用いるのがPL/Iの王道です。制御フローがどこに流れるのかを常に意識してください。

4. タイムゾーンの考慮とオンライン/バッチの罠

システム日付を取得する際にもう一つ忘れてはならないのが「タイムゾーン(Time Zone)」の問題です。特にグローバル展開しているシステムや、クラウド環境(AWSメインフレームモダナイゼーションやAzure等)へ移行したシステムでは、Z/OSのTZ環境変数やTZ設定が影響を与えます。

  • ローカル時刻 vs 協定世界時 (UTC)

`DATETIME` 関数が返すのは、基本的にはOS(MVS/Zお墨付きのシステムクロック)が保持するローカルタイムですが、基幹システムの運用によっては、夜間バッチの稼働中にサマータイム(日本国内ではありませんが海外拠点との連携時)や、サーバーのタイムゾーン設定(UTC運用かJST運用か)の差異に泣かされることがあります。

  • 高精度タイムスタンプの落とし穴

ログ出力や排他制御のキーとして `DATETIME(‘YYYYMMDDHHMISS999’)` のような高精度な値を取得する場合、複数CPU(スプロケット環境)での厳密な時刻同期が取れていないと、ミリ秒単位で処理順序が逆転する怪奇現象(競合バグ)が発生することがあります。バッチの並行実行(マルチタスク/マルチステップ)を行う設計では特に注意が必要です。

5. 先輩からのアドバイス:改修時の心構え

もし、あなたが今担当しているプロジェクトで「古い `DATE` 関数」を見つけたら、安易にその行だけを直すのではなく、以下の手順で影響範囲を調査してください。

1. データ定義の確認:受け渡される変数が `CHAR(6)` なのか `CHAR(8)` なのか。構造体のオフセットが変わることで、他のプログラムやJCL、DB2の定義に影響を与えないか。
2. 比較演算のチェック:日付の大小比較(`IF WK_DATE > ‘991231’` のような古いロジック)が残っていないか。8桁化に伴い、条件式の定数側も `20000000` のように修正が必要です。
3. テストの徹底:西暦2000年問題のときと同様に、世紀をまたぐテストデータ(例:12月31日から翌年1月1日への日跨ぎ、うるう年の判定など)を必ず網羅した結合テストを実施してください。

レガシーシステムの維持は地味で泥臭い作業が多いですが、基幹システムを支えているという誇りを持って、確実で堅牢なコードを書き続けていきましょう。何か困ったことがあれば、いつでも声をかけてください。

タイトルとURLをコピーしました