UE4 時間処理関連についてのメモ

# 概要
UnrealEngine の時間処理関連のメモです。
FDateTime型とFTimespan型のコードについての記述がほとんどです。


## 環境
Windows10
Visual Studio 2017
UnrealEngine 4.26


## 参考
以下を参考にさせて頂きました、ありがとうございます。

[UE4公式：FDateTime](https://docs.unrealengine.com/en-US/API/Runtime/Core/Misc/FDateTime/index.html)
[UE4公式：Timespan](https://docs.unrealengine.com/en-US/BlueprintAPI/Math/Timespan/index.html)
[UE4小ネタ：制限時間の実装方法を色々と](https://pafuhana1213.hatenablog.com/entry/2015/11/21/014427)
[時差計算](https://www.jisakeisan.com/)


# DateTime
日付と時刻を扱うための構造体。

▼関連ソース
"Engine\Source\Runtime\Core\Public\Misc\DateTime.h"
"Engine\Source\Runtime\Engine\Classes\Kismet\KismetMathLibrary.h"

▼BPメソッド例
![DateTimeBP.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/c597c339-0f3f-5f58-e366-9e4b9bba14e3.jpeg)


## 現在時刻
`Now` ノードでローカルタイムゾーンでの現在時刻を取得できます。
![NowBP.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/247a221e-2984-897d-a49f-b89f6fed6702.jpeg)
UTC(協定世界時)での現在時刻は`UtcNow`ノードで取得できます。
`Now`ノードでの時間の取得はローカルPCのタイムゾーンとサマータイムに対応しているようです。そしてタイムゾーン指定はできないと思われるためローカルと異なるタイムゾーンでの時間取得は`UtcNow`から自前で計算する必要があるようです。

`DateTime`構造体は以下の様になっています。
![breakDatetime.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/f7c08006-ee10-a6c4-0de0-871a039423ac.jpeg)

単純に `DateTime`型からテキストへ変換するためのノードがいろいろ用意されています。
![asDateBP.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/b7efebaf-2a2f-2c84-22f1-3d0be33c6d71.jpeg)

C++では以下の様に取得できます。
`UKismetMathLibrary::Now()` で取得していますが `FDateTime::Now()` でも同じです。

```cpp:現在時刻表示サンプル
#include "Kismet/KismetMathLibrary.h"
#include "Misc/DateTime.h"

// ローカルタイムゾーンの現在時刻の取得(FDateTime::Now()と同じ)
FDateTime _DateTime = UKismetMathLibrary::Now();
		
// UTCでの現在時刻の取得(FDateTime::UtcNow()と同じ)
//FDateTime _DateTime = UKismetMathLibrary::UtcNow();

UE_LOG(LogTemp, Log, TEXT("%d/%d/%d %d:%d %d"),
	_DateTime.GetYear(),
	_DateTime.GetMonth(),
	_DateTime.GetDay(),
	_DateTime.GetHour(),
	_DateTime.GetMinute(),
	_DateTime.GetSecond());
	
```

## Unix時間
DateTimeとUnix時間の変換等は以下の様に行うことができます。
以下コード例

```cpp:UNIX時間変換
// 現在時刻のUnix時間を取得する
uint64 _Unix = FDateTime::Now().ToUnixTimestamp();

// Unix時間からDateTimeを取得する
FDateTime _Time = FDateTime::FromUnixTimestamp(_Unix);

```


## 時刻のインポート/エクスポート
FString と FDateTime のインポート/エクスポートができます。
以下コード例

```cpp:エクスポート例
// 現在時刻を文字列にエクスポートする
FDateTime _DateTime = FDateTime::Now();
FString _Value;
_DateTime.ExportTextItem(_Value, _DateTime, nullptr, false, nullptr);

UE_LOG(LogTemp, Log, TEXT("ExportTextItem : %s"), *_Value );
```

```cpp:インポート例
// 指定時刻文字列をFDateTimeへインポートする
FString _DateStr = FString("2020.01.02-03.04.05");
const TCHAR* _Buffer = *_DateStr;
	
FDateTime _DateTime
_DateTime.ImportTextItem(_Buffer, 0, nullptr, nullptr);
```


## 文字列出力
各種文字列フォーマットで時刻の出力ができます。
以下コード例

```cpp:文字列出力例
FDateTime _DateTimeNow = FDateTime::Now();

UE_LOG(LogTemp, Log, TEXT("%s"), *_DateTimeNow.ToHttpDate() );	// Mon, 15 Mar 2021 15:15:33 GMT
UE_LOG(LogTemp, Log, TEXT("%s"), *_DateTimeNow.ToIso8601() );	// 2021-03-15T15:15:33.083Z
UE_LOG(LogTemp, Log, TEXT("%s"), *_DateTimeNow.ToString() );	// 2021.03.15-15.15.33

```



## その他メソッド
その他時刻関連処理で便利そうなメソッドがいろいろあります。

```cpp:DateTime.h
// 曜日定義（EDayOfWeek）の取得
EDayOfWeek GetDayOfWeek() const;
// 月定義（EMonthOfYear）の取得
EMonthOfYear GetMonthOfYear() const;

// その年の経過日数を取得する
int32 GetDayOfYear() const;
// 日付の時間部分を12時間制（1-12）で取得する
int32 GetHour12() const;
// その日の経過時間をFTimespanで取得する
FTimespan GetTimeOfDay() const;

// 午後か？
bool IsAfternoon() const;
// 午前か？
bool IsMorning() const;

// ユリウス日を取得
double GetJulianDay() const;
// 修正ユリウス日を取得
double GetModifiedJulianDay() const;

// 指定年月が何日か取得する
static int32 DaysInMonth(int32 Year, int32 Month);
// 指定年が何日あるか取得する
static int32 DaysInYear(int32 Year);
// うるう年かをチェックする
static bool IsLeapYear(int32 Year);
```


## 範囲(FDateRange)
範囲を扱うテンプレートクラス `TRange` に時刻用の定義 `FDateTime` が用意されています。

▼関連ソース
"\Engine\Source\Runtime\Core\Public\Math\Range.h"




# Timespan
2つの時刻の差などの日時に関する計算を扱うための構造体。

▼関連ソース
"Engine\Source\Runtime\Core\Public\Misc\Timespan.h"
"Engine\Source\Runtime\Engine\Classes\Kismet\KismetMathLibrary.h"

▼BPメソッド例
![TimeSpanBP.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/2a250ee5-8e45-7e26-f9e5-5dee6db9d18e.jpeg)

C++では以下のように現在時間と特定の時間との差を求めることができます。

```cpp:サンプル
// 現在時刻
FDateTime _DateTimeNow = UKismetMathLibrary::Now();
// 測定する基準時刻（2021年3月1日 12時0分0秒）
FDateTime _DateTimeStart = FDateTime(2021, 3, 1, 12, 0, 0);
// 差分を計算			
FTimespan _Span = _DateTimeNow - _DateTimeStart;
			
// 差分の時間（日時分秒）を表示
UE_LOG(LogTemp, Log, TEXT("%d %d %d %d"), _Span.GetDays(), _Span.GetHours(), _Span.GetMinutes(), _Span.GetSeconds());
// 差分の時間を合計秒数で表示
UE_LOG(LogTemp, Log, TEXT("%lf"), _Span.GetTotalSeconds());

```


## 経過時間の取得
`Timespan` で表される期間を各種単位で取得ができます。
返値の型が`double`なことに注意。

```cpp:Timespan.h
// 期間の合計日数を取得
double GetTotalDays() const;
// 期間の合計時間を取得
double GetTotalHours() const;
// 期間の合計分数を取得
double GetTotalMinutes() const;
// 期間の合計秒数を取得
double GetTotalSeconds() const;
```


## 経過時間からTimespanを生成
各種時間単位から `Timespan`を生成できます。
小数指定は細かい単位に変換されます。(例：1.5日 -> 1日12時間)

```cpp:Timespan.h
// 日数からTimespanを生成
static FTimespan FromDays(double Days);
// 時からTimespanを生成
static FTimespan FromHours(double Hours);
// 分からTimespanを生成
static FTimespan FromMinutes(double Minutes);
// 秒からTimespanを生成
static FTimespan FromSeconds(double Seconds);
```


## 時間間隔のインポート/エクスポート
文字列とTimespanとのインポート/エクスポートが可能です。
以下コード例

```cpp:エクスポート例
FTimespan _Span = FTimespan::FromDays(1.25f);
FString _Value;
_Span.ExportTextItem(_Value, _Span, nullptr, false, nullptr);
			
UE_LOG(LogTemp, Log, TEXT("Export: %s"), *_Value);
```

```cpp:インポート例
FTimespan _Span;
FString _SpanStr = FString("+42.15:11:36.457");
const TCHAR* _Buffer = *_SpanStr;
			
_Span.ImportTextItem(_Buffer, 0, nullptr, nullptr);
```


# 実装例
## 制限時間があるイベントコード例
`BeginTime` に開始時刻、 `LimitTime`にイベント制限時間を入れて残り時間を表示するコード例。開始時にその時の現在時刻を保持し、その差分(`GetTotalSeconds()`)から残り時間を計算しています。

![eventBP.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/09b97ac4-7eb9-d7bd-5adb-196fa921ab83.jpeg)

C++で書くと以下のような感じになるかと思われます。

```cpp:.h
class TEST_API AMyActor
{
	GENERATED_BODY()

	// ..省略..

public:
	// 開始時刻
	FDateTime _BeginTime;
	// 制限時間
	int32 _LimitTime;
};

```

```cpp:.cpp
void AMyActor::BeginPlay()
{
	_BeginTime = FDateTime::Now();
	_LimitTime = 5;
}

void AMyActor::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
	
	auto _Span = FDateTime::Now() - _BeginTime;
	if( _Span.GetTotalSeconds() > (double)_LimitTime){
		UE_LOG(LogTemp, Log, TEXT("Event!"));
	}else{
		UE_LOG(LogTemp, Log, TEXT("Count:%d"),  (int32)(_LimitTime - _Span.GetTotalSeconds()) );
	}
}
```


この処理はアプリ側の処理速度の影響を受けないため安定しています。



# まとめ
サマータイムは全世界から排除してください。:sun_with_face: