【C++】時刻計算を簡単に行えるライブラリを自作した


はじめに

C++ での時刻計算では、struct tmを使った方法がよく紹介されています。ただし、struct tmにはクセがあり、1900年始まりだったり、0月始まりだったりとプログラマが注意を払わなければならない部分があります。

そこで、struct tmをラッピングし、時刻計算を簡単に扱えるライブラリを作成しました。
名前をEasy Datetimeとし、下記のシンプルな機能を提供します。

  • 機能一覧
    • 日付計算
    • 時刻差の計算
    • 文字列 ↔︎ 時刻への相互パース
    • 現在時刻の取得

Eazy-Datetime-Cpp ← ライブラリへのリンクはこちらです!

目次

インストール

  • こちらのGitを開く
    • includeフォルダをあなたのC++プロジェクトに追加します。
    • 以下のサンプルのように #include datetime.h を宣言すればOKです。
    • ヘッダオンリーで使用できます。
      • ※ 下記のようにtry-catch句で囲んでおくと、エラーメッセージの確認ができて便利です。
sample.cpp

/* 必須 */
#include "datetime.h"

/* 関連するヘッダ */
#include <iostream>
#include <string>
#include <vector>

/* 使用する名前空間 */
// using namespace EZ;

int main()
{
    try {
        // ここにサンプルを記述
    }
    catch (EZ::DatetimeException e) {
        // 例外時のエラーメッセージを表示
        std::cout << e.what() << std::endl;
    }

    return 0;
}

EZ::Datetime(日付クラス)

現在時刻の取得

  • static関数の now()を呼び出すと取得できます。返り値は EZ::Datetime型です。
    • now(true)とすると、UTC基準の時刻が取得できます。
sample.cpp
    // 現在時刻の取得
    auto now = EZ::Datetime::now();
    std::cout << "Now is " << now << std::endl;

    /* 出力 */
    // >> Now is 2021/04/25 22:02:00 JST

時刻の設定

  • 時刻の設定は、コンストラクタで行います。渡し方は次の通りです。
    • 1. タイムスタンプのみを引数に渡す(※ ただし、フォーマットは"%Y/%m/%d %H:%M:%S"として解釈されます。)
    • 2. タイムスタンプとフォーマットを引数に渡す
    • 3. 年, 月, 日, 時, 分, 秒 の順に整数値を渡す
sample.cpp
    // 時刻の設定
    auto date1 = EZ::Datetime("2021/1/1 0:00:00" /*, false*/);
    auto date2 = EZ::Datetime("2021/1/1 0:00:00", "%Y/%m/%d %H:%M:%S" /*, false*/);
    auto date3 = EZ::Datetime(2022,1,1,0,0,0 /*, false*/);

    // 時刻の表示
    std::cout << "date1 is " << date1 << std::endl;
    std::cout << "date2 is " << date2 << std::endl;
    std::cout << "date3 is " << date3.str("西暦%Y年%m月%d日 %H時%M分%S秒 TZ=%Z") << std::endl;

    /* 出力 */
    // >> date1 is 2021/01/01 00:00:00 JST
    // >> date2 is 2021/01/01 00:00:00 JST
    // >> date3 is 西暦2022年01月01日 00時00分00秒 TZ=JST
  • コンストラクタの末尾の引数を true にすると、タイムゾーンがUTCとなります。
    • falseにすると、タイムゾーンは現地時間になります。
    • デフォルトは false です。

フォーマット指定子

  • Datetimeクラスでは下記の入出力指定子をサポートしています。
    • 入力フォーマットはコンストラクタで指定可能
    • 出力フォーマットは関数str()で指定可能
指定子 機能 入力フォーマット 出力フォーマット
%Y 西暦を4桁で指定 ○ 対応 ○ 対応
%y 西暦を下2桁で指定 × 非対応 ○ 対応
%m 月を指定 ○ 対応(入力桁数:1〜2桁) ○ 対応(2桁で出力)
%d 日を指定 ○ 対応(入力桁数:1〜2桁) ○ 対応(2桁で出力)
%H 時を指定 ○ 対応(入力桁数:1〜2桁) ○ 対応(2桁で出力)
%M 分を指定 ○ 対応(入力桁数:1〜2桁) ○ 対応(2桁で出力)
%S 秒を指定 ○ 対応(入力桁数:1〜2桁) ○ 対応(2桁で出力)
%Z タイムゾーンを指定 × 非対応 (引数isUTCで設定) ○ 対応

Datetimeクラスの値の取得

sample.cpp
int     sec() const // 日時のうち"秒"のみを返却
int     minute() const // 日時のうち"分"のみを返却
int     hour() const // 日時のうち"時"のみを返却
int     day() const // 日時のうち"日"のみを返却
int     month() const // 日時のうち"月"のみを返却
long    year() const // 日時のうち"西暦年"のみを返却
int     daysOfWeek() const // 日曜始まりの曜日番号を返却

// 時刻を {年, 月, 日, 時, 分, 秒}の6つの要素を持つvectorで返却する
std::vector<long long>  toVector() const

時刻どうしの引き算

  • EZ::Datetimeオブジェクトどうしの時刻差を計算することができます。
    • 返り値は、EZ::TimeDelta型として返却されます。
sample.cpp
    // 時刻差の計算
    auto delta = date2 - date1;
    std::cout << date2 << " - " << date1 << "\n\t = " << delta << std::endl;

    /* 出力 */
    // >> 2022/01/01 00:00:00 JST - 2021/01/01 00:00:00 JST
    // >>    = TimeDelta(days=365, hours=0, minutes=0, seconds=0)

EZ::TimeDelta(時刻差クラス)

TimeDelta(時刻差クラス)の設定

  • 時刻差の設定はコンストラクタで行います。引数の渡し方は下記の通りです。
    • 1. 時刻差を秒数で渡す
    • 2. 時刻差を (日, 時, 分, 秒) として渡す
sample.cpp
    auto delta0 = EZ::TimeDelta(-365 * 24 * 3600);
    auto delta1 = EZ::TimeDelta(365, 0, 0, 0);

    std::cout << "delta0 = " << delta0 << std::endl;
    std::cout << "delta1 = " << delta1 << std::endl;

    /* 出力 */
    // >> delta0 = TimeDelta(days=-365, hours=0, minutes=0, seconds=0)
    // >> delta1 = TimeDelta(days=365, hours=0, minutes=0, seconds=0)

TimeDelta(時刻差クラス)の値の取得

  • totalSeconds()のようにトータル時間で取得する方法と、toVector()のように、(日, 時, 分, 秒)の組み合わせで取得する方法があります。
sample.cpp
long long   totalSeconds() const // 時刻差をトータル秒数で返す
long long   totalMinutes() const // 時刻差をトータル分数で返す(小数切り捨て)
long long   totalHours() const // 時刻差をトータル時間数で返す(小数切り捨て)
long long   totalDays() const // 時刻差をトータル日数で返す(小数切り捨て)
long long   totalWeeks() const // 時刻差をトータル週数で返す(小数切り捨て)

// 時刻差を {日, 時, 分, 秒}の4つの要素を持つvectorで返却する
std::vector<long long>  toVector() const

TimeDelta(時刻差クラス)の四則演算

  • 和,差 : 2つのTimeDeltaオブジェクトどうしの演算ができます。
  • 積,商 : 左にTimeDeltaオブジェクト, 右にdoubleのオペランドでの演算ができます。
    • なお、TimeDeltaオブジェクトどうしの除算も可能です。その際の返り値はdoubleです。
sample.cpp
    // 和・差
    std::cout << "delta0 + delta1 = "<< delta1 + delta1 << std::endl;
    std::cout << "delta0 - delta1 = "<< delta1 - delta1 << std::endl;
    // 積・商
    std::cout << "delta1 * 2.0 = "<< delta1 * 2 << std::endl;
    std::cout << "delta1 / 2.0 = "<< delta1 / 2 << std::endl;

    /* 出力 */
    // >> delta0 + delta1 = TimeDelta(days=730, hours=0, minutes=0, seconds=0)
    // >> delta0 - delta1 = TimeDelta(days=0, hours=0, minutes=0, seconds=0)
    // >> delta1 * 2.0 = TimeDelta(days=730, hours=0, minutes=0, seconds=0)
    // >> delta1 / 2.0 = TimeDelta(days=182, hours=12, minutes=0, seconds=0)

例外送出

  • Datetimeオブジェクト、TimeDeltaオブジェクトともに、EZ::DatetimeExceptionを送出します。
    • 送出された例外は、ex.what()でメッセージを確認できます。
sample.cpp
    // 例外処理
    try{
        // Year is Negative.
        EZ::Datetime err = EZ::Datetime(-2000, 1, 1, 0, 0, 0);
    }
    catch(EZ::DatetimeException ex){
        std::cout << "Error message: " << ex.what() << std::endl;
    }

    /* 出力 */
    // >> Error message: Input time is out of range. minimum datetime is 1970/01/01 09:00:00 JST

Q&A

Q1. タイムゾーンは2種類しか設定できないの?
  • はい。現地時刻 か UTC の2種類しか選べません。
  • 現地時刻を選択した場合、どのタイムゾーンが適用されるかはOSの時刻設定に依存し、自動で決まります。

Q2. タイムゾーンはどうやって設定するの?
  • コンストラクタの末尾の引数const bool& isUTCの真偽値で決定します。
    • isUTC = false ならばローカル時刻
    • isUTC = true ならばUTC
    • デフォルトでは isUTC = false です。

Q3. タイムゾーンの確認方法は?
  • Datetimeインスタンスに対し、メンバ関数timezone()を呼び出します。
    • インスタンスに適用されたタイムゾーン情報が std::string 型で返却されます。

Q4. 小数秒の計算には対応しているの?
  • 小数秒には対応しておりません。小数秒は切り捨てられ、整数秒として扱われます。

Q5. 閏年、閏秒への対応は?
  • 閏年には対応しておりますが、閏秒には対応していません。
  • 閏年、閏秒の計算方法は、struct tmの計算方法に依存しています。

Q6. サマータイムの設定は?
  • struct tm と同様に、タイムゾーンに応じてサマータイムは自動設定されます。
  • サマータイムの適用状況は、メンバ関数isDst()を呼び出すことで確認できます。
    • isDst() => 0 :夏時間が無効
    • isDst() => 1 :夏時間が有効
    • isDst() => 負の値 :処理系に依存
  • 日本で使用する場合は、常に0になります。

Q7. 処理可能な時間幅は?
  • 1970/1/1 0:00:00 UTC 〜 3000/1/2 0:00:00 UTC です。

Q8. このライブラリの実体は何?
  • struct tmtime_tのラッピングライブラリです。

Q9. テストは行ったの?
  • GoogleTestでテストコードを記述し、テストを行いました。
  • バグがございましたらお気軽にコメントをお願いいたします。

リンク

フィードバックいただけると大変励みになります。最後までご覧いただきありがとうございました。