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

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

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

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

## 目次
- [インストール](#インストール)
- [EZ::Datetime(日付クラス)](#ezdatetime日付クラス)
    - [現在時刻の取得](#現在時刻の取得)
    - [時刻の設定](#時刻の設定)
    - [フォーマット指定子](#フォーマット指定子)
    - [Datetimeクラスの値の取得](#datetimeクラスの値の取得)
    - [時刻どうしの引き算](#時刻どうしの引き算)
- [EZ::TimeDelta(時刻差クラス)](#eztimedelta時刻差クラス)
    - [TimeDelta(時刻差クラス)の設定](#timedelta時刻差クラスの設定)
    - [TimeDelta(時刻差クラス)の値の取得](#timedelta時刻差クラスの値の取得)
    - [TimeDelta(時刻差クラス）の四則演算](#timedelta時刻差クラスの四則演算)
- [例外送出](#例外送出)
- [Q&A](#qa)
- [リンク](#リンク)


## インストール
- [**こちらのGitを開く**](https://github.com/n700a-dev/Easy-Datetime-Cpp)
    - `include`フォルダをあなたのC++プロジェクトに追加します。
    - 以下のサンプルのように `#include datetime.h` を宣言すればOKです。
    - ヘッダオンリーで使用できます。
        - **※ 下記のように`try-catch`句で囲んでおくと、エラーメッセージの確認ができて便利です。**

```C++: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基準の時刻が取得できます。

```C++: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.__ 年, 月, 日, 時, 分, 秒 の順に整数値を渡す

```C++: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クラスの値の取得

```C++: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`型として返却されます。

```C++: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.__ 時刻差を (日, 時, 分, 秒) として渡す

```C++: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()のように、(日, 時, 分, 秒)の組み合わせで取得する方法があります。

```C++: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です。

```C++: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()`でメッセージを確認できます。

```C++: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
<details><summary>Q1. タイムゾーンは2種類しか設定できないの？</summary><div>

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

<details><summary>Q2. タイムゾーンはどうやって設定するの？</summary><div>

- コンストラクタの末尾の引数`const bool& isUTC`の真偽値で決定します。
    - isUTC = false　ならばローカル時刻
    - isUTC = true ならばUTC
    - デフォルトでは isUTC = false です。
        - [こちらの章](#時刻の設定)もご覧ください。
</div></details>

<details><summary>Q3. タイムゾーンの確認方法は？</summary><div>

- Datetimeインスタンスに対し、メンバ関数`timezone()`を呼び出します。
    - インスタンスに適用されたタイムゾーン情報が std::string 型で返却されます。
</div></details>

<details><summary>Q4. 小数秒の計算には対応しているの？</summary><div>

- 小数秒には対応しておりません。小数秒は切り捨てられ、整数秒として扱われます。
</div></details>

<details><summary>Q5. 閏年、閏秒への対応は？</summary><div>

- 閏年には対応しておりますが、閏秒には対応していません。
- 閏年、閏秒の計算方法は、`struct tm`の計算方法に依存しています。
</div></details>

<details><summary>Q6. サマータイムの設定は？</summary><div>

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

</div></details>

<details><summary>Q7. 処理可能な時間幅は？</summary><div>

- 1970/1/1 0:00:00 UTC 〜 3000/1/2 0:00:00 UTC です。
</div></details>

<details><summary>Q8. このライブラリの実体は何？</summary><div>

- `struct tm` と `time_t`のラッピングライブラリです。
</div></details>

<details><summary>Q9. テストは行ったの？</summary><div>

- GoogleTestでテストコードを記述し、テストを行いました。
- バグがございましたらお気軽にコメントをお願いいたします。
</div></details>

## リンク
- [Eazy-Datetime-Cpp (GitHub)](https://github.com/n700a-dev/Easy-Datetime-Cpp)
- [クラス・関数一覧表はこちら](https://n700a-dev.github.io/Easy-Datetime-Cpp/annotated.html)


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