一般社団法人 全国個人事業主支援協会

COLUMN コラム

こんにちは、システムエンジニアの篠原 透です。

今回から、私が実際に体験した問題やその対応策を載せていきたいと思います。
私見に基づく記載もあるかと思いますが、何卒ご容赦ください。

日付計算の曖昧性

システム設計者やプログラマーの方には常に注意していただきたい問題の1つ。
その日付時刻、正しく計算できていますでしょうか?

システムのよくある要件に、下記のような事例があります。

  • 1ヶ月先まで有効なリンクをメールで送信する
  • 月末にバッチ処理を実行する
  • 1日1回定時にバッチ処理を実行する

日付の加減算について、曖昧性があることくらい理解してるよ!と思われるかもしれませんが、
私が最もお伝えしたい点は、ある日付時刻に対して

加減算すべき基準が「年」「月」「日」「時」「分」「秒」のどれなのかを適切に判断しなければならない

ということです。

月末日の計算

もっともイメージしやすい例だと思いますが、
2020/1/31の1ヶ月先の日付を求めようとしたとき、2020/2/31は実在しません。
そのときどんな日付に変わるかは処理系に依存します。
※Javaではこんなコードを使って確認できると思います。

import java.util.*;
import java.text.*;

public class Main {
    public static void after1month(int year, int month, int date, int hourOfDay, int minute, int second) {

        DateFormat df = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
        Calendar cal = Calendar.getInstance();

        // 日付をセット(monthだけ-1が必要)
        cal.set(year, month - 1, date, hourOfDay, minute, second);

        // 1ヶ月先を計算
        cal.add(Calendar.MONTH, 1);

        System.out.println(df.format(cal.getTime()));

    }

    public static void main(String[] args) throws Exception {

        // 2020/1/30
        after1month(2020, 1, 30, 0, 0, 0); // 2020/02/29 00:00:00

        // 2020/1/31
        after1month(2020, 1, 31, 0, 0, 0); // 2020/02/29 00:00:00

        // 2020/2/28
        after1month(2020, 2, 28, 0, 0, 0); // 2020/03/28 00:00:00

        // 2020/2/29
        after1month(2020, 2, 29, 0, 0, 0); // 2020/03/29 00:00:00

        // 2020/3/31
        after1month(2020, 3, 31, 0, 0, 0); // 2020/04/29 00:00:00

        // 2020/4/30
        after1month(2020, 4, 30, 0, 0, 0); // 2020/05/30 00:00:00

    }
}

お手元の環境で実行されるとわかるかと思いますが、31日が存在しない月はありえる月末日に丸められたりします。
逆に、丸められた日付の1か月後は、丸められたままの1か月後の日付であったりします。
この計算を毎月「月末日」の算出を目的として使っていると、2021/3/31を指すべき日付が2020/3/29を指してしまう…という事象が起こりえます。

対応策としては、下記2つがオーソドックスだと思います。

  1. 月末日を次月月初日-1日で求める。
  2. 1ヶ月先ではなく、30日などと日数で期間指定する。

「月末日にバッチ処理を実行する」必要があるなら1. で、
「1ヶ月先まで有効なリンクをメールで送信する」なら2. とすることをクライアントと調整するのが良いと思います。

閏年をまたぐ日付計算

日付計算の意図しないズレは、閏年が起因になることもあります。
2020/2/29の1年先は2021/2/29ではありません。
また、365日先と考えても常に同日になるとは限りません。
2019/3/1の365日先は2020/2/29、2020/2/29の365日先は2021/2/28など…。

明らかなのは2020年の1年先が2021年ということだけであり、閏年を挟んで日数計算を行う場合は注意が必要です。

私も4年に1度だけ発生したり、4年間のうち1年間だけ周期的に発生するバグに見舞われたことがあります。

1日≠24時間?

また、日本国内にいる限り、1日は24時間固定と考えてほぼ問題ありませんが、
世界規模のアプリケーションを設計する場合は、サマータイムの考え方を考慮しなければならず、
日によっては23時間だったり、25時間だったりすることもありえます。
このような場合、1日を60秒×60分×24時間などと計算している処理は、本当にそれで問題ないか、確認する必要があるでしょう。
「1日1回定時にバッチ処理を実行する」つもりでも、サマータイム期間中標準時の時刻で実行されている、なんてこともありえます。

まとめ

上記でご紹介した通り、日付計算には意外な落とし穴があります。
システムに落とし込む際は、常に意識して向き合う必要がある問題です。
悩ましい…。

The following two tabs change content below.

篠原 透

2015年より企業SEとして勤務し、Javaを中心とした業務系Webアプリケーションの設計・開発・運用を経験。2019年2月に独立し、個人事業主となる。

この記事をシェアする

  • Twitterでシェア
  • Facebookでシェア
  • LINEでシェア