時刻・日付

時刻・日付の操作についてまとめます。
Examples:

時間を表す7つの型

Dには日時に関する型が複数定義されており、その性質によって役割が異なります。
以下の説明では、「システムタイム(日時)」「日時」「日付」「時刻」「時間」「モノトニックタイム(時間)」「期間」を明確に使い分けます。
// それぞれの型は以下のモジュールでimportできます
import core.time: Duration, MonoTime;
import std.datetime: SysTime, Date, TimeOfDay, DateTime, Interval;
Examples:

1. 「時間」 Duration

Durationは「時間」を表す型です。 ここでの「時間」とは、10秒間とか、3時間、のような、時間的な長さを表す表現です。 各種単位の「時間」を得るには、hours関数や、seconds関数のようなものを core.time からimportして使います。
import core.time: Duration, hours, minutes, seconds, msecs, usecs, nsecs;

// 3600秒間なら以下のように得ます。
auto dur = 3600.seconds;

// 3600秒間は、1時間と同等です。
assert(dur == 1.hours);

// 得た「時間」の型は Duration です。
static assert(is(typeof(60.minutes) == Duration));
Examples:

2. 「システムタイム(日時)」 SysTime

SysTimeは「時差を考慮した日時」を表す型です。 現在日時を取得するには、 std.datetime でimportできるClockのメソッドを使用します。
import std.datetime: Clock, SysTime;

// Clock.currTimeでは、OSで設定されたタイムゾーンに基づく時差を持った「ローカル日時」が得られます。
auto localTime = Clock.currTime();

// 時計から得た「時刻」の型は SysTime です
static assert(is(typeof(localTime) == SysTime));
Examples:

3. 「日時」 DateTime

DateTimeは「時差を考慮しない日時」を表す型です。 SysTimeは時差を考慮しますが、DateTimeは考慮せず、ただ 何年何月何日の何時何分何秒という情報だけを持っています。 ミリ秒以下の情報も持ちません。
import std.datetime: DateTime, SysTime;

auto xmasEve = DateTime(2019, 12, 24, 21, 0, 0);
static assert(is(typeof(xmasEve) == DateTime));

// DateTimeからSysTimeを作ることもできます
auto xmasEve2 = SysTime(xmasEve);

// 逆に、SysTimeからDateTimeは、castで変換できます
auto xmasEve3 = cast(DateTime)xmasEve2;
Examples:

4. 「日付」 Date

Dateは、DateTimeのうち、何年何月何日(つまり日付)の部分です。
import std.datetime: Date;
auto newyear = Date(2020, 1, 1);

// Dateは差分を求めることもできます。
//
{
    // 一ヶ月前の日付を求めます。
    auto date = Date(2019, 8, 31);
    assert(date.add!"months"(-1) == Date(2019, 7, 31));
}

{
    // 一ヶ月前を表す方法は、可能であれば前月の同日同時刻を表し、
    // 前日に同日が存在しない場合は差分を計算して付け足します。
    // なので3月31日は以下のようになります。
    auto date = Date(2019, 3, 31);
    assert(date.add!"months"(-1) == Date(2019, 3, 3));

    // うるう年のときの結果も異なります。
    //
    date = Date(2019, 3, 29);
    assert(date.add!"months"(-1) == Date(2019, 3, 1));
    date = Date(2016, 3, 29);
    assert(date.add!"months"(-1) == Date(2016, 2, 29));
}
Examples:

5. 「時刻」TimeOfDay

TimeOfDayは、DateTimeのうち、「何時何分何秒」の部分です。
import std.datetime: TimeOfDay, Date, DateTime;
auto noon = TimeOfDay(12, 0, 0);
// DateとTimeOfDayを組み合わせて、DateTimeが作れます
auto newyearNoon = DateTime(Date(2020, 1, 1), noon);
Examples:

6. 「期間」 Interval

2つの「時刻」の間の時間を「期間」とすることができます。
import std.datetime: Interval, DateTime;
auto interval = Interval!DateTime(
    DateTime(2019, 12, 25,  0, 0, 0),
    DateTime(2020, 1,  1,  12, 0, 0));
Examples:

7. 「モノトニックタイム(時間)」 MonoTime

2つ目の「時間」 MonoTime は、ベンチマークやゲームのFPSの計算などで使用される、 高精度な時間単位を扱います。 また、NTPによる巻き戻しがおこらず、単調増加(monotonic)であることが特徴です。 「時間」を測定するストップウォッチで得られます。
import core.time: Duration;
import std.datetime.stopwatch: StopWatch, AutoStart;
// ストップウォッチの定義と同時に計測開始
auto sw = StopWatch(AutoStart.yes);
// peekで計測開始からの時間を得ます
auto monotime = sw.peek();

// totalメソッドで各単位の整数が得られます
assert(monotime.total!"msecs" == 0);

// Durationにキャストすることもできます
auto peekDur = cast(Duration)monotime;
Examples:

SysTimeと文字列の変換

import std.datetime;
import std.format;

auto tim = SysTime(DateTime(2019, 5, 1, 10, 0, 0));

// 日本でよく見る時刻の文字列表現 YYYY/MM/DD hh:mm:ss.SSS
// にするには以下のようにします
auto timstr = format!"%04d/%02d/%02d %02d:%02d:%02d.%03d"(
    tim.year, tim.month,  tim.day,
    tim.hour, tim.minute, tim.second,
    tim.fracSecs.total!"msecs");
assert(timstr == "2019/05/01 10:00:00.000");

// 特にフォーマットを気にしないで、わかればいい程度なら toString
auto timstrDefault = tim.toString();

// YYYY-Mon-DD HH:MM:SS.FFFFFFFTZ 形式
auto timstrSimple = tim.toSimpleString();
assert(timstrSimple == "2019-May-01 10:00:00");
// 逆変換
auto timSimple    = SysTime.fromSimpleString(timstrSimple);
assert(timSimple == tim);

// ISOで定められているやつ YYYYMMDDTHHMMSS.FFFFFFFTZ
auto timstrISO = tim.toISOString();
assert(timstrISO == "20190501T100000");
// 逆変換
auto timISO    = SysTime.fromISOString(timstrISO);
assert(timISO == tim);

// ISOで定められているやつ(別版) YYYY-MM-DDThh:MM:SS.FFFFFFFTZ
auto timstrISOExt = tim.toISOExtString();
assert(timstrISOExt == "2019-05-01T10:00:00");
// 逆変換
auto timISOExt    = SysTime.fromISOExtString(timstrISOExt);
assert(timISOExt == tim);
Examples:

Durationの使い方

import core.time;
import std.datetime;
import std.format;

// 平成元年 1/8 10時
auto timA = SysTime(DateTime(1989, 1, 8, 10, 0, 0));
// 令和元年 5/1 10時
auto timB = SysTime(DateTime(2020, 5, 1, 10, 0, 0));
// 2つの「日時」から「時間」を得る
auto dur = timB - timA;

// 平成の秒数
assert(dur.total!"seconds" == 988_070_400);

// 平成の時間を日数とミリ秒に分解
// 注) Durationは時刻情報ではないので、うるう年や1か月に何日あるかなどの
//     計算ができないため、days, hours, minutes, seconds...といった
//     長さが変化しない"日"以下の単位にしか分解できません。
auto heiseiTimes = dur.split!("days", "msecs");
assert(heiseiTimes.days == 365*31 + 121); // 約31年とちょっと
assert(heiseiTimes.msecs == 0);
Examples:

StopWatchの使い方

import core.thread;
import std.datetime.stopwatch;

void wait() @trusted
{
    Thread.sleep(1.msecs);
}

// ストップウォッチを作成
StopWatch sw;

// running プロパティで計測中かどうかが確認できる
assert(!sw.running);

// ストップウォッチで時間計測を開始
sw.start();
assert(sw.running);

// 計測中のストップウォッチで計測結果を確認
auto t1 = sw.peek();
wait();

// ストップウォッチを一時停止
sw.stop();
assert(!sw.running);

// (停止中は計測値が進みません)
auto t2 = sw.peek();
assert(t2 > t1);
wait();

// 停止中のストップウォッチの計測結果を確認
// 計測値が進んでいないので、1ナノ秒も変わらず完全に一致する。
auto t3 = sw.peek();
assert(t2 == t3);

// ストップウォッチを再開
sw.start();
assert(sw.running);
wait();

// ストップウォッチを確認
auto t4 = sw.peek();
assert(t4 > t3);

// 計測中にストップウォッチをリセット
sw.reset();

assert(sw.peek() <= t4);
Examples:

タイムゾーンの扱い

import core.time : hours;
import std.datetime.date : DateTime;
import std.datetime.systime: Clock, SysTime;
import std.datetime.timezone : LocalTime, SimpleTimeZone, UTC;

// LocalTimeクラスはプログラムが実行されているのシステムのローカルタイムゾーンを表す。
// Clockで取得できる現在時刻にはこのタイムゾーン情報が含まれる。
auto tim1 = Clock.currTime();
assert(tim1.timezone is LocalTime());

// UTCクラスはUTCのタイムゾーンを表す。
// タイムゾーンを指定して作成されたSysTimeは時差情報を含む。
auto tim2 = SysTime(DateTime(2019, 5, 1, 10, 0, 0), UTC());
assert(tim2.toSimpleString() == "2019-May-01 10:00:00Z");

// ローカル日時は toUTC でUTC基準に変更できる
auto utc = tim1.toUTC();
assert(utc.timezone is UTC());
assert(utc.toUTC() == utc); // 複数回実行しても変化しない

// UTC日時は toLocalTime でローカル日時に変換できる
auto local = tim2.toLocalTime();
assert(local.timezone is LocalTime());
assert(local.toLocalTime() == local); // 複数回実行しても変化しない

// UTCから任意のタイムゾーンに変換する。
auto JST = new immutable SimpleTimeZone(9.hours);
auto tim3 = tim2.toOtherTZ(JST);
assert(tim3.toSimpleString() == "2019-May-01 19:00:00+09:00");
Examples:

「今日」の日付を得る

import std.datetime : Clock, Date;

// 現在のシステム日時(ローカル)を得て、日付部分を取り出すことで今日の日付を得ます。
Date today = cast(Date) Clock.currTime();
Examples:

値の構築方法に関するパターン

std.datetimeの各型は、値の直接指定か、ベースとなる型+付加情報、というパターンでコンストラクタを使って構築します。
import std.datetime;

// 直接値を指定できるタイプの型
//     Date, TimeOfDay, DateTime
auto date = Date(2020, 10, 20);
auto time = TimeOfDay(12, 34, 56);
auto dt1 = DateTime(2021, 12, 31, 10, 20, 30);

// 他の値から合成するタイプの型
//     DateTime, SysTime, Interval

// 日時(DateTime) = 日付(Date) + 時刻(TimeOfDay、未指定の場合は 0時0分0秒)
auto dt2 = DateTime(date); // 0時0分0秒の日時を得るにはこれが簡単です
auto dt3 = DateTime(date, time);

// 日時(SysTime)  = 日時(DateTime) + タイムゾーン(TimeZone、未指定の場合は OSのタイムゾーンに基づく時差)
auto st1 = SysTime(dt1); // ローカル日時として、OSのタイムゾーンから時差が設定されます
auto st2 = SysTime(dt2, UTC());

// 期間 = 開始時点 + 終了時点
auto interval = Interval!DateTime(dt2, dt1);
Examples:

DateTimeからDateを得るなど、上位の複合型から一部を取り出す方法

多くの場合、目的の部分型を得るためのプロパティがあります。 DateTime型の場合は datetimeOfDay プロパティで取り出します。 Interval型の場合は beginend プロパティで取り出します。
SysTime型の場合は、DateTime型やDate型へ直接キャストすることで部分型を得ることができます。
import std.datetime;

// 直接構築したあと、日付や時刻部分を取り出す
auto dt = DateTime(2021, 12, 31, 10, 20, 30);
auto date = dt.date;
auto time = dt.timeOfDay;
assert(date.year == 2021 && date.month == 12 && date.day == 31);
assert(time.hour == 10 && time.minute == 20 && time.second == 30);

auto interval = Interval!Date(Date(2020, 1, 1), Date(2021, 12, 31));
assert(interval.begin == Date(2020, 1, 1));
assert(interval.end == Date(2021, 12, 31));

auto st = Clock.currTime();
Date date2 = cast(Date) st;
TimeOfDay time2 = cast(TimeOfDay) st;
DateTime dt2 = cast(DateTime) st;

assert(date2.year == st.year && date2.month == st.month && date2.day == st.day);
assert(time2.hour == st.hour && time2.minute == st.minute && time2.second == st.second);
assert(dt2.year == st.year && dt2.month == st.month && dt2.day == st.day);
assert(dt2.hour == st.hour && dt2.minute == st.minute && dt2.second == st.second);
Examples:

Unix timeとSysTimeの相互変換方法、Unix timeの作成方法

Unix timeは UTC時間の1970年1月1日 0時0分0秒からの秒数となります。 これはタイムゾーンがUTC基準と定められていることから、 タイムゾーン情報を持つ SysTimeのみ が相互変換の方法を提供しています。
import std.datetime;

// ローカル時刻を想定したUnix時間をSysTimeへ変換
long unixtime = 1640913630; // 2021-12-31 01:20:30 + 00:00

// テスト環境が不定のためタイムゾーンを固定します
immutable localTZ = new SimpleTimeZone(9.hours, "JST");

// fromUnixTimeにタイムゾーンを指定するとSysTimeに反映されます。
// Localのタイムゾーンは省略するとローカルタイムゾーン(日本ならJSTで同じく+09:00)です。
auto stLocal = SysTime.fromUnixTime(unixtime, localTZ); // 2021-12-31 10:20:30 + 09:00
auto stUTC = SysTime.fromUnixTime(unixtime, UTC());     // 2021-12-31 01:20:30 + 00:00

assert(cast(Date) stLocal == Date(2021, 12, 31));
assert(cast(TimeOfDay) stLocal == TimeOfDay(10, 20, 30));
assert(stLocal.timezone !is LocalTime()); // 構築時にタイムゾーンを省略した場合はLocalTimeになります
assert(cast(Date) stUTC == Date(2021, 12, 31));
assert(cast(TimeOfDay) stUTC == TimeOfDay(1, 20, 30));
assert(stUTC.timezone is UTC());

// Unix時間にする場合
auto utFromLocal = stLocal.toUnixTime();
auto utFromUTC = stUTC.toUnixTime();

assert(utFromLocal == unixtime);
assert(utFromUTC == unixtime);

// 日時を指定して Unix time を作成する
auto ut1 = SysTime(DateTime(1970, 1, 1, 0, 0, 0), UTC()).toUnixTime();
assert(ut1 == 0);
// 元のSysTimeがローカル時刻でもUnix timeはUTC基準です
auto ut2 = SysTime(DateTime(2021, 12, 31, 23, 59, 59), localTZ).toUnixTime();
assert(ut2 == 1640962799);
Examples:

日時文字列を組み込みの日時型(SysTime)に変換する簡便な方法

外部ライブラリを利用できる場合、dateparser パッケージの parse 関数を利用する方法が簡単です。
See_Also: https://code.dlang.org/packages/dateparser
import dateparser;
import std.datetime;

// SysTime型が得られます
SysTime date1 = parse("2023-01-01");
SysTime date2 = parse("2023/01/01");
SysTime date3 = parse("2023/01/01 12:34:56"); // スペース区切りの時刻もOKです

assert(date1 == SysTime(DateTime(2023, 1, 1)));
assert(date2 == SysTime(DateTime(2023, 1, 1)));
assert(date3 == SysTime(DateTime(2023, 1, 1, 12, 34, 56)));