日付と時刻2

前ページから続いて、DateTime構造体TimeSpan構造体DateTimeOffset構造体について説明します。

日時の比較

一致/不一致の判定

同じ日時データ型同士は等値演算子(==)または不等値演算子(!=)で一致/不一致を判定できます。


DateTime dt1 = new DateTime(2001, 2, 3, 4, 5, 6);
DateTime dt2 = new DateTime(2001, 2, 3, 4, 5, 6);
Console.WriteLine($"dt1: {dt1}");
Console.WriteLine($"dt2: {dt2}");

if (dt1 == dt2)
    Console.WriteLine("dt1とdt2は同じ日時です");
else
    Console.WriteLine("dt1とdt2は異なる日時です");

Console.WriteLine();
TimeSpan ts1 = new TimeSpan(3, 0, 0);
TimeSpan ts2 = new TimeSpan(0, 3, 0, 0);
Console.WriteLine($"ts1: {ts1}");
Console.WriteLine($"ts2: {ts2}");

if (ts1 == ts2)
    Console.WriteLine("ts1とts2は同じ時間間隔です");
else
    Console.WriteLine("ts1とts2は異なる時間間隔です");
dt1: 2001/02/03 4:05:06
dt2: 2001/02/03 4:05:06
dt1とdt2は同じ日時です

ts1: 03:00:00
ts2: 03:00:00
ts1とts2は同じ時間間隔です

DateTime型同士の比較ではKindプロパティは考慮されず、異なる値であっても日時の情報が同じならば一致と判定されます。


DateTime dt1 = new DateTime(2001, 2, 3, 4, 5, 6, DateTimeKind.Local);
DateTime dt2 = new DateTime(2001, 2, 3, 4, 5, 6, DateTimeKind.Utc);
Console.WriteLine($"dt1: {dt1} {dt1.Kind}");
Console.WriteLine($"dt2: {dt2} {dt2.Kind}");

if (dt1 == dt2)
    Console.WriteLine("dt1とdt2は同じ日時です");
else
    Console.WriteLine("dt1とdt2は異なる日時です");
dt1: 2001/02/03 4:05:06 Local
dt2: 2001/02/03 4:05:06 Utc
dt1とdt2は同じ日時です

DateTimeOffset型同士の比較ではUTC時刻(UtcDateTimeプロパティ)で判定が行われます。
DateTime型とDateTimeOffset型の比較は、DateTime型がDateTimeOffset型に暗黙的型変換された上で比較されます。
つまりKindプロパティがLocalまたはUnspecifiedならばシステムローカルの時差情報、Utcならば時差ゼロとして比較が行われます。


DateTimeOffset dto1 = new DateTimeOffset(
    2001, 2, 3, 4, 5, 6, TimeSpan.FromHours(9));
DateTimeOffset dto2 = new DateTimeOffset(
    2001, 2, 3, 4, 5, 6, TimeSpan.FromHours(8));
Console.WriteLine($"dto1: {dto1}");
Console.WriteLine($"dto2: {dto2}");

if (dto1 == dto2)
    Console.WriteLine("dto1とdto2は同じ日時です");
else
    Console.WriteLine("dto1とdto2は異なる日時です");

Console.WriteLine();
DateTime dt = new DateTime(2001, 2, 3, 4, 5, 6, DateTimeKind.Local);
Console.WriteLine($"dt:   {dt} {dt.Kind}");
Console.WriteLine($"dto1: {dto1}");
Console.WriteLine($"dto2: {dto2}");

if (dt == dto1)
    Console.WriteLine("dtとdto1は同じ日時です");
else
    Console.WriteLine("dtとdto1は異なる日時です");

if (dt == dto2)
    Console.WriteLine("dtとdto2は同じ日時です");
else
    Console.WriteLine("dtとdto2は異なる日時です");
dto1: 2001/02/03 4:05:06 +09:00
dto2: 2001/02/03 4:05:06 +08:00
dto1とdto2は異なる日時です

dt:   2001/02/03 4:05:06 Local
dto1: 2001/02/03 4:05:06 +09:00
dto2: 2001/02/03 4:05:06 +08:00
dtとdto1は同じ日時です
dtとdto2は異なる日時です

この実行結果は日本時間(JST、UTC+0900)のシステム上で実行した場合です。

なお、日付だけや時刻だけの一致判定を行いたい場合はDateプロパティ、TimeOfDayプロパティを使用します。
(→日付と時刻1#年月日/時刻)


DateTime dt1 = new DateTime(2001, 2, 3, 4, 5, 6);
DateTime dt2 = new DateTime(2001, 2, 3, 7, 8, 9);
Console.WriteLine($"dt1: {dt1}");
Console.WriteLine($"dt2: {dt2}");

if (dt1.Date == dt2.Date)
    Console.WriteLine("dt1とdt2は同じ日付です");
else
    Console.WriteLine("dt1とdt2は異なる日付です");

if (dt1.TimeOfDay == dt2.TimeOfDay)
    Console.WriteLine("dt1とdt2は同じ時刻です");
else
    Console.WriteLine("dt1とdt2は異なる時刻です");
dt1: 2001/02/03 4:05:06
dt2: 2001/02/03 7:08:09
dt1とdt2は同じ日付です
dt1とdt2は異なる時刻です

前後関係の比較

どちらの日時情報がカレンダー上で先になるか、という前後関係を比較したい場合はCompareToメソッドを使用します。


DateTime dt1 = new DateTime(2001, 2, 3, 4, 5, 6);
DateTime dt2 = new DateTime(2001, 2, 3, 7, 8, 9);
Console.WriteLine($"dt1: {dt1}");
Console.WriteLine($"dt2: {dt2}");

int compare = dt1.CompareTo(dt2);
if (compare < 0)
    Console.WriteLine("dt1はdt2より前です");
else if (compare > 0)
    Console.WriteLine("dt1はdt2より後です");
else
    Console.WriteLine("dt1とdt2は同じ日時です");
dt1: 2001/02/03 4:05:06
dt2: 2001/02/03 7:08:09
dt1はdt2より前です

CompareToメソッドは、比較されるオブジェクト(左側)が、比較するオブジェクト(右側)よりも順序が前の場合は0未満の値を返します。
順序が後になる場合は0より大きい値を返します。
順序が同じ場合、つまり日時が一致する場合は0を返します。

DateTime型のKindプロパティの値は考慮されません。

CompareToメソッドはTimeSpan型とDateTimeOffset型でも同様に使用可能です。
DateTimeOffset型ではUTC時刻(UtcDateTimeプロパティ)で比較が行われます。


DateTimeOffset dto1 = new DateTimeOffset(
    2001, 2, 3, 0, 0, 0, new TimeSpan(3, 0, 0));
DateTimeOffset dto2 = new DateTimeOffset(
    2001, 2, 3, 0, 30, 0, new TimeSpan(3, 30, 0));
Console.WriteLine($"dto1: {dto1}");
Console.WriteLine($"dto2: {dto2}");

int compare = dto1.CompareTo(dto2);
if (compare < 0)
    Console.WriteLine("dto1はdto2より前です");
else if (compare > 0)
    Console.WriteLine("dto1はdot2より後です");
else
    Console.WriteLine("dto1とdto2は同じ日時です");
dto1: 2001/02/03 0:00:00 +03:00
dto2: 2001/02/03 0:30:00 +03:30
dto1とdto2は同じ日時です

日時の計算

日時情報は加算や減算によって、ある日時と日時との差(期間)の計算や、○日後の日時の取得などが可能です。


//2001/2/3 4:00:00
DateTime dt1 = new DateTime(2001, 2, 3, 4, 0, 0);

//2001/2/3 5:30:00
DateTime dt2 = new DateTime(2001, 2, 3, 5, 30, 0);

//「DateTIme + DateTime」はできない
//TimeSpan tsAdd = dt2 + dt1;

//「DateTime - DateTime」はTimeSpan型
TimeSpan tsSub = dt2 - dt1;

//TimeSpan型に2時間を加算
TimeSpan tsAdd = tsSub + new TimeSpan(2, 0, 0);

DateTime dtAdd = dt1 + tsAdd;

Console.WriteLine($"{dt1}\t(dt1)");
Console.WriteLine($"{dt2}\t(dt2)");
Console.WriteLine($"{tsSub}\t\t(dt2 - dt1)");
Console.WriteLine($"{tsAdd}\t\t(dt2 - dt1 + 2:00:00)");
Console.WriteLine($"{dtAdd}\t(dt1 + (dt2 - dt1 + 2:00:00))");
2001/02/03 4:00:00      (dt1)
2001/02/03 5:30:00      (dt2)
01:30:00                (dt2 - dt1)
03:30:00                (dt2 - dt1 + 2:00:00)
2001/02/03 7:30:00      (dt1 + (dt2 - dt1 + 2:00:00))

日時情報同士を加算をすることは通常行わないので、DateTime型同士の加算はできません。
DateTime型同士の減算は日時の差(期間)がTimeSpan型で返されます。

DateTime型とTimeSpan型は加算も減算も可能で、結果はDateTime型になります。
TimeSpan型に同士の加算/減算の結果はTimeSpan型です。

DateTimeOffset型の場合、減算(時間の差の取得)はUTC時刻(UtcDateTimeプロパティ)を元に計算されます。
加算(TimeSpan型)は単純に時刻部分に加算され、時差情報には影響しません。


DateTimeOffset dto1 = new DateTimeOffset(
    2001, 2, 3, 4, 0, 0, TimeSpan.FromHours(3));
DateTimeOffset dto2 = new DateTimeOffset(
    2001, 2, 3, 5, 30, 0, TimeSpan.FromHours(4));

TimeSpan tsSub = dto2 - dto1;
DateTimeOffset dtoAdd = dto1 + TimeSpan.FromHours(5);

Console.WriteLine($"{dto1}\t(dto1)");
Console.WriteLine($"{dto2}\t(dto2)");
Console.WriteLine($"{tsSub}\t\t\t(dto2 - dto1)");
Console.WriteLine($"{dtoAdd}\t(dto1 + 5:00:00)");
2001/02/03 4:00:00 +03:00       (dto1)
2001/02/03 5:30:00 +04:00       (dto2)
00:30:00                        (dto2 - dto1)
2001/02/03 9:00:00 +03:00       (dto1 + 5:00:00)

日時計算用のメソッド

日時は+/-演算子を使用する以外にもメソッドを使用することもできます。
〇時間後などの単純な計算はこちらのほうが簡単です。

メソッド名 説明
Add(TimeSpan) TimeSpanの値を加算する
AddYears(int) 年数を加算する
AddMonths(int) 月数を加算する
AddDays(double) 日数を加算する
AddHours(double) 時間数を加算する
AddMinutes(double) 分数を加算する
AddSeconds(double) 秒数を加算する
AddMilliseconds(double) ミリ秒数を加算する
AddMicroseconds(double) マイクロ秒数を加算する
AddTicks(long) タイマー刻み数を加算する
Subtract(TimeSpan) TimeSpanの値を減算する
Subtract(DateTime) DateTimeの値を減算する
(戻り値はTimeSpan型)

これらのメソッドは既存のDateTime型オブジェクトの値は変更せずに、計算結果を新しいオブジェクトで返します。


DateTime dt = new DateTime(2020, 1, 2, 3, 4, 56);

//+1年
DateTime dt1 = dt.AddYears(1);

//-3時間
DateTime dt2 = dt.AddHours(-3);

//+0.5分(+30秒)
DateTime dt3 = dt.AddMinutes(0.5);

Console.WriteLine(dt);
Console.WriteLine($"{dt1} (AddYears(1))");
Console.WriteLine($"{dt2} (AddHours(-3))");
Console.WriteLine($"{dt3} (AddMinutes(0.5))");
2020/01/02 3:04:56
2021/01/02 3:04:56 (AddYears(1))
2020/01/02 0:04:56 (AddHours(-3))
2020/01/02 3:05:26 (AddMinutes(0.5))

計算によって存在しない日付になる場合は、存在する日付になるまで日数が減算されます。
例えば3月31に1か月を加算すると4月30日になります。


DateTime dt = new DateTime(2020, 3, 31);
Console.WriteLine(dt);
Console.WriteLine($"{dt.AddMonths(1)} (AddMonths(1))");
Console.WriteLine($"{dt.AddMonths(-1)} (AddMonths(-1))");
2020/03/31 0:00:00
2020/04/30 0:00:00 (AddMonths(1))
2020/02/29 0:00:00 (AddMonths(-1))

※2020年はうるう年

UNIX時間

コンピューターでは、時間の基準にUNIX時間(UNIX時刻)というものを用いることが多くあります。
UNIX時間は「1970年1月1日 午前0時0分0秒」(UNIXエポック)を起点とした経過秒数で時刻を管理するものです。

DateTimeOffset構造体

DateTimeOffset構造体では、.NET FrameWork4.6以降、.NET Coreは1.0から、UNIX時間を扱うためのメソッドが用意されています。

ToUnixTimeSecondsメソッドは、DateTimeOffsetオブジェクトをUNIX時間(long型)に変換します。


//1970年1月1日 0:00:00
DateTimeOffset dto = new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero);

//UNIX時間に変換
long unixTime = dto.ToUnixTimeSeconds();
Console.WriteLine(unixTime);

dto = new DateTimeOffset(2001, 2, 3, 4, 5, 6, TimeSpan.Zero);
Console.WriteLine(dto.ToUnixTimeSeconds());

dto = new DateTimeOffset(2001, 2, 3, 4, 5, 6, TimeSpan.FromHours(9));
Console.WriteLine(dto.ToUnixTimeSeconds());
0
981173106
981140706

UNIX時間はUTC時刻を元にして変換されます。

UNIX時間からDateTimeOffsetオブジェクトを作成するにはDateTimeOffset.FromUnixTimeSecondsメソッドを使用します。


//1970年1月1日 0:00:00
DateTimeOffset dto1 = new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero);
long unixTime = dto1.ToUnixTimeSeconds();
DateTimeOffset dto2 = DateTimeOffset.FromUnixTimeSeconds(unixTime);
Console.WriteLine(dto2);

dto1 = new DateTimeOffset(2001, 2, 3, 4, 5, 6, TimeSpan.Zero);
dto2 = DateTimeOffset.FromUnixTimeSeconds(dto1.ToUnixTimeSeconds());
Console.WriteLine(dto2);

dto1 = new DateTimeOffset(2001, 2, 3, 4, 5, 6, TimeSpan.FromHours(9));
dto2 = DateTimeOffset.FromUnixTimeSeconds(dto1.ToUnixTimeSeconds());
Console.WriteLine(dto2);
1970/01/01 0:00:00 +00:00
2001/02/03 4:05:06 +00:00
2001/02/02 19:05:06 +00:00

上記のコードは作成したDateTimeOffsetオブジェクトからUNIX時間を取得し、それをすぐにDateTimeOffsetオブジェクトに変換しています。
変換後の時差情報はUTC時刻になります。

これらのメソッドはUNIX時間を秒で扱いますが、ミリ秒で扱うToUnixTimeMillisecondsメソッド、DateTimeOffset.FromUnixTimeMillisecondsメソッドもあります。
精度が1000倍になる以外は使い方は同じです。

UNIXエポック

UNIXエポックを表すDateTimeOffsetオブジェクトはDateTimeOffset.UnixEpochという定数で取得できます。
この定数は.NET Core 2.1以降で使用できます。
.NET Frameworkには存在しません。


DateTimeOffset dto = DateTimeOffset.UnixEpoch;
Console.WriteLine(dto);
1970/01/01 0:00:00 +00:00

DateTime構造体

DateTime構造体にはUNIX時間を扱うメソッドは用意されていません。

.NET Framework4.6以降、.NET Core環境なら、DateTime型をDateTimeOffset型に変換した上で標準のメソッドを使う方法があります。
標準メソッドが使用できない場合でも簡単な計算でUNIX時間を扱うことができるので、その例を示します。


//UNIXエポック
//.NET Core2.1以降では
//「DateTime.UnixEpoch」という定数が用意されているので
//そちらを使う
static readonly DateTime UNIX_EPOCH =
    new DateTime(1970, 1, 1);

//DateTimeオブジェクトから
//UNIX時間を取得する
static long GetUnixTime(DateTime dateTime)
{
    //UTC時刻に変換
    //システムのローカル時刻が使用される
    dateTime = dateTime.ToUniversalTime();

    //UNIXエポックからの経過時間
    TimeSpan elapsed = dateTime - UNIX_EPOCH;
    
    //秒数に変換
    return (long)elapsed.TotalSeconds;
}

//UNIX時間から
//DateTimeオブジェクトを取得する
//第二引数isUTCが真ならUTC時刻、偽ならローカル時刻を返す
static DateTime DateTimeFromUnixTime(long unixTime, bool isUTC = true)
{
    //UNIXエポック+UNIX時間でUTC時刻が得られる
    DateTime dateTime = UNIX_EPOCH + TimeSpan.FromSeconds(unixTime);

    //引数isUTCが偽ならローカル時刻に変換して返す
    return isUTC ? 
        dateTime : dateTime.ToLocalTime();
}

まずUNIXエポックとなるDateTimeオブジェクトを用意します。
これは複数のメソッドから使用するのでメソッド外(フィールド)で定数にしています。

処理は単純で、UNIX時間を取得する場合はUTC時刻に変換したDateTimeオブジェクトからUNIXエポックを減算します。
UNIX時間をDateTime型に変換する場合はUNIXエポックを示すDateTimeオブジェクトにUNIX時間を加算します。

日時を文字列に変換する

DateTime型オブジェクトはConsole.Writelineメソッドに渡すと日時情報を文字列として表示することができますが、これは内部的にDateTimeオブジェクトのToStringというメソッドが呼ばれています。
これは名前の通り、データを文字列に変換するメソッドです。


DateTime dt = new DateTime(2001, 2, 3, 4, 5, 6);
string dateString = dt.ToString();
Console.WriteLine(dateString);
2001/02/03 4:05:06

このToStringメソッドは引数を使用して出力される日時情報の形式を変更することができます。

カルチャ

日付の表示形式は国や地域によって違いがあります。
例えば日本では「年/月/日」の順番が一般的ですが、アメリカでは「月/日/年」、イギリスでは「日/月/年」が一般的です。
このような国や地域によって異なるものをカルチャ(Culture、カルチャー)といいます。

DateTime型オブジェクトをそのまま文字列に変換すると、システムのカルチャが使用されます。
これを別の形式に変更したい場合はCultureInfoクラスのCultureInfo.GetCultureInfoメソッドを使用します。
(カルチャについてはデータ型の相互変換#通貨の変更も参照)

以下は日付を日本形式、アメリカ形式、イギリス形式で表示する例です。


using System.Globalization;

DateTime dt = new DateTime(2001, 2, 3, 4, 5, 6);

//日本語
string date1 = dt.ToString(CultureInfo.GetCultureInfo("ja-JP"));
//英語(アメリカ)
string date2 = dt.ToString(CultureInfo.GetCultureInfo("en-US"));
//英語(イギリス)
string date3 = dt.ToString(CultureInfo.GetCultureInfo("en-GB"));

Console.WriteLine(date1);
Console.WriteLine(date2);
Console.WriteLine(date3);
2001/02/03 4:05:06
2/3/2001 4:05:06 AM
03/02/2001 04:05:06

書式指定文字列

ToStringメソッドは引数に書式文字列を指定することで、出力される日時情報の形式を変更することができます。
書式には「標準日時書式」と「カスタム日時書式」の二種類があります。

以下、例に使用する日時は「2001年2月3日 4時5分6秒012ミリ秒」を使用します。


DateTime dt = new DateTime(
    2001, 2, 3, 4, 5, 6, 12, DateTimeKind.Local);

書式指定文字列とカルチャを同時に指定する場合は書式指定文字列を先に記述します。


string date = dt.ToString(
    "d", CultureInfo.GetCultureInfo("en-US"));

標準日時書式

標準日時書式はC#があらかじめ用意している日時の書式で、ToStringメソッドの引数に任意の一文字を指定します。


//短い形式の日付
string date = dt.ToString("d");
書式指定子 説明 例(ja-JP/en-US)
d 短い形式の日付 2001/02/03
2/3/2001
D 長い形式の日付 2001年2月3日
Saturday, February 3, 2001
f 完全な形式の日付と短い時刻 2001年2月3日 4:05
Saturday, February 3, 2001 4:05 AM
F 完全な形式の日付と長い時刻 2001年2月3日 4:05:06
Saturday, February 3, 2001 4:05:06 AM
g 一般形式の日付と短い時刻 2001/02/03 4:05
2/3/2001 4:05 AM
G 一般形式の日付と長い時刻 2001/02/03 4:05:06
2/3/2001 4:05:06 AM
m、M 月と日 2月3日
February 3
o、O ラウンドトリップする日付と時刻 2001-02-03T04:05:06.0120000+09:00
2001-02-03T04:05:06.0120000+09:00
(KindプロパティがUtcの場合は末尾は"Z"、
KindプロパティがUnspecifiedの場合はタイムゾーンなし)
r、R RFC1123形式 Sat, 03 Feb 2001 04:05:06 GMT
Sat, 03 Feb 2001 04:05:06 GMT
s 並べ替え可能な日付と時刻 2001-02-03T04:05:06
2001-02-03T04:05:06
t 短い形式の時刻 4:05
4:05 AM
T 長い形式の時刻 4:05:06
4:05:06 AM
u 並べ替え可能な日付と時刻(UTC) 2001-02-03 04:05:06Z
2001-02-03 04:05:06Z
U 完全な日付と時刻(UTC) 2001年2月3日 4:05:06
Saturday, February 3, 2001 4:05:06 AM
y、Y 年と月 2001年2月
February 2001
その他の一文字 例外(FormatException)をスロー (なし)

「r」「R」の出力文字列の末尾の「GMT」、および「u」の出力文字列の末尾の「Z」はUTCを意味します。
しかしDateTimeオブジェクトのKindプロパティがDateTimeKind.Local(またはUnspecified)の場合に、これらの書式指定で出力したとしても自動的に変換されるわけではありません。
そのためToUniversalTimeメソッドなどで事前にUTCに変換しておく必要があります。

DateTimeOffsetオブジェクトの場合な自動的に変換が行われます。

カスタム日時書式

カスタム日時書式は、日時の情報を示す複数の文字を組み合わせて、日時の書式(フォーマット)をプログラマが自由に決定することができます。


//2001/02/03
Console.WriteLine(dt.ToString("yyyy/MM/dd"));
書式指定子 説明
d 日にち(1~31) 3
dd 日にち(01~31) 03
ddd 曜日の省略名 土(ja-JP)
Sat(en-US)
dddd 曜日の完全名 土曜日(ja-JP)
Saturday(en-US)
f
ff
fff
ミリ秒 0(f)
01(ff)
012(fff)
ffff
fffff
ffffff
ミリ秒+マイクロ秒 0120(ffff)
01200(fffff)
012000(ffffff)
fffffff ミリ秒+マイクロ秒+100ナノ秒 0120000
F
FF
FFF
ミリ秒
(不要な0は出力しない)
(空)(F)
01(FF)
012(FFF)
FFFF
FFFFF
FFFFFF
ミリ秒+マイクロ秒
(不要な0は出力しない)
012(FFFF)
012(FFFFF)
012(FFFFFF)
FFFFFFF ミリ秒+マイクロ秒+100ナノ秒
(不要な0は出力しない)
012
g、gg 時代(西暦紀元) 西暦(ja-JP)
AD(en-US)
h 時間(12時間形式)
(1~12)
4
hh 時間(12時間形式)
(01~12)
04
H 時間(24時間形式)
(0~23)
4
HH 時間(24時間形式)
(00~23)
04
K タイムゾーン +09:00(Local、JST)
Z(Utc)
(空)(Unspecified)
m 分(0~59) 5
mm 分(00~59) 05
M 月(1~12) 2
MM 月(01~12) 02
MMM 月の省略名(1~12) 2月(ja-JP)
Feb(en-US)
MMMM 月の完全名(01~12) 2月(ja-JP)
February(en-US)
s 秒(0~59) 6
ss 秒(00~59) 06
t 午前/午後指定子の最初の文字 午(ja-JP)
A(en-US)
tt 午前/午後指定子 午前(ja-JP)
AM(en-US)
y 年(0~99) 1
yy 年(01~99) 01
yyy 年(最低3桁表記) 2001
yyyy 年(4桁表記) 2001
yyyyy 年(5桁表記) 02001
z タイムゾーンオフセット
(記号と最低1桁の時間(不要なゼロなし))
+9
zz タイムゾーンオフセット
(記号と2桁の時間)
+09
zzz タイムゾーンオフセット
(記号と2桁の時間と2桁の分)
+09:00
: 時刻の区切り記号 :
/ 日付の区切り記号 /
% 後続の文字をカスタム日時書式として定義する
一文字だけの書式を標準書式と解釈させないために使用する
例えば"d"は標準日時書式(短い形式の日付)だが、"%d"はカスタム日時書式(日にち)になる
(なし)
\
(バックスペース)
エスケープ文字 (なし)
"または'で区切られた文字列 上記に含まれる文字を書式指定文字列と解釈しない (なし)
その他の文字列 そのまま出力される (なし)

カスタム日時書式の例をいくつか示します。


DateTime dt = new DateTime(2001, 2, 3, 4, 5, 6, 12, DateTimeKind.Local);

Console.WriteLine(dt.ToString("yyyy/MM/dd HH:mm:ss"));
Console.WriteLine(dt.ToString("g yyyy/MM/dd"));
Console.WriteLine(dt.ToString("HH:mm:ss MM/dd dddd"));
Console.WriteLine(dt.ToString("tt hh:mm:ss"));
Console.WriteLine(dt.ToString("HH:mm:ss.fff"));
2001/02/03 04:05:06
西暦 2001/02/03
04:05:06 02/03 土曜日
午前 04:05:06
04:05:06.012

一文字だけの文字列は標準日時書式と解釈されるので、カスタム日時書式として解釈させたい場合は%記号を先頭に付加します。
(半角スペースなどで二文字以上にすることでも可能だがスペース自体も出力される)
カスタム日時書式に使用される文字をそのまま出力したい場合は\で文字をエスケープするか、"または'で文字列の範囲を指定します。


DateTime dt = new DateTime(2001, 2, 3, 4, 5, 6, 12, DateTimeKind.Local);

//標準日時書式
Console.WriteLine(dt.ToString("d"));
//カスタム日時書式
Console.WriteLine(dt.ToString("%d"));

Console.WriteLine();

//f、m、tはカスタム日時書式と解釈される
Console.WriteLine(dt.ToString("format"));

//\で文字をエスケープする
Console.WriteLine(dt.ToString("\\for\\ma\\t"));
Console.WriteLine(dt.ToString(@"\for\ma\t")); //逐語的文字列

//"または'で区切った範囲はそのまま出力される
Console.WriteLine(dt.ToString("'format'"));
2001/02/03
3

0or5a午
format
format
format

日本の元号(和暦)を表示する

カスタム日時書式の「g」「gg」指定子を使用して、年を日本の元号に変換することができます。
この指定子はそのまま使用すると「西暦」や「AD」などと表示されますが、日本語カルチャのDateTimeFormat.CalendarプロパティにJapaneseCalendarクラス(System.Globalization名前空間)を割り当てると、年に対応した元号に変換されます。


DateTime dt = new DateTime(2001, 2, 3, 4, 5, 6);

//新しいカルチャを作成
CultureInfo cultureJA = CultureInfo.CreateSpecificCulture("ja-JP");

//日本語カレンダーを割り当て
cultureJA.DateTimeFormat.Calendar = new System.Globalization.JapaneseCalendar();
Console.WriteLine(dt.ToString("gyy MM/dd", cultureJA));
平成13 02/03

注意点として、今までカルチャオブジェクトはCultureInfo.GetCultureInfoメソッドで作成していましたが、これはシステムにキャッシュされたカルチャを取得します。
この方法で作成したカルチャは読み取り専用なので、DateTimeFormat.Calendarプロパティにカレンダーを割り当てることができません。
上記コードで使用しているCultureInfo.CreateSpecificCultureメソッドは新しいカルチャを作成します。
新しく作られたカルチャオブジェクトは中身を自由に変更できます。

なお、年号は明治元年以降の日付がサポートされています。
具体的には「1868年9月8日 0時0分0秒」以降で、それより前の日付情報は変換に失敗します。
(ArgumentOutOfRangeException例外)

文字列をDateTimeオブジェクトに変換する

日時を表す文字列からDateTimeオブジェクトを作るには、DateTime.Parseメソッド、DateTime.TryParseメソッド、もしくはDateTime.ParseExactメソッド、DateTime.TryParseExactメソッドを使用します。

DateTime.Parse、DateTime.TryParseメソッド

DateTime.Parseメソッド、DateTime.TryParseメソッドは標準日時書式の形式の文字列をDateTimeオブジェクトに変換します。

DateTime.ParseメソッドはDateTimeオブジェクトを返し、日時と解釈できない文字列を渡したときに例外(FormatException)をスローします。
DateTime.TryParseメソッドは変換の成否をbool型で返し、DateTimeオブジェクトはout参照渡しで受け取ります。
例外は対応が少し面倒なのでif文等で簡単に書けるTryParseメソッドを使用すると良いでしょう。


try
{
    DateTime dt1 = DateTime.Parse("99/99/99");
    Console.WriteLine("変換成功");
    Console.WriteLine(dt1);
} catch {
    Console.WriteLine("変換失敗");
}

DateTime dt2;
if(DateTime.TryParse("99/99/99", out dt2))
{
    Console.WriteLine("変換成功");
    Console.WriteLine(dt2);
}
else
{
    Console.WriteLine("変換失敗");
}

Parseメソッド、TryParseメソッドは標準日時書式で出力される形式の文字列を変換できます。
文字列の先頭と末尾、および途中にある空白文字は無視されます。
標準日時文字列の書式と厳密に一致する必要はなく、要素が前後している程度であれば柔軟に解釈してくれます。

タイムゾーン情報のない形式ではKindプロパティはUnspecifiedになります。
タイムゾーン情報がある場合はKindプロパティはLocalになり、その情報に基づいて日時はローカル時刻に変換されます。


string[] dateStrings = {
    "2001/2/3 4:5:6",
    "1/2/3",
    "1:2:3", //システムの日付+時刻
    "  2001 / 2 / 3  ", //空白は無視される
    "Saturday, February 3, 2001 4:5:6",
    "2001-02-03T04:05:06.0120000+09:00",
    "Sat, 03 Feb 2001 04:05:06 GMT",
    "2001-02-03 04:05:06Z",
    "S39 1/2",          //日本カルチャでは
    "昭和39年 1月2日",   //元号も変換可能
    "1/2/99"
};

DateTime dt;
for (int n = 0; n < dateStrings.Length; ++n)
{
    if (DateTime.TryParse(dateStrings[n], out dt))
        Console.WriteLine("{0}, {1}", dt, dt.Kind);
    else
        Console.WriteLine("変換失敗");
}
2001/02/03 4:05:06, Unspecified
2001/02/03 0:00:00, Unspecified
2024/12/02 1:02:03, Unspecified
2001/02/03 0:00:00, Unspecified
2001/02/03 4:05:06, Unspecified
2001/02/03 4:05:06, Local
2001/02/03 13:05:06, Local
2001/02/03 13:05:06, Local
1964/01/02 0:00:00, Unspecified
1964/01/02 0:00:00, Unspecified
変換失敗

日本カルチャでは(明治以降の)元号をそのまま変換可能です。
「明治」や「昭和」などの文字のほか、頭文字のアルファベット一文字(「M」「T」「S」「H」「R」)でも変換可能です。
ただし漢数字や元号の一年目を表す「元年」は変換できません。

カルチャの指定

変換メソッドは標準では現在のカルチャに基づいて日時文字列を解釈します。
上記コードの最後の"1/2/99"は日本方式では「2001年2月99日」と解釈され、あり得ない日付なので変換に失敗しますが、アメリカ方式ならば正しい年月日文字列です。
特定の地域に基づいて変換する場合は、第二引数にカルチャ指定することができます。


DateTime dt;
if (DateTime.TryParse("1/2/99", CultureInfo.GetCultureInfo("ja-JP"), out dt))
    Console.WriteLine("{0}, {1}", dt, dt.Kind);
else
    Console.WriteLine("変換失敗");

if (DateTime.TryParse("1/2/99", CultureInfo.GetCultureInfo("en-US"), out dt))
    Console.WriteLine("{0}, {1}", dt, dt.Kind);
else
    Console.WriteLine("変換失敗");
変換失敗
1999/01/02 0:00:00, Unspecified

DateTimeStyle列挙型

第二引数にカルチャを指定する場合、第三引数にDateTimeStyle列挙型を指定することができます。
DateTimeStyle列挙型は文字列解釈の動作を変更することができます。
これらのフィールドはビット演算で組み合わせが可能です。

DateTimeStyle列挙型
フィールド 説明
None 規定のスタイルを使用する。
AllowLeadingWhite 先頭の空白文字を無視する。
DateTimeFormatInfo書式パターンに従う場合を除く。
Parseメソッド、TryParseメソッドではこれを指定しても無視される。
AllowTrailingWhite 末尾の空白文字を無視する。
DateTimeFormatInfo書式パターンに従う場合を除く。
Parseメソッド、TryParseメソッドではこれを指定しても無視される。
AllowInnerWhite 文字列の途中にある空白文字を無視する。
DateTimeFormatInfo書式パターンに従う場合を除く。
Parseメソッド、TryParseメソッドではこれを指定しても無視される。
AllowWhiteSpaces 文字列中の空白文字を無視する。
AllowLeadingWhite、AllowTrailingWhite、AllowInnerWhiteの組み合わせ。
DateTimeFormatInfo書式パターンに従う場合を除く。
(Parseメソッド、TryParseメソッドの規定の動作)
NoCurrentDateDefault 文字列に日付情報が含まれない場合、日付を1年1月1日(グレゴリオ暦)と解釈する。
(このスタイルを使用しない場合はシステムの日時が使用される)
AdjustToUniversal 返される日時をUTC時刻に調整する。
文字列にタイムゾーン情報がある場合、またはAssumeLocalスタイルを使用する場合は、ローカル時刻からUTC時刻に変換される。
タイムゾーン情報がUTCである場合、または AssumeUniversalスタイルを使用する場合は変換を行わない。
タイムゾーン情報がない場合は変換は行われず、KindプロパティはUnspecifiedになる。
このスタイルはRoundtripKindスタイルと同時に使用できない。
AssumeLocal 文字列にタイムゾーン情報がない場合、日時をローカル時刻と解釈する。
KindプロパティはLocalになる。
このスタイルはAssumeUniversalスタイル、RoundtripKindスタイルと同時に使用できない。
AssumeUniversal 文字列にタイムゾーン情報がない場合、日時をUTC時刻と解釈する。
日時はUTC時刻からローカル時刻に変換され、KindプロパティはLocalになる。
このスタイルはAssumeLocalスタイル、RoundtripKindスタイルと同時に使用できない。
RoundtripKind 文字列にUTCを表すタイムゾーン情報がある場合、KindプロパティをUtcにする。

上記の説明の「DateTimeFormatInfo書式」というのはCultureInfoオブジェクトのDateTimeFormatプロパティのことで、ここに日時に関するカルチャ情報がまとめられています。

以下にDateTimeStyle列挙型を使用して文字列をDateTime型に変換する例をいくつか示します。


string[] dateStrings = {
    "1:2:3",
    "1:2:3",
    "2001/2/3 4:5:6+09:00",
    "2001/2/3 4:5:6+09:00",
    "2001/2/3 4:5:6",
    "2001/2/3 4:5:6"
};
DateTimeStyles[] dateTimeStyles = {
    DateTimeStyles.None,
    DateTimeStyles.NoCurrentDateDefault, //日付情報がなければ1年1月1日にする
    DateTimeStyles.None,
    DateTimeStyles.AdjustToUniversal,   //UTC時刻に変換
    DateTimeStyles.AssumeLocal,         //ローカル時刻と解釈
    DateTimeStyles.AssumeUniversal      //UTC時刻と解釈してローカル時刻に変換
};
CultureInfo cultureJA = CultureInfo.GetCultureInfo("ja-JP");

DateTime dt;
for (int n = 0; n < dateStrings.Length; ++n)
{
    if (DateTime.TryParse(dateStrings[n], cultureJA, dateTimeStyles[n], out dt))
        Console.WriteLine("{0}, {1}", dt, dt.Kind);
    else
        Console.WriteLine("変換失敗");
}
2020/01/02 1:02:03, Unspecified
0001/01/01 1:02:03, Unspecified
2001/02/03 4:05:06, Local
2001/02/02 19:05:06, Utc
2001/02/03 4:05:06, Local
2001/02/03 13:05:06, Local

DateTime.ParseExact、DateTime.TryParseExactメソッド

DateTime.ParseExactメソッド、DateTime.TryParseExactメソッドメソッドはカスタム日時書式文字列を使用して文字列をDateTimeオブジェクトに変換します。
ParseExactTryParseExactメソッドの関係はParseメソッドとTryParseメソッドの関係と同じです。

これらのメソッドはカスタム日時書式を使用して、日時文字列を解釈するための独自の書式を定義することができます。
なお、カルチャの指定とDateTimeStyle列挙型の指定は必須です。


string[] dateStrings = {
    "2001 02 03 04 05 06",  //区切り記号がない
    "02/03/01",             //年月日があいまい
    "'01 2.3"               //独自の形式
};
string[] formatStrings = {
    "yyyy MM dd HH mm ss",
    "MM/dd/yy",
    @"\'yy M.d"
};

DateTime dt;
CultureInfo culture = CultureInfo.CreateSpecificCulture("ja-JP");

for (int n = 0; n < dateStrings.Length; ++n)
{
    if (DateTime.TryParse(dateStrings[n], culture, out dt))
        Console.WriteLine("{0}, {1}", dt, dt.Kind);
    else
        Console.WriteLine("変換失敗");
}
Console.WriteLine();

for (int n = 0; n < dateStrings.Length; ++n)
{
    if (DateTime.TryParseExact(dateStrings[n], formatStrings[n], culture, DateTimeStyles.AllowWhiteSpaces, out dt))
        Console.WriteLine("{0}, {1}", dt, dt.Kind);
    else
        Console.WriteLine("変換失敗");
}
変換失敗
2002/03/01 0:00:00, Unspecified
変換失敗

2001/02/03 4:05:06, Unspecified
2001/02/03 0:00:00, Unspecified
2001/02/03 0:00:00, Unspecified

ParseExactTryParseExactメソッドによる変換は書式指定文字列に正確に一致する必要があります。
標準では空白文字すら許容されないので、上記コードではDateTimeStyles.AllowWhiteSpacesスタイルを指定して空白文字を許容しています。

バイナリに変換/復元

DateTime構造体の場合

DateTime型のオブジェクトは、ToBinaryメソッドでバイナリ化することができます。
バイナリ化とはデータを0と1の羅列で表現することで、通常のバイナリ化はbyte型の配列に変換しますが、このメソッドはlong型(8バイト)に変換します。
long型データひとつでKindプロパティを含めたDateTime構造体のデータ全てを管理できるため、ファイルへの保存や通信等で扱いやすくなります。

バイナリ化されたDateTime型のデータはDateTime.FromBinaryメソッドで元のDateTime型オブジェクトに復元できます。

基本的なデータ型のバイナリ化についてはbyte型配列との相互変換の項で説明します。


DateTime dt = new DateTime(2001, 2, 3, 4, 5, 6, DateTimeKind.Local);
Console.WriteLine($"{dt}, {dt.Kind}");

//バイナリ変換
long binary = dt.ToBinary();
Console.WriteLine(binary);

//バイナリからDateTime型に復元
DateTime dt2 = DateTime.FromBinary(binary);
Console.WriteLine($"{dt2}, {dt2.Kind}");
2001/02/03 4:05:06, Local
-8592204661794775808
2001/02/03 4:05:06, Local

変換後はlong型になりますが、この値はDateTime.FromBinaryメソッドで復元する以外の使い道はありません。
Ticksプロパティのような秒数やミリ秒などの意味はありません。

時刻の調整

KindプロパティがLocalのとき、バイナリの変換と復元をそれぞれ異なるタイムゾーンのシステムで行うと、復元を行ったシステムの時刻に調整されます。
例えばUTC+0900のシステムでバイナリ化したデータを、UTC+0800のシステムで復元すると時刻が1時間遅くなります。
時刻の表示は異なりますが、これはUTC時刻で同じ時間になるように調整された結果です。

その他、夏時間が存在するタイムゾーンが設定されているシステム上でも時刻が調整される場合があります。
夏時間は時刻を1時間早める精度ですが、夏時間の開始日は1日が23時間になるため「存在しない時刻」が存在します。
この時間帯を表すDateTimeオブジェクトをバイナリ化した場合、復元時に有効な時刻に調整されます。
例えばアメリカでは3月の第2日曜日の午前2時から時計を1時間進めます。
それに該当する日である2001年の3月11日には「午前2時30分」という時刻は存在しません。
この日時情報をバイナリ化して復元すると、「2001年 3月11日 午前3時30分」に調整されます。

DateTimeOffset構造体の場合

DateTimeOffset構造体にはバイナリ化(と復元)をするメソッドは用意されていません。
バイナリ化が必要な場合は自前で実装する必要があります。

以下はその例です。
データをバイナリに変換/復元するメソッドについてはbyte型配列との相互変換の項を参照してください。


static byte[] DateTimeOffsetToBinary(DateTimeOffset dto)
{
    //Ticksプロパティのバイナリ化
    byte[] bytes = BitConverter.GetBytes(dto.Ticks);
    //Offsetプロパティをバイナリ化し、byte型配列を結合
    //オフセットの範囲は-840~840(14時間*60分)であるためサイズはshort型(2バイト)で良い
    bytes = bytes.Concat(BitConverter.GetBytes((short)dto.Offset.TotalMinutes)).ToArray();
    return bytes;
}
static DateTimeOffset DateTimeOffsetFromBinary(byte[] bytes)
{
    //byte型配列のサイズがlong+short未満なら例外
    if (bytes.Length < sizeof(long) + sizeof(short))
        throw new ArgumentException("'bytes' length is too short.");
    long ticks = BitConverter.ToInt64(bytes);
    short offset = BitConverter.ToInt16(bytes, sizeof(long));
    return new DateTimeOffset(ticks, TimeSpan.FromMinutes(offset));
}

static void Main(string[] args)
{
    DateTimeOffset dto = new DateTimeOffset(2001, 2, 3, 4, 5, 6, TimeSpan.FromHours(9));
    Console.WriteLine(dto);

    //バイナリ変換
    byte[] binary = DateTimeOffsetToBinary(dto);
    Console.WriteLine(binary);

    //バイナリからDateTime型に復元
    DateTimeOffset dto2 = DateTimeOffsetFromBinary(binary);
    Console.WriteLine(dto);
}
2001/02/03 4:05:06 +09:00
System.Byte[]
2001/02/03 4:05:06 +09:00