こんにちは。開発本部で主にバックエンドの開発をしている 宮崎(@sucalul) です。
今回は2024年2月29日に発生したバグ事例について紹介します。
実際のバグ事例
社内のソースコード上で、ある機能を判定する条件の1つである「会社設立年月から2年以内であること」を判定するロジックにうるう日のパターンが考慮されていなかったためバグが発生しました。
実際の関数内のコードがこちら。(一部省略・値を直接入力しています。)
list($current_date_year, $current_date_month, $current_date_day) = explode('-', $current_date);
$check_foundation_date = date('Ym', mktime(0, 0, 0, $current_date_month, $current_date_day, $current_date_year - 2));
list($foundation_date_year, $foundation_date_month) = explode('-', '2022-02');
$foundation_date = sprintf('%04d%02d', $foundation_date_year, $foundation_date_month);
if($foundation_date < $check_foundation_date) {
// 2年以上経っていると判断されたため、別の処理
}1行ずつ見ていきます。
list($current_date_year, $current_date_month, $current_date_day) = explode('-', $current_date);まず最初の行では、変数 $current_date の値を-を区切り文字として分割し、結果の配列を変数 $current_date_year、$current_date_month、$current_date_day の3つに代入しています。
例えば今回の事例では、$current_date の値が2024-02-29になり、このコード行を実行すると、
$current_date_year の値は 2024、$current_date_month の値は02、current_date_day の値は29となります。
$check_foundation_date = date('Ym', mktime(0, 0, 0, $current_date_month, $current_date_day, $current_date_year - 2));この行では、date 関数と mktime 関数を使い、先ほどの$current_date_year、$current_date_month 、$current_date_day から2年前の日付の0時をYm形式で取得します。
今回の事例では、$check_foundation_date の値は、202203 となります。
2024-02-29 の2年前と考えると2022-02-29 になりそうと考えてしまいますが、2022-02-29 は存在しない日付なため、ここで実行しているPHPのmktime関数では自動的に2022年3月1日として解釈されます。
本来ここでは、$check_foundation_dateの値は202202になることが期待されていたと推測されます。
list($foundation_date_year, $foundation_date_month) = explode('-', '2022-02');この行では、’2022-02’(会社設立年月)を-を区切り文字として分割し、結果の配列を変数 $foundation_date_year、$foundation_date_month の2つに代入しています。
このコードでは、$foundation_date_yearは2022、$foundation_date_monthは02となります。
$foundation_date = sprintf('%04d%02d', $foundation_date_year, $foundation_date_month);この行ではsprintf 関数を使用して、年と月を代入し、固定の長さで表示(4桁の年と2桁の月)するフォーマット文字列を作成します。
今回のケースだと、$foundation_dateは202202です。
if($foundation_date < $check_foundation_date) {
// 2年以上経っていると判断されたため、別の処理
}最後の行では、$foundation_dateと$check_foundation_dateを比較しています。
今回のケースだと、以下の比較になり、if文がtrueになってしまい、本来2024年2月29日は2022年2月の2年以内と判断されるべきでしたが、2年以上経っていると判断され、別の処理が走ってしまい、バグの原因となりました。
if("202202" < "202203") {
// 2年以上経っていると判断されたため、別の処理
}実際に値を入れてみて動かしてみるとこのようにif文内の処理に入ってしまうことがわかります。
まとめ
今回はうるう日にのみこのようにバグが起きてしまうコードを紹介しました。
このコードはレガシーコードで、かなり昔から動いており、単体テスト等がなかったため十分なテストができずバグが起きてしまう・調査に時間がかかってしまうことを体感し、改めてこういった日付周りの処理には特に注意して実装し、テストコードが必要だなと学びになりました。


