一口に日時の文字列化といっても、日付・時刻を表す形式・書式にはいくつか種類があり、また国や地域によって表記が異なる場合もあるため、どのような書式を使って文字列化するかを定める必要があります。 DateTime.ToStringやConsole.WriteLine、String.Formatなどのメソッドでは、書式を指定して日時を文字列化することが出来るようになっています。 逆に、DateTime.Parseなどのメソッドを使えば、書式に従って文字列からDateTimeに変換することができます。

ここではDateTime・DateTimeOffsetの文字列化および日付と時刻の書式、文字列への/からの変換について見ていきます。

なお、本文中にあるいくつかのサンプルコードについて、実行環境に設定されているタイムゾーン・言語・書式等によって実行結果・出力内容が変わるものもが存在します。 特に明記していない場合は、日本標準時(UTC+9)・日本語の環境での実行結果となります。

§1 文字列への変換

DateTime.ToStringおよびDateTimeOffset.ToStringメソッドを用いることでDateTime・DateTimeOffsetの表す日時を文字列化することができます。

using System;

class Sample {
  static void Main()
  {
    DateTime dt = DateTime.Now;

    Console.WriteLine(dt.ToString());

    DateTimeOffset dto = DateTimeOffset.Now;

    Console.WriteLine(dto.ToString());
  }
}
実行結果例
2013/04/01 15:00:30
2013/04/01 15:00:30 +09:00

ToStringメソッドに何も引数を指定しない場合、「一般的な日付と日時の形式」と定義されている形式に従って日時が文字列化されます。 一方、ToStringメソッドの引数に書式指定子を指定すると、その書式に従って日時が文字列化されるようになります。

§1.1 標準の書式

.NET Frameworkでは、標準の日付と時刻の書式が用意されていて、その中から目的に応じた書式を選ぶことが出来ます。 以下は、DateTime・DateTimeOffsetに対していくつかの標準の書式指定子を指定して文字列化した例です。

using System;

class Sample {
  static void Main()
  {
    DateTime dt = DateTime.Now;

    Console.WriteLine("D : {0}", dt.ToString("D"));  // 長い日付の形式
    Console.WriteLine("d : {0}", dt.ToString("d"));  // 短い日付の形式
    Console.WriteLine("T : {0}", dt.ToString("T"));  // 長い時刻の形式
    Console.WriteLine("t : {0}", dt.ToString("t"));  // 短い時刻の形式
    Console.WriteLine("F : {0}", dt.ToString("F"));  // 完全な日付と日時の形式 (長い形式)
    Console.WriteLine("u : {0}", dt.ToString("u"));  // UTCでの完全な日付と日時の形式 (短い形式)
    Console.WriteLine("G : {0}", dt.ToString("G"));  // 一般的な日付と日時の形式 (書式指定子を指定しなかった場合のデフォルト)
    Console.WriteLine();

    DateTimeOffset dto = DateTimeOffset.Now;

    Console.WriteLine("D : {0}", dto.ToString("D"));  // 長い日付の形式
    Console.WriteLine("d : {0}", dto.ToString("d"));  // 短い日付の形式
    Console.WriteLine("T : {0}", dto.ToString("T"));  // 長い時刻の形式
    Console.WriteLine("t : {0}", dto.ToString("t"));  // 短い時刻の形式
    Console.WriteLine("F : {0}", dto.ToString("F"));  // 完全な日付と日時の形式 (長い形式)
    Console.WriteLine("u : {0}", dto.ToString("u"));  // UTCでの完全な日付と日時の形式 (短い形式)
    Console.WriteLine("G : {0}", dto.ToString("G"));  // 一般的な日付と日時の形式 (書式指定子を指定しなかった場合のデフォルト)
    Console.WriteLine();
  }
}
ja-JPでの実行結果例
D : 2013年4月1日
d : 2013/04/01
T : 15:00:30
t : 15:00
F : 2013年4月1日 15:00:30
u : 2013-04-01 15:00:30Z
G : 2013/04/01 15:00:30

D : 2013年4月1日
d : 2013/04/01
T : 15:00:30
t : 15:00
F : 2013年4月1日 15:00:30
u : 2013-04-01 06:00:30Z
G : 2013/04/01 15:00:30

上記の例で使用した書式はローカライズされる書式(実行環境の国と地域の設定によって結果が異なる書式)で、上記のコードをen-US(英語/アメリカ合衆国)の環境で実行すると実行結果は次のようになります。

en-USでの実行結果例
D : Monday, April 01, 2013
d : 4/1/2013
T : 3:00:30 PM
t : 3:00 PM
F : Monday, April 01, 2013 3:00:30 PM
u : 2013-04-01 15:00:30Z
G : 4/1/2013 3:00:30 PM

D : Monday, April 01, 2013
d : 4/1/2013
T : 3:00:30 PM
t : 3:00 PM
F : Monday, April 01, 2013 3:00:30 PM
u : 2013-04-01 06:00:30Z
G : 4/1/2013 3:00:30 PM

一方、上記の書式とは異なり、ローカライズされない書式も用意されています。 これらの書式では、実行環境によらず常に同じ結果が得られます。

using System;

class Sample {
  static void Main()
  {
    DateTime dt = DateTime.Now;

    Console.WriteLine("r : {0}", dt.ToString("r"));  // RFC1123形式
    Console.WriteLine("o : {0}", dt.ToString("o"));  // ISO8601(W3C-DTF)形式
    Console.WriteLine("s : {0}", dt.ToString("s"));  // ISO8601形式に似た、ソートに適した形式
    Console.WriteLine("u : {0}", dt.ToString("u"));  // ISO8601形式に似た、ソートに適した形式 (UTC)
    Console.WriteLine();

    DateTimeOffset dto = DateTimeOffset.Now;

    Console.WriteLine("r : {0}", dto.ToString("r"));  // RFC1123形式
    Console.WriteLine("o : {0}", dto.ToString("o"));  // ISO8601(W3C-DTF)形式
    Console.WriteLine("s : {0}", dto.ToString("s"));  // ISO8601形式に似た、ソートに適した形式
    Console.WriteLine("u : {0}", dto.ToString("u"));  // ISO8601形式に似た、ソートに適した形式 (UTC)
    Console.WriteLine();
  }
}
実行結果例
r : Mon, 01 Apr 2013 15:00:30 GMT
o : 2013-04-01T15:00:30.1230000+09:00
s : 2013-04-01T15:00:30
u : 2013-04-01 15:00:30Z

r : Mon, 01 Apr 2013 06:00:30 GMT
o : 2013-04-01T15:00:30.1230000+09:00
s : 2013-04-01T15:00:30
u : 2013-04-01 06:00:30Z

このように標準の書式にはいくつかの種類があり、ローカライズされるものとそうでないものが存在します。 さらに、書式によってはUTCへの変換が行われた上で文字列化されるものもあります。 各書式のより詳細な解説については書式指定子 §.日付と時刻の書式指定子を参照してください。 また、書式とローカライズに関しては別途解説します。

なお、DateTimeにはToString以外にも文字列化を行う次のようなメソッドが用意されています。 いずれも、標準の書式指定子を指定してToStringメソッドを呼び出した場合と同じです。

DateTimeの文字列化を行うメソッド
メソッド 得られる文字列 等価な書式指定子
ToLongDateString 長い日付の形式 "D"
ToLongTimeString 長い時刻の形式 "T"
ToShortDateString 短い日付の形式 "d"
ToShortTimeString 短い時刻の形式 "t"

これらのメソッドで使用される書式はローカライズされる書式であるため、得られる文字列は実行環境によって異なります。

using System;

class Sample {
  static void Main()
  {
    DateTime now = DateTime.Now;

    Console.WriteLine("{0} {1}", now.ToString("D"), now.ToLongDateString());  // 長い日付の形式
    Console.WriteLine("{0} {1}", now.ToString("T"), now.ToLongTimeString());  // 長い時刻の形式
    Console.WriteLine("{0} {1}", now.ToString("d"), now.ToShortDateString()); // 短い日付の形式
    Console.WriteLine("{0} {1}", now.ToString("t"), now.ToShortTimeString()); // 短い時刻の形式
  }
}
ja-JPでの実行結果例
2013年4月1日 2013年4月1日
15:00:30 15:00:30
2013/04/01 2013/04/01
15:00 15:00

DateTimeOffsetにはこういったメソッドは用意されていないため、ToStringメソッドと書式指定子を組み合わせて文字列化する必要があります。

§1.2 カスタム書式

標準の書式の他にも、独自に書式を定義して文字列化することも出来ます。 例えば、年月だけ出力したい、西暦の桁数を指定したい、時刻を12時間制にしたい、ミリ秒部分の精度を指定して文字列化したい、といった標準以外の書式を指定したい場合にはカスタム書式指定子を使用することが出来ます。

次の例では、いくつかのカスタム書式指定子を使ってDateTime・DateTimeOffsetを文字列化しています。

using System;

class Sample {
  static void Main()
  {
    DateTime dt = DateTime.Now;

    Console.WriteLine(dt.ToString());
    Console.WriteLine("M/d/yy            : {0}", dt.ToString("M/d/yy"));          // 月/日/年2桁
    Console.WriteLine("yyyy/MM/dd        : {0}", dt.ToString("yyyy/MM/dd"));      // 年4桁/月(0埋めあり)/日(0埋めあり)
    Console.WriteLine("M月d日 dddd       : {0}", dt.ToString("M月d日 dddd"));     // x月y日 曜日
    Console.WriteLine("tt hh時mm分ss秒   : {0}", dt.ToString("tt hh時mm分ss秒"));  // [午前|午後] x時y分z秒 (0埋めあり、12時間制)
    Console.WriteLine("H:m:s.ff          : {0}", dt.ToString("H:m:s.ff"));        // 時:分:秒.秒の端数 (0埋めなし、24時間制、1/10秒単位で表示)
    Console.WriteLine();

    DateTimeOffset dto = DateTimeOffset.Now;

    Console.WriteLine(dto.ToString());
    Console.WriteLine("tt hh時mm分ss秒   : {0}", dto.ToString("tt hh時mm分ss秒"));  // [午前|午後] x時y分z秒 (0埋めあり、12時間制)
    Console.WriteLine("H:m:s.ff          : {0}", dto.ToString("H:m:s.ff"));        // 時:分:秒.秒の端数 (0埋めなし、24時間制、1/10秒単位で表示)
    Console.WriteLine("HH:mm:ss (zzz)    : {0}", dto.ToString("HH:mm:ss (zzz)"));  // 時:分:秒 (オフセット値) (0埋めあり、24時間制)
    Console.WriteLine();
  }
}
ja-JPでの実行結果例
2013/04/01 15:00:30
M/d/yy            : 4/1/13
yyyy/MM/dd        : 2013/04/01
M月d日 dddd       : 4月1日 月曜日
tt hh時mm分ss秒   : 午後 03時00分30秒
H:m:s.ff          : 15:0:30.12

2013/04/01 15:00:30 +09:00
tt hh時mm分ss秒   : 午後 03時00分30秒
H:m:s.ff          : 15:0:30.12
HH:mm:ss (zzz)    : 15:00:30 (+09:00)
en-USでの実行結果例
4/1/2013 3:00:30 PM
M/d/yy            : 4/1/13
yyyy/MM/dd        : 2013/04/01
M月d日 dddd       : 4月1日 Monday
tt hh時mm分ss秒   : PM 03時00分30秒
H:m:s.ff          : 15:0:30.12

4/1/2013 3:00:30 PM +09:00
tt hh時mm分ss秒   : PM 03時00分30秒
H:m:s.ff          : 15:0:30.12
HH:mm:ss (zzz)    : 15:00:30 (+09:00)

なお、上記の実行結果からも分かるように、カスタム書式指定子にもローカライズされるものとそうでないものが存在するため、実行環境に設定されているカルチャ(ロケール)によっては結果が異なる個所があります。 日付・時刻の区切り記号もエスケープしない限りローカライズの対象となる点に注意が必要です。 上記以外のカスタム書式指定子やその詳細についてはカスタムの日付と時刻の書式指定文字列にまとめられています。

1文字だけの書式指定子は標準の書式指定子として扱われる点に注意が必要です。 例えば、24時間制で0埋めなしの時間部分を表すカスタム書式指定子 H を指定した場合、これは標準の書式指定子として扱われますが、そのような書式は存在しないためFormatExceptionがスローされます。 カスタム書式指定子を1文字だけ指定する場合は、先頭に % を付けることでそれがカスタム書式指定子であることを明示する必要があります。

using System;

class Sample {
  static void Main()
  {
    DateTime dt = DateTime.Now;

    Console.WriteLine(dt.ToString());
    Console.WriteLine("%H : {0}", dt.ToString("%H")); // 時
    Console.WriteLine("%m : {0}", dt.ToString("%m")); // 分
    Console.WriteLine("%s : {0}", dt.ToString("%s")); // 秒
    Console.WriteLine("%d : {0}", dt.ToString("%d")); // 日
    Console.WriteLine("d  : {0}", dt.ToString("d"));  // 短い形式の日付 (標準の書式指定子)
    Console.WriteLine();

    DateTimeOffset dto = DateTimeOffset.Now;

    Console.WriteLine(dto);
    Console.WriteLine("%H : {0}", dto.ToString("%H")); // 時
    Console.WriteLine("%m : {0}", dto.ToString("%m")); // 分
    Console.WriteLine("%s : {0}", dto.ToString("%s")); // 秒
    Console.WriteLine("%d : {0}", dto.ToString("%d")); // 日
    Console.WriteLine("d  : {0}", dto.ToString("d"));  // 短い形式の日付 (標準の書式指定子)
  }
}
ja-JPでの実行結果例
2013/04/01 15:00:30
%H : 15
%m : 0
%s : 30
%d : 1
d  : 2013/04/01

2013/04/01 15:00:30 +09:00
%H : 15
%m : 0
%s : 30
%d : 1
d  : 2013/04/01

§1.3 複合書式

Console.WriteLineString.FormatStreamWriter.WriteLineなどのメソッドでは複合書式設定を用いることができるため、ToStringメソッドと同様にDateTimeの書式を指定することができ、さらに右詰め・左詰めといった指定も可能になっています。

using System;

class Sample {
  static void Main()
  {
    DateTime dt = DateTime.Now;

    Console.WriteLine(dt);
    Console.WriteLine("|{0,15:d}|", dt);  // 短い日付の形式・幅15で右揃え
    Console.WriteLine("|{0,-15:d}|", dt); // 短い日付の形式・幅15で左揃え
    Console.WriteLine("{0:%y}-{0:%M}-{0:%d}", dt); // 年-月-日 (0埋めなし)
    Console.WriteLine();

    DateTimeOffset dto = DateTimeOffset.Now;

    Console.WriteLine(dto);
    Console.WriteLine("|{0,15:d}|", dto);   // 短い日付の形式・幅15で右揃え
    Console.WriteLine("|{0,-15:d}|", dto);  // 短い日付の形式・幅15で左揃え
    Console.WriteLine("{0:%y}-{0:%M}-{0:%d}", dto); // 年-月-日 (0埋めなし)
  }
}
ja-JPでの実行結果例
2013/04/01 15:00:30
|     2013/04/01|
|2013/04/01     |
13-4-1

2013/04/01 15:00:30 +09:00
|     2013/04/01|
|2013/04/01     |
13-4-1

上記の例で使用している書式のうち % で始まるものは、複合書式設定特有の記述ではなく、それが標準の書式指定子ではなくカスタム書式指定子であることを明示するためのものです。

複合書式設定についてより詳しくは書式指定子 §.複合書式設定と0埋め・右揃え・左揃えを参照してください。

§1.4 書式とローカライズ

ToStringメソッドでは、書式に加えて引数に書式プロバイダと呼ばれるものを指定できます。 書式プロバイダとは、書式指定子からそれにに対応する形式の文字列に変換するものです。 CultureInfoクラスは書式プロバイダとして機能するため、これをToStringメソッドの引数に指定することで特定カルチャでの形式に従って文字列化することができます。

次の例では、書式指定子として一般的な日付と日時の形式 G を指定し、書式プロバイダにはen-US(英語/アメリカ合衆国)、de-DE(ドイツ語/ドイツ)、ja-JP(日本語/日本)をそれぞれ指定し、その結果の違いを表示しています。

using System;
using System.Globalization;

class Sample {
  static void Main()
  {
    // 書式プロバイダとして使用するカルチャ
    CultureInfo enus = new CultureInfo("en-US");
    CultureInfo dede = new CultureInfo("de-DE");
    CultureInfo jajp = new CultureInfo("ja-JP");

    DateTime dt = DateTime.Now;

    Console.WriteLine(dt.ToString("G", enus));  // en-USでの一般的な日付と日時の形式で文字列化
    Console.WriteLine(dt.ToString("G", dede));  // de-DEでの一般的な日付と日時の形式で文字列化
    Console.WriteLine(dt.ToString("G", jajp));  // ja-JPでの一般的な日付と日時の形式で文字列化
    Console.WriteLine(dt.ToString("G"));        // デフォルトの一般的な日付と日時の形式で文字列化
  }
}
ja-JPでの実行結果例
4/1/2013 3:00:30 PM
01.04.2013 15:00:30
2013/04/01 15:00:30
2013/04/01 15:00:30

このように、カルチャによって「一般的な日付と日時の形式」として定義されている書式には違いがあるため、同じ書式指定子でもカルチャによって異なる文字列が返されます。 なお、書式プロバイダを指定しなかった場合(省略した場合およびnull/Nothingを指定した場合)は、現在のスレッドに設定されているカルチャが書式プロバイダとして使用されます。

また、既に解説したとおり、書式にはローカライズされるものとそうでないものが存在します。 上記の例で使用した一般的な日付と日時の形式 G はローカライズされる書式ですが、ISO8601(W3C-DTF)形式 o やRFC1123形式 r などはローカライズされない書式であるため、異なるカルチャを書式プロバイダとして指定しても得られる結果は常に同じとなります。

月名や曜日名を英語などその他の言語での表記で取得する目的にも書式プロバイダを使うことができます。 次の例では、書式指定子として曜日の完全名 dddd、月の完全名 MMMM、書式プロバイダとしてen-US(英語/アメリカ合衆国)、de-DE(ドイツ語/ドイツ)、ja-JP(日本語/日本)を指定することで、英語・ドイツ語・日本語での曜日名・月名を取得・表示しています。

using System;
using System.Globalization;

class Sample {
  static void Main()
  {
    // 書式プロバイダとして使用するカルチャ
    CultureInfo enus = new CultureInfo("en-US");
    CultureInfo dede = new CultureInfo("de-DE");
    CultureInfo jajp = new CultureInfo("ja-JP");

    // カルチャ毎に曜日の完全名'dddd'で文字列化した結果を取得する
    Console.WriteLine("{0,-20}|{1,-20}|{2}", enus.Name, dede.Name, jajp.Name);
    Console.WriteLine(new string('-', 60));

    for (int day = 0; day < 7; day++) {
      DateTime dt = (new DateTime(2013, 3, 31)).AddDays(day); // 日曜日から土曜日のDateTimeを作成

      Console.WriteLine("{0,-20}|{1,-20}|{2}",
                        dt.ToString("dddd", enus),
                        dt.ToString("dddd", dede),
                        dt.ToString("dddd", jajp));
    }

    // カルチャ毎に月の完全名'MMMM'で文字列化した結果を取得する
    Console.WriteLine(new string('-', 60));

    for (int month = 1; month <= 12; month++) {
      DateTime dt = new DateTime(2013, month, 1); // 1月から12月のDateTimeを作成

      Console.WriteLine("{0,-20}|{1,-20}|{2}",
                        dt.ToString("MMMM", enus),
                        dt.ToString("MMMM", dede),
                        dt.ToString("MMMM", jajp));
    }
  }
}
実行結果
en-US               |de-DE               |ja-JP
------------------------------------------------------------
Sunday              |Sonntag             |日曜日
Monday              |Montag              |月曜日
Tuesday             |Dienstag            |火曜日
Wednesday           |Mittwoch            |水曜日
Thursday            |Donnerstag          |木曜日
Friday              |Freitag             |金曜日
Saturday            |Samstag             |土曜日
------------------------------------------------------------
January             |Januar              |1月
February            |Februar             |2月
March               |März                |3月
April               |April               |4月
May                 |Mai                 |5月
June                |Juni                |6月
July                |Juli                |7月
August              |August              |8月
September           |September           |9月
October             |Oktober             |10月
November            |November            |11月
December            |Dezember            |12月

書式と書式プロバイダについては文字列と書式、カルチャと書式についてカルチャの基本とカルチャ情報およびカルチャと書式・テキスト処理・暦でより詳しく解説しているので、合わせてご覧ください。



§1.4.1 インバリアントカルチャ

カルチャにはインバリアントカルチャと呼ばれる特殊なカルチャが用意されています。 インバリアントカルチャでは、特定の言語や国・地域に依存しない書式が定義されています。 インバリアントカルチャを使用すると、ローカライズされる書式、例えば一般的な日付と日時の形式 Gを使って文字列化しても、実行環境のカルチャの影響は受けず常に同じ結果が得られます。 インバリアントカルチャには、英語圏で使われるものを基本とする書式が用意されています。

using System;
using System.Globalization;

class Sample {
  static void Main()
  {
    DateTime dt = DateTime.Now;

    // 短い日付の形式で文字列化
    Console.WriteLine(dt.ToString("d")); // 現在のカルチャ
    Console.WriteLine(dt.ToString("d", CultureInfo.InvariantCulture)); // インバリアントカルチャ
  }
}
ja-JPでの実行結果例
2013/04/01
04/01/2013

インバリアントカルチャが特に有用となるのは、異なるカルチャ間で日時を扱う場合です。 例えば、文字列形式で日時のデータをやり取りすることを考えます。 ja-JPの環境で "2013年4月1日" の日付を短い日付の形式"d"で文字列化すると "2013/04/01" (年月日の順)となります。 これを仮にファイルに記録したとして、ファイルから読み込んだ文字列 "2013/04/01" をParseメソッドで復元すると、同じja-JPの環境なら2013年4月1日という日付が復元されます。 一方、fr-FRの環境では "2013/01/04" という文字列をParseメソッドで復元しようとすると "2013日1月4年" と解釈され、異なる日付として復元されてしまいます。

次の例は、そのような状況を再現するものです。 Thread.CurrentThread.CurrentCultureに設定されているカルチャを変更することで、異なるカルチャの環境を再現しています。

using System;
using System.Globalization;
using System.Threading;

class Sample {
  static void Main()
  {
    DateTime now = DateTime.Now;

    // ja-JPの環境において、短い日付の形式で文字列化したとする
    Thread.CurrentThread.CurrentCulture = new CultureInfo("ja-JP");

    string str = now.ToString("d");

    Console.WriteLine(str);

    // fr-FRの環境において、文字列で表された日付を復元しようとする
    Thread.CurrentThread.CurrentCulture = new CultureInfo("fr-FR");

    // 解析に失敗するため、ここでFormatExceptionがスローされる
    Console.WriteLine(DateTime.ParseExact(str, "d", null));
    // ハンドルされていない例外: System.FormatException: 文字列は有効な DateTime ではありませんでした。
    //    場所 System.DateTimeParse.ParseExact(String s, String format, DateTimeFormatInfo dtfi, DateTimeStyles style)
    //    場所 Sample.Main()
  }
}

こういった問題を避けるために、変換時と復元時には異なる環境でも同じ書式を使うことが求められます。 もちろん、ローカライズされない書式を使うことでも変換時・復元時で同一の形式を使うように指定することができます。 ですが、ローカライズされる書式で文字列化しなければならない場合でも、インバリアントカルチャを使えばこういった問題を避けることができます。

インバリアントカルチャについてより詳しくはカルチャの基本とカルチャ情報 §.インバリアントカルチャをご覧ください。

§2 文字列からの変換

日時には様々な表記が存在するため、文字列からDateTime・DateTimeOffsetへの変換はその逆よりも考慮すべき事柄が多く、すべての表記に対応しようとすると複雑になります。 まずはシンプルな例として、Parseメソッドを使った例を見ていきます。

using System;

class Sample {
  static void Main()
  {
    string[] inputs = new string[] {
      "Mon, 01 Apr 2013 15:00:30 GMT",     // RFC1123形式の文字列
      "2013-04-01T15:00:30.1230000+09:00", // ISO8601形式の文字列
      "2013年4月1日 15:00:30",             // 一般的な形式の文字列
    };

    // 文字列をDateTimeに変換
    foreach (string input in inputs) {
      Console.WriteLine("{0,-35} -> {1}", input, DateTime.Parse(input));
    }
    Console.WriteLine();

    // 文字列をDateTimeOffsetに変換
    foreach (string input in inputs) {
      Console.WriteLine("{0,-35} -> {1}", input, DateTimeOffset.Parse(input));
    }
    Console.WriteLine();
  }
}
実行結果
Mon, 01 Apr 2013 15:00:30 GMT       -> 2013/04/02 0:00:30
2013-04-01T15:00:30.1230000+09:00   -> 2013/04/01 15:00:30
2013年4月1日 15:00:30                  -> 2013/04/01 15:00:30

Mon, 01 Apr 2013 15:00:30 GMT       -> 2013/04/01 15:00:30 +09:00
2013-04-01T15:00:30.1230000+09:00   -> 2013/04/01 15:00:30 +09:00
2013年4月1日 15:00:30                  -> 2013/04/01 15:00:30 +09:00

DateTime.ParseおよびDateTimeOffset.Parseメソッドは、引数で与えられた文字列を解析し、DateTime・DateTimeOffsetに変換た結果を返します。 Parseメソッドは、標準の書式としてサポートされている形式での解析を試み、解析出来た場合はその結果を返します。

Parseメソッドは、日付のみ・時刻のみといった不完全な日時を表す文字列も解析出来ます。 この際、時刻部分に欠損があれば0時0分0秒、日付部分に欠損があれば現在の日付を表すものとして扱われます。 オフセット値に欠損がある場合は、ローカル時刻のオフセット値が設定されます。

using System;

class Sample {
  static void Main()
  {
    string[] inputs = new string[] {
      "Mon, 01 Apr 2013",   // RFC1123形式・日付のみ
      "15:00:30",           // RFC1123形式・時刻のみ
      "2013-04",            // ISO8601形式・年月のみ
      "15:00:30.12-05:00",  // ISO8601形式・時刻のみ
      "15:00",              // ISO8601形式・時分のみ
      "2013年4月1日",       // 一般的な形式・日付のみ
    };

    // 文字列をDateTimeに変換
    foreach (string input in inputs) {
      Console.WriteLine("{0,-20} -> {1}", input, DateTime.Parse(input));
    }
    Console.WriteLine();

    // 文字列をDateTimeOffsetに変換
    foreach (string input in inputs) {
      Console.WriteLine("{0,-20} -> {1}", input, DateTimeOffset.Parse(input));
    }
    Console.WriteLine();
  }
}
実行結果
Mon, 01 Apr 2013     -> 2013/04/01 0:00:00
15:00:30             -> 2013/02/03 15:00:30
2013-04              -> 2013/04/01 0:00:00
15:00:30.12-05:00    -> 2013/02/04 5:00:30
15:00                -> 2013/02/03 15:00:00
2013年4月1日            -> 2013/04/01 0:00:00

Mon, 01 Apr 2013     -> 2013/04/01 0:00:00 +09:00
15:00:30             -> 2013/02/03 15:00:30 +09:00
2013-04              -> 2013/04/01 0:00:00 +09:00
15:00:30.12-05:00    -> 2013/02/03 15:00:30 -05:00
15:00                -> 2013/02/03 15:00:00 +09:00
2013年4月1日            -> 2013/04/01 0:00:00 +09:00

Parseメソッドに指定された文字列が日時として解析できない場合は例外FormatExceptionがスローされます。 次の例は、ParseメソッドがFormatExceptionをスローするような文字列の一例です。

using System;

class Sample {
  static void Main()
  {
    string[] inputs = new string[] {
      "29 Feb 2012",
      "1/2/3",
      "31 Feb 2013",                        // 存在しない日付
      "1 Apr 10000",                        // 年数が最小(0)〜最大(9999)の範囲外
      "27:00:00",                           // 時刻が最小(0:0:0)〜最大(23:59:59)の範囲外
      "2013-04-01T15:00:30.1230000+15:00",  // オフセットが最小・最大(±14:00)の範囲外
      "+09:00",                             // オフセットのみの時刻
      "0123456",                            // 日時として判別できない文字列
    };

    // 文字列をDateTimeに変換
    foreach (string input in inputs) {
      try {
        Console.WriteLine("{0,-35} -> {1}", input, DateTime.Parse(input));
      }
      catch (FormatException) {
        Console.WriteLine("{0,-35} -> FormatException", input);
      }
    }
    Console.WriteLine();

    // 文字列をDateTimeOffsetに変換
    foreach (string input in inputs) {
      try {
        Console.WriteLine("{0,-35} -> {1}", input, DateTimeOffset.Parse(input));
      }
      catch (FormatException) {
        Console.WriteLine("{0,-35} -> FormatException", input);
      }
    }
    Console.WriteLine();
  }
}
実行結果
29 Feb 2012                         -> 2012/02/29 0:00:00
1/2/3                               -> 2001/02/03 0:00:00
31 Feb 2013                         -> FormatException
1 Apr 10000                         -> FormatException
27:00:00                            -> FormatException
2013-04-01T15:00:30.1230000+15:00   -> 2013/04/02 0:00:30
+09:00                              -> FormatException
0123456                             -> FormatException

29 Feb 2012                         -> 2012/02/29 0:00:00 +09:00
1/2/3                               -> 2001/02/03 0:00:00 +09:00
31 Feb 2013                         -> FormatException
1 Apr 10000                         -> FormatException
27:00:00                            -> FormatException
2013-04-01T15:00:30.1230000+15:00   -> FormatException
+09:00                              -> FormatException
0123456                             -> FormatException

文字列が不正な形式だった場合にFormatExceptionがスローされることを望まない場合は、Parseメソッドの代わりにTryParseメソッドを使うことが出来ます。 このメソッドでは、DateTime・DateTimeOffsetに変換できた場合は第二引数のoutパラメータに結果が代入され、trueが返されます。 解析できない場合でもFormatExceptionはスローせず、単にfalseを返します。

using System;

class Sample {
  static void Main()
  {
    string[] inputs = new string[] {
      "29 Feb 2012",
      "31 Feb 2013",                        // 存在しない日付
      "27:00:00",                           // 時刻が最小(0:0:0)〜最大(23:59:59)の範囲外
      "2013-04-01T15:00:30.1230000+15:00",  // オフセットが最小・最大(±14:00)の範囲外
      "0123456",                            // 日時として判別できない文字列
    };

    // 文字列をDateTimeに変換
    foreach (string input in inputs) {
      DateTime dt;

      if (DateTime.TryParse(input, out dt))
        Console.WriteLine("{0,-35} -> {1}", input, dt);
      else
        Console.WriteLine("{0,-35} -> (invalid format)", input);
    }
    Console.WriteLine();

    // 文字列をDateTimeOffsetに変換
    foreach (string input in inputs) {
      DateTimeOffset dto;

      if (DateTimeOffset.TryParse(input, out dto))
        Console.WriteLine("{0,-35} -> {1}", input, dto);
      else
        Console.WriteLine("{0,-35} -> (invalid format)", input);
    }
    Console.WriteLine();
  }
}
実行結果
29 Feb 2012                         -> 2012/02/29 0:00:00
31 Feb 2013                         -> (invalid format)
27:00:00                            -> (invalid format)
2013-04-01T15:00:30.1230000+15:00   -> 2013/04/02 0:00:30
0123456                             -> (invalid format)

29 Feb 2012                         -> 2012/02/29 0:00:00 +09:00
31 Feb 2013                         -> (invalid format)
27:00:00                            -> (invalid format)
2013-04-01T15:00:30.1230000+15:00   -> (invalid format)
0123456                             -> (invalid format)

§2.1 書式とローカライズ

DateTime・DateTimeOffsetの文字列化に際してローカライズされる書式があるのと同様、ParseメソッドでもDateTime・DateTimeOffsetへの変換の際にローカライズされているものとして解釈される場合があります。

分かりやすい例として、"1/2/3"という文字列がja-JP(日本語/日本)・en-US(英語/アメリカ合衆国)・fr-FR(フランス語/フランス)の各環境ではどのように変換されるかその違いを見てみます。

using System;
using System.Globalization;
using System.Threading;

class Sample {
  static void Main()
  {
    string input = "1/2/3";

    // カルチャを変更して文字列をDateTimeに変換
    foreach (string culture in new string[] {"ja-JP", "en-US", "fr-FR"}) {
      Thread.CurrentThread.CurrentCulture = new CultureInfo(culture);

      Console.WriteLine("{0} ({1}) -> {2:o}", input, culture, DateTime.Parse(input));
    }
    Console.WriteLine();

    // カルチャを変更して文字列をDateTimeOffsetに変換
    foreach (string culture in new string[] {"ja-JP", "en-US", "fr-FR"}) {
      Thread.CurrentThread.CurrentCulture = new CultureInfo(culture);

      Console.WriteLine("{0} ({1}) -> {2:o}", input, culture, DateTimeOffset.Parse(input));
    }
    Console.WriteLine();
  }
}
実行結果
1/2/3 (ja-JP) -> 2001-02-03T00:00:00.0000000
1/2/3 (en-US) -> 2003-01-02T00:00:00.0000000
1/2/3 (fr-FR) -> 2003-02-01T00:00:00.0000000

1/2/3 (ja-JP) -> 2001-02-03T00:00:00.0000000+09:00
1/2/3 (en-US) -> 2003-01-02T00:00:00.0000000+09:00
1/2/3 (fr-FR) -> 2003-02-01T00:00:00.0000000+09:00

上記のコードではThread.CurrentThreadを変更することで異なるカルチャに変更していますが、実行環境によってThread.CurrentThreadに設定されているカルチャが異なることになるため、実行環境によってParseメソッドの結果は異なることになります。 上記の例で言えば、"1/2/3" という文字列はja-JPの環境では "2001年/2月/3日"、en-USの環境では "1月/2日/2003年"、fr-FRの環境では "1日/2月/2003年" として解釈されます。 このように、Parseメソッドに渡す文字列によっては、カルチャ・実行環境によって解析結果が異なる場合がある点に注意する必要があります。

なお、RFC1123形式やISO8601形式などはカルチャに依存しない形式なので、カルチャ・実行環境によって結果が異なるということはありません。

using System;
using System.Globalization;
using System.Threading;

class Sample {
  static void Main()
  {
    string input = "2001-02-03T04:05:06";

    // カルチャを変更して文字列をDateTimeに変換
    foreach (string culture in new string[] {"ja-JP", "en-US", "fr-FR"}) {
      Thread.CurrentThread.CurrentCulture = new CultureInfo(culture);

      Console.WriteLine("{0} ({1}) -> {2:o}", input, culture, DateTime.Parse(input));
    }
    Console.WriteLine();

    // カルチャを変更して文字列をDateTimeOffsetに変換
    foreach (string culture in new string[] {"ja-JP", "en-US", "fr-FR"}) {
      Thread.CurrentThread.CurrentCulture = new CultureInfo(culture);

      Console.WriteLine("{0} ({1}) -> {2:o}", input, culture, DateTimeOffset.Parse(input));
    }
    Console.WriteLine();
  }
}
実行結果
2001-02-03T04:05:06 (ja-JP) -> 2001-02-03T04:05:06.0000000
2001-02-03T04:05:06 (en-US) -> 2001-02-03T04:05:06.0000000
2001-02-03T04:05:06 (fr-FR) -> 2001-02-03T04:05:06.0000000

2001-02-03T04:05:06 (ja-JP) -> 2001-02-03T04:05:06.0000000+09:00
2001-02-03T04:05:06 (en-US) -> 2001-02-03T04:05:06.0000000+09:00
2001-02-03T04:05:06 (fr-FR) -> 2001-02-03T04:05:06.0000000+09:00

Parseメソッドでは、ToStringメソッドと同様に書式プロバイダを指定して変換を行うことも出来るようになっています。 例えば、入力される文字列を常にen-USの環境で使われる形式として解釈したい、といった場合にはParseメソッドの第二引数に書式プロバイダとしてen-USのCultureInfoを指定します。 指定しなかった場合やnull/Nothingを指定した場合は、現在のカルチャで使われる形式で表記されているものとして解釈されます。

using System;
using System.Globalization;

class Sample {
  static void Main()
  {
    string input = "1/2/3";

    Console.WriteLine(DateTime.Parse(input));       // 文字列は現在のカルチャの書式で解析される
    Console.WriteLine(DateTime.Parse(input, null)); // 文字列は現在のカルチャの書式で解析される
    Console.WriteLine(DateTime.Parse(input, new CultureInfo("en-US"))); // 文字列はen-USの書式で解析される
    Console.WriteLine();

    Console.WriteLine(DateTimeOffset.Parse(input));       // 文字列は現在のカルチャの書式で解析される
    Console.WriteLine(DateTimeOffset.Parse(input, null)); // 文字列は現在のカルチャの書式で解析される
    Console.WriteLine(DateTimeOffset.Parse(input, new CultureInfo("en-US"))); // 文字列はen-USの書式で解析される
    Console.WriteLine();
  }
}
実行結果
2001/02/03 0:00:00
2001/02/03 0:00:00
2003/01/02 0:00:00

2001/02/03 0:00:00 +09:00
2001/02/03 0:00:00 +09:00
2003/01/02 0:00:00 +09:00

当然、Parseメソッドでも書式プロバイダとしてインバリアントカルチャを指定することができます。

§2.2 書式を指定した変換 (ParseExact)

Parseメソッドでは、サポートされているすべての書式を使ってDateTime・DateTimeOffsetへの変換を試み、変換できた場合はその結果を返します。 一方、変換しようとする日時の書式があらかじめ定まっている場合では、目的の書式と完全に一致するものだけを変換し、それ以外の書式のものは不正な値として扱いたい場合があります。 そういった場合にはParseExactメソッドを使うことが出来ます。

ParseExactメソッドでは、一つ以上の書式を指定し、文字列がその書式と完全に一致した場合はその値を変換した結果を返します。 指定されたどの書式とも一致しない場合は例外FormatExceptionをスローします。 次の例では、書式として"o"(ISO8601形式)を指定し、日時を表すいくつかの文字列の変換を行った結果を表示しています。

using System;

class Sample {
  static void Main()
  {
    string[] inputs = new string[] {
      "Mon, 01 Apr 2013 15:00:30 GMT",     // RFC1123形式の文字列
      "2013年4月1日 15:00:30",             // 一般的な形式の文字列
      "2013-04-01T15:00:30.1230000+09:00", // ISO8601形式の文字列
      "2013-04-01T15:00:30.1230000",       // ISO8601形式の文字列 (オフセット無し)
      "2013-04-01",                        // ISO8601形式の文字列 (日付のみ)
      "15:00:30",                          // ISO8601形式の文字列 (時刻のみ)
    };

    // 書式"o"で文字列をDateTimeに変換
    foreach (string input in inputs) {
      try {
        Console.WriteLine("{0,-35} -> {1}", input, DateTime.ParseExact(input, "o", null));
      }
      catch (FormatException) {
        Console.WriteLine("{0,-35} -> FormatException", input);
      }
    }
    Console.WriteLine();

    // 書式"o"で文字列をDateTimeOffsetに変換
    foreach (string input in inputs) {
      try {
        Console.WriteLine("{0,-35} -> {1}", input, DateTimeOffset.ParseExact(input, "o", null));
      }
      catch (FormatException) {
        Console.WriteLine("{0,-35} -> FormatException", input);
      }
    }
    Console.WriteLine();
  }
}
実行結果
Mon, 01 Apr 2013 15:00:30 GMT       -> FormatException
2013年4月1日 15:00:30                  -> FormatException
2013-04-01T15:00:30.1230000+09:00   -> 2013/04/01 15:00:30
2013-04-01T15:00:30.1230000         -> 2013/04/01 15:00:30
2013-04-01                          -> FormatException
15:00:30                            -> FormatException

Mon, 01 Apr 2013 15:00:30 GMT       -> FormatException
2013年4月1日 15:00:30                  -> FormatException
2013-04-01T15:00:30.1230000+09:00   -> 2013/04/01 15:00:30 +09:00
2013-04-01T15:00:30.1230000         -> 2013/04/01 15:00:30 +09:00
2013-04-01                          -> FormatException
15:00:30                            -> FormatException

文字列化の場合と同様、書式には標準およびカスタム書式指定子を指定することができます。 上記の結果からも分かるとおり、書式と完全に一致する場合のみ変換が行われるため、日付のみ・時刻のみなど一部が一致していても不正な文字列として扱われ、変換は失敗します。

ParseExactメソッドもParseメソッドと同様に書式プロバイダを指定することができます。 ただし、ParseExactメソッドには書式プロバイダを省略できるバージョンのオーバーロードが用意されていないので、常に何らかのIFormatProviderを指定する必要があります。 上記の例では、省略する代わりにnull/Nothingを指定しています。

また、Parseメソッドに対するTryParseメソッドと同様、ParseExactメソッドにも変換できなかった場合でも例外をスローしないTryParseExactメソッドが存在します。 上記の例をTryParseExactメソッドを使ったものに書き換えると次のようになります。

using System;
using System.Globalization;

class Sample {
  static void Main()
  {
    string[] inputs = new string[] {
      "Mon, 01 Apr 2013 15:00:30 GMT",     // RFC1123形式の文字列
      "2013年4月1日 15:00:30",             // 一般的な形式の文字列
      "2013-04-01T15:00:30.1230000+09:00", // ISO8601形式の文字列
      "2013-04-01T15:00:30.1230000",       // ISO8601形式の文字列 (オフセット無し)
      "2013-04-01",                        // ISO8601形式の文字列 (日付のみ)
      "15:00:30",                          // ISO8601形式の文字列 (時刻のみ)
    };

    // 書式"o"で文字列をDateTimeに変換
    foreach (string input in inputs) {
      DateTime dt;

      if (DateTime.TryParseExact(input, "o", null, DateTimeStyles.None, out dt))
        Console.WriteLine("{0,-35} -> {1}", input, dt);
      else
        Console.WriteLine("{0,-35} -> (invalid format)", input);
    }
    Console.WriteLine();

    // 書式"o"で文字列をDateTimeOffsetに変換
    foreach (string input in inputs) {
      DateTimeOffset dto;

      if (DateTimeOffset.TryParseExact(input, "o", null, DateTimeStyles.None, out dto))
        Console.WriteLine("{0,-35} -> {1}", input, dto);
      else
        Console.WriteLine("{0,-35} -> (invalid format)", input);
    }
    Console.WriteLine();
  }
}
実行結果
Mon, 01 Apr 2013 15:00:30 GMT       -> (invalid format)
2013年4月1日 15:00:30                  -> (invalid format)
2013-04-01T15:00:30.1230000+09:00   -> 2013/04/01 15:00:30
2013-04-01T15:00:30.1230000         -> 2013/04/01 15:00:30
2013-04-01                          -> (invalid format)
15:00:30                            -> (invalid format)

Mon, 01 Apr 2013 15:00:30 GMT       -> (invalid format)
2013年4月1日 15:00:30                  -> (invalid format)
2013-04-01T15:00:30.1230000+09:00   -> 2013/04/01 15:00:30 +09:00
2013-04-01T15:00:30.1230000         -> 2013/04/01 15:00:30 +09:00
2013-04-01                          -> (invalid format)
15:00:30                            -> (invalid format)

このように、変換できたかどうかの結果はtrue/falseの値として返される、変換できた場合は結果をoutパラメータで受け取る、といった点はTryParseメソッドと同様ですが、TryParseExactメソッドでは変換時のオプションをDateTimeStylesで指定する必要があります。 DateTimeStylesについては追って解説します。

ParseExact・TryParseExactメソッドで複数の書式を許容するようにしたい場合は、次の例のように、その書式を配列で指定します。

using System;
using System.Globalization;

class Sample {
  static void Main()
  {
    string[] inputs = new string[] {
      "Mon, 01 Apr 2013 15:00:30 GMT",     // RFC1123形式の文字列
      "2013年4月1日 15:00:30",             // 一般的な形式の文字列
      "2013-04-01T15:00:30.1230000+09:00", // ISO8601形式の文字列
      "2013-04-01T15:00:30.1230000",       // ISO8601形式の文字列 (オフセット無し)
      "2013-04-01",                        // ISO8601形式の文字列 (日付のみ)
      "15:00:30",                          // ISO8601形式の文字列 (時刻のみ)
    };

    // 変換時に許容する書式
    string[] formats = new string[] {
      "o",          // 一般的な形式
      "yyyy-MM-dd", // 日付のみの形式
      "HH:mm:ss",   // 時刻のみの形式
    };

    // 指定した書式で文字列をDateTimeに変換
    foreach (string input in inputs) {
      DateTime dt;

      if (DateTime.TryParseExact(input, formats, null, DateTimeStyles.None, out dt))
        Console.WriteLine("{0,-35} -> {1}", input, dt);
      else
        Console.WriteLine("{0,-35} -> (invalid format)", input);
    }
    Console.WriteLine();

    // 指定した書式で文字列をDateTimeOffsetに変換
    foreach (string input in inputs) {
      DateTimeOffset dto;

      if (DateTimeOffset.TryParseExact(input, formats, null, DateTimeStyles.None, out dto))
        Console.WriteLine("{0,-35} -> {1}", input, dto);
      else
        Console.WriteLine("{0,-35} -> (invalid format)", input);
    }
    Console.WriteLine();
  }
}
実行結果
Mon, 01 Apr 2013 15:00:30 GMT       -> (invalid format)
2013年4月1日 15:00:30                  -> (invalid format)
2013-04-01T15:00:30.1230000+09:00   -> 2013/04/01 15:00:30
2013-04-01T15:00:30.1230000         -> 2013/04/01 15:00:30
2013-04-01                          -> 2013/04/01 0:00:00
15:00:30                            -> 2013/03/08 15:00:30

Mon, 01 Apr 2013 15:00:30 GMT       -> (invalid format)
2013年4月1日 15:00:30                  -> (invalid format)
2013-04-01T15:00:30.1230000+09:00   -> 2013/04/01 15:00:30 +09:00
2013-04-01T15:00:30.1230000         -> 2013/04/01 15:00:30 +09:00
2013-04-01                          -> 2013/04/01 0:00:00 +09:00
15:00:30                            -> 2013/03/08 15:00:30 +09:00

§2.3 変換時のオプション (DateTimeStyles)

Parse*メソッド・TryParse*メソッドでは、引数にDateTimeStylesの値を指定することで解析時の動作オプションを指定することが出来ます。 例えば、文字列中の空白をどう扱うか、文字列にタイムゾーンが指定されていない場合にローカル時刻もしくはUTCのどちらと見なすかといったオプションを指定することが出来ます。 DateTimeStylesには次のような値が用意されていて、この中から複数の値を組み合わせて指定することができます。

空白に関するオプション
AllowLeadingWhite, AllowTrailingWhite, AllowInnerWhite, AllowWhiteSpaces
タイムゾーン・時刻の種類に関するオプション
AssumeLocal, AssumeUniversal, AdjustToUniversal, RoudtripKind
その他のオプション
NoCurrentDateDefault, None

このうち、DateTimeStyles.Noneはオプションを一切指定しなかった場合と同じもので、既定の動作となります。 また、このうちいくつかのオプションはDateTime.ParseとDateTimeOffset.Parseで異なる動作のもの・使用できないものもあります。 DateTimeStylesのそれぞれのオプションと、その動作について詳しく見ていきます。

§2.3.1 空白に関するオプション

DateTimeStylesの空白に関するオプションには次のようなものがあります。

AllowLeadingWhite
文字列中の日時に先行する空白文字を無視する。
AllowTrailingWhite
文字列中の日時に後続する空白文字を無視する。
AllowInnerWhite
文字列中の日時の途中に存在する空白文字を無視する。
AllowWhiteSpaces
文字列中の日時に含まれる空白文字を無視する。
(上記のAllowLeadingWhite, AllowTrailingWhite, AllowInnerWhiteを組み合わせたものと同じ)

これらのオプションは厳密に書式を指定するParseExactメソッドで特に効果を発揮します。 書式だけでは定義できない空白の表記ゆれを許容するかどうかをこのオプションで指定することが出来ます。 次の表は、様々な個所に空白を含む文字列をParseExactメソッドで解析した場合の結果の違いをまとめたものです。

実行結果
DateTime.ParseExact
(input)             |None                  |AllowLeadingWhite     |AllowTrailingWhite    |AllowInnerWhite       |AllowWhiteSpaces      
'2013-04-01'        |2013/04/01 0:00:00    |2013/04/01 0:00:00    |2013/04/01 0:00:00    |2013/04/01 0:00:00    |2013/04/01 0:00:00    |
' 2013-04-01'       |(false)               |2013/04/01 0:00:00    |(false)               |2013/04/01 0:00:00    |2013/04/01 0:00:00    |
'2013-04-01 '       |(false)               |(false)               |2013/04/01 0:00:00    |(false)               |2013/04/01 0:00:00    |
' 2013-04-01 '      |(false)               |(false)               |(false)               |(false)               |2013/04/01 0:00:00    |
'2013- 4-01'        |(false)               |(false)               |(false)               |2013/04/01 0:00:00    |2013/04/01 0:00:00    |
'2013 - 4 - 1'      |(false)               |(false)               |(false)               |2013/04/01 0:00:00    |2013/04/01 0:00:00    |
' 2013 -4 - 1 '     |(false)               |(false)               |(false)               |(false)               |2013/04/01 0:00:00    |

DateTimeOffset.ParseExact
(input)             |None                        |AllowLeadingWhite           |AllowTrailingWhite          |AllowInnerWhite             |AllowWhiteSpaces            
'2013-04-01'        |2013/04/01 0:00:00 +09:00   |2013/04/01 0:00:00 +09:00   |2013/04/01 0:00:00 +09:00   |2013/04/01 0:00:00 +09:00   |2013/04/01 0:00:00 +09:00   |
' 2013-04-01'       |(false)                     |2013/04/01 0:00:00 +09:00   |(false)                     |2013/04/01 0:00:00 +09:00   |2013/04/01 0:00:00 +09:00   |
'2013-04-01 '       |(false)                     |(false)                     |2013/04/01 0:00:00 +09:00   |(false)                     |2013/04/01 0:00:00 +09:00   |
' 2013-04-01 '      |(false)                     |(false)                     |(false)                     |(false)                     |2013/04/01 0:00:00 +09:00   |
'2013- 4-01'        |(false)                     |(false)                     |(false)                     |2013/04/01 0:00:00 +09:00   |2013/04/01 0:00:00 +09:00   |
'2013 - 4 - 1'      |(false)                     |(false)                     |(false)                     |2013/04/01 0:00:00 +09:00   |2013/04/01 0:00:00 +09:00   |
' 2013 -4 - 1 '     |(false)                     |(false)                     |(false)                     |(false)                     |2013/04/01 0:00:00 +09:00   |
using System;
using System.Globalization;

class Sample {
  static void Main()
  {
    string format = "yyyy-M-d"; // "年-月-日"(0埋めなし)の書式
    string[] inputs = new string[] {
      // ISO8601形式の文字列
      "2013-04-01",     // 空白を含まない
      " 2013-04-01",    // 空白が先行する
      "2013-04-01 ",    // 空白が後続する
      " 2013-04-01 ",   // 前後に空白がある
      "2013- 4-01",     // 年と月の間に空白がある
      "2013 - 4 - 1",   // 年月日と区切り記号の間に空白がある
      " 2013 -4 - 1 ",  // 上記の例の複合
    };
    DateTimeStyles[] styles = new DateTimeStyles[] {
      DateTimeStyles.None,
      DateTimeStyles.AllowLeadingWhite,
      DateTimeStyles.AllowTrailingWhite,
      DateTimeStyles.AllowInnerWhite,
      DateTimeStyles.AllowWhiteSpaces,
    };

    // DateTimeStylesとDateTimeへの変換結果の違いを表示
    Console.WriteLine("DateTime.ParseExact");
    Console.WriteLine("{0,-20}|{1,-22}|{2,-22}|{3,-22}|{4,-22}|{5,-22}",
                      "(input)",
                      "None",
                      "AllowLeadingWhite",
                      "AllowTrailingWhite",
                      "AllowInnerWhite",
                      "AllowWhiteSpaces");

    foreach (string input in inputs) {
      Console.Write("{0,-20}|", "'" + input + "'");

      foreach (DateTimeStyles style in styles) {
        DateTime dt;

        if (DateTime.TryParseExact(input, format, null, style, out dt))
          Console.Write("{0,-22}|", dt);
        else
          Console.Write("{0,-22}|", "(false)");
      }

      Console.WriteLine();
    }
    Console.WriteLine();

    // DateTimeStylesとDateTimeOffsetへの変換結果の違いを表示
    Console.WriteLine("DateTimeOffset.ParseExact");
    Console.WriteLine("{0,-20}|{1,-28}|{2,-28}|{3,-28}|{4,-28}|{5,-28}",
                      "(input)",
                      "None",
                      "AllowLeadingWhite",
                      "AllowTrailingWhite",
                      "AllowInnerWhite",
                      "AllowWhiteSpaces");

    foreach (string input in inputs) {
      Console.Write("{0,-20}|", "'" + input + "'");

      foreach (DateTimeStyles style in styles) {
        DateTimeOffset dto;

        if (DateTimeOffset.TryParseExact(input, format, null, DateTimeStyles.AssumeLocal | style, out dto))
          Console.Write("{0,-28}|", dto);
        else
          Console.Write("{0,-28}|", "(false)");
      }

      Console.WriteLine();
    }
    Console.WriteLine();
  }
}

なお、この例では日付のみを解析する際に時刻をローカル時刻であると仮定するために、DateTimeOffset.ParseExactの呼び出し時にDateTimeStyles.AssumeLocalを指定しています。

§2.3.2 タイムゾーン・時刻の種類に関するオプション

DateTimeStylesのタイムゾーン・時刻の種類に関するオプションには次のようなものがあります。

AssumeLocal
文字列中にオフセット値またはタイムゾーン指定子が無い場合、ローカル時刻を表すものとして扱う。
AssumeUniversalおよびRoundtripKindとは排他的なオプションで、同時には指定できない。
AssumeUniversal
文字列中にオフセット値またはタイムゾーン指定子が無い場合、UTCを表すものとして扱う。
AssumeLocalおよびRoundtripKindとは排他的なオプションで、同時には指定できない。
AdjustToUniversal
解析した日時をUTCに変換する。
RoudtripKindとは排他的なオプションで、同時には指定できない。
DateTime.Parseでは、文字列中にオフセット値またはタイムゾーン指定子が無い場合、UTCへの変換は行われず、KindプロパティにDateTimeKind.Unspecifiedが設定される。
RoudtripKind
解析した日時の種類を維持する。
AdjustToUniversalとは排他的なオプションで、同時には指定できない。
DateTimeOffset.Parseでは、このオプションは無視される(動作に影響しない)。
DateTime.Parseでは、文字列中のオフセット値またはタイムゾーン指定子によってDateTime.Kindが次のように設定される。
UTCの場合
DateTime.KindプロパティにDateTimeKind.Utcが設定される。
UTC以外のオフセット値の場合
ローカル時刻に変換され、DateTime.KindプロパティにDateTimeKind.Localが設定される。
指定されていない場合
DateTime.KindプロパティにDateTimeKind.Unspecifiedが設定される。

AssumeLocalおよびAssumeUniversalは、文字列中の日時がどのタイムゾーンも表さない場合にローカル時刻もしくはUTCと見なすように指定するオプションです。 当然、オフセット値が指定されている場合は、その値が使用されます。

UTC+9の環境での実行結果例
DateTime.ParseExact
2013-04-01T15:00:30.1234567         (None           ) -> 2013/04/01 15:00:30 (Unspecified)
2013-04-01T15:00:30.1234567         (AssumeLocal    ) -> 2013/04/01 15:00:30 (Local)
2013-04-01T15:00:30.1234567         (AssumeUniversal) -> 2013/04/02 0:00:30 (Local)

2013-04-01T15:00:30.1234567+00:00   (None           ) -> 2013/04/02 0:00:30 (Local)
2013-04-01T15:00:30.1234567+00:00   (AssumeLocal    ) -> 2013/04/02 0:00:30 (Local)
2013-04-01T15:00:30.1234567+00:00   (AssumeUniversal) -> 2013/04/02 0:00:30 (Local)

2013-04-01T15:00:30.1234567+09:00   (None           ) -> 2013/04/01 15:00:30 (Local)
2013-04-01T15:00:30.1234567+09:00   (AssumeLocal    ) -> 2013/04/01 15:00:30 (Local)
2013-04-01T15:00:30.1234567+09:00   (AssumeUniversal) -> 2013/04/01 15:00:30 (Local)

2013-04-01T15:00:30.1234567-05:00   (None           ) -> 2013/04/02 5:00:30 (Local)
2013-04-01T15:00:30.1234567-05:00   (AssumeLocal    ) -> 2013/04/02 5:00:30 (Local)
2013-04-01T15:00:30.1234567-05:00   (AssumeUniversal) -> 2013/04/02 5:00:30 (Local)


DateTimeOffset.ParseExact
2013-04-01T15:00:30.1234567         (None           ) -> 2013/04/01 15:00:30 +09:00
2013-04-01T15:00:30.1234567         (AssumeLocal    ) -> 2013/04/01 15:00:30 +09:00
2013-04-01T15:00:30.1234567         (AssumeUniversal) -> 2013/04/01 15:00:30 +00:00

2013-04-01T15:00:30.1234567+00:00   (None           ) -> 2013/04/01 15:00:30 +00:00
2013-04-01T15:00:30.1234567+00:00   (AssumeLocal    ) -> 2013/04/01 15:00:30 +00:00
2013-04-01T15:00:30.1234567+00:00   (AssumeUniversal) -> 2013/04/01 15:00:30 +00:00

2013-04-01T15:00:30.1234567+09:00   (None           ) -> 2013/04/01 15:00:30 +09:00
2013-04-01T15:00:30.1234567+09:00   (AssumeLocal    ) -> 2013/04/01 15:00:30 +09:00
2013-04-01T15:00:30.1234567+09:00   (AssumeUniversal) -> 2013/04/01 15:00:30 +09:00

2013-04-01T15:00:30.1234567-05:00   (None           ) -> 2013/04/01 15:00:30 -05:00
2013-04-01T15:00:30.1234567-05:00   (AssumeLocal    ) -> 2013/04/01 15:00:30 -05:00
2013-04-01T15:00:30.1234567-05:00   (AssumeUniversal) -> 2013/04/01 15:00:30 -05:00
using System;
using System.Globalization;

class Sample {
  static void Main()
  {
    string[] inputs = new string[] {
      "2013-04-01T15:00:30.1234567",        // タイムゾーンの明記無し
      "2013-04-01T15:00:30.1234567+00:00",  // UTC+0での時刻
      "2013-04-01T15:00:30.1234567+09:00",  // UTC+9での時刻
      "2013-04-01T15:00:30.1234567-05:00",  // UTC-5での時刻
    };
    DateTimeStyles[] styles = new DateTimeStyles[] {
      DateTimeStyles.None,              // オプション指定なし (デフォルトの動作)
      DateTimeStyles.AssumeLocal,       // 時刻はローカル時刻と仮定する
      DateTimeStyles.AssumeUniversal,   // 時刻はUTCと仮定する
    };

    // 文字列をDateTimeへ変換
    Console.WriteLine("DateTime.ParseExact");
    foreach (string input in inputs) {
      foreach (DateTimeStyles style in styles) {
        DateTime dt = DateTime.ParseExact(input, "o", null, style);

        Console.WriteLine("{0,-35} ({1,-15}) -> {2} ({3})", input, style, dt, dt.Kind);
      }
      Console.WriteLine();
    }
    Console.WriteLine();

    // 文字列をDateTimeOffsetへ変換
    Console.WriteLine("DateTimeOffset.ParseExact");
    foreach (string input in inputs) {
      foreach (DateTimeStyles style in styles) {
        try {
          DateTimeOffset dto = DateTimeOffset.ParseExact(input, "o", null, style);

          Console.WriteLine("{0,-35} ({1,-15}) -> {2}", input, style, dto);
        }
        catch (FormatException) {
          Console.WriteLine("{0,-35} ({1,-15}) -> (FormatException)", input, style);
        }
      }
      Console.WriteLine();
    }
    Console.WriteLine();
  }
}

AdjustToUniversalが指定された場合は、解析した日時をUTCに補正します。 このため、DateTime.Kindは常にDateTimeKind.Utc、DateTimeOffset.Offsetは 00:00:00 となります。 文字列中の日時がどのタイムゾーンも表さない場合、時刻の補正は行われず、DateTime.KindにはDateTimeKind.Unspecifiedが設定されます。

一方RoudtripKindが指定された場合は、解析した日時の種類とタイムゾーン情報を維持します。 時刻に 'Z' や 'GMT' などのUTCであることを表す文字列が含まれていれば、DateTimeKind.Utcとして扱われます。 このオプションはDateTimeOffsetでは無視されます。

UTC+9の環境での実行結果例
DateTime.ParseExact
2013-04-01T15:00:30.1234567         (None              ) -> 2013/04/01 15:00:30 (Unspecified)
2013-04-01T15:00:30.1234567Z        (None              ) -> 2013/04/02 0:00:30 (Local)
2013-04-01T15:00:30.1234567+00:00   (None              ) -> 2013/04/02 0:00:30 (Local)
2013-04-01T15:00:30.1234567+09:00   (None              ) -> 2013/04/01 15:00:30 (Local)
2013-04-01T15:00:30.1234567-05:00   (None              ) -> 2013/04/02 5:00:30 (Local)

2013-04-01T15:00:30.1234567         (AdjustToUniversal ) -> 2013/04/01 15:00:30 (Unspecified)
2013-04-01T15:00:30.1234567Z        (AdjustToUniversal ) -> 2013/04/01 15:00:30 (Utc)
2013-04-01T15:00:30.1234567+00:00   (AdjustToUniversal ) -> 2013/04/01 15:00:30 (Utc)
2013-04-01T15:00:30.1234567+09:00   (AdjustToUniversal ) -> 2013/04/01 6:00:30 (Utc)
2013-04-01T15:00:30.1234567-05:00   (AdjustToUniversal ) -> 2013/04/01 20:00:30 (Utc)

2013-04-01T15:00:30.1234567         (RoundtripKind     ) -> 2013/04/01 15:00:30 (Unspecified)
2013-04-01T15:00:30.1234567Z        (RoundtripKind     ) -> 2013/04/01 15:00:30 (Utc)
2013-04-01T15:00:30.1234567+00:00   (RoundtripKind     ) -> 2013/04/02 0:00:30 (Local)
2013-04-01T15:00:30.1234567+09:00   (RoundtripKind     ) -> 2013/04/01 15:00:30 (Local)
2013-04-01T15:00:30.1234567-05:00   (RoundtripKind     ) -> 2013/04/02 5:00:30 (Local)


DateTimeOffset.ParseExact
2013-04-01T15:00:30.1234567         (None              ) -> 2013/04/01 15:00:30 +09:00
2013-04-01T15:00:30.1234567Z        (None              ) -> 2013/04/01 15:00:30 +00:00
2013-04-01T15:00:30.1234567+00:00   (None              ) -> 2013/04/01 15:00:30 +00:00
2013-04-01T15:00:30.1234567+09:00   (None              ) -> 2013/04/01 15:00:30 +09:00
2013-04-01T15:00:30.1234567-05:00   (None              ) -> 2013/04/01 15:00:30 -05:00

2013-04-01T15:00:30.1234567         (AdjustToUniversal ) -> 2013/04/01 6:00:30 +00:00
2013-04-01T15:00:30.1234567Z        (AdjustToUniversal ) -> 2013/04/01 15:00:30 +00:00
2013-04-01T15:00:30.1234567+00:00   (AdjustToUniversal ) -> 2013/04/01 15:00:30 +00:00
2013-04-01T15:00:30.1234567+09:00   (AdjustToUniversal ) -> 2013/04/01 6:00:30 +00:00
2013-04-01T15:00:30.1234567-05:00   (AdjustToUniversal ) -> 2013/04/01 20:00:30 +00:00

2013-04-01T15:00:30.1234567         (RoundtripKind     ) -> 2013/04/01 15:00:30 +09:00
2013-04-01T15:00:30.1234567Z        (RoundtripKind     ) -> 2013/04/01 15:00:30 +00:00
2013-04-01T15:00:30.1234567+00:00   (RoundtripKind     ) -> 2013/04/01 15:00:30 +00:00
2013-04-01T15:00:30.1234567+09:00   (RoundtripKind     ) -> 2013/04/01 15:00:30 +09:00
2013-04-01T15:00:30.1234567-05:00   (RoundtripKind     ) -> 2013/04/01 15:00:30 -05:00
using System;
using System.Globalization;

class Sample {
  static void Main()
  {
    string[] inputs = new string[] {
      "2013-04-01T15:00:30.1234567",        // タイムゾーンの明記無し
      "2013-04-01T15:00:30.1234567Z",       // UTCでの時刻
      "2013-04-01T15:00:30.1234567+00:00",  // UTC+0での時刻
      "2013-04-01T15:00:30.1234567+09:00",  // UTC+9での時刻
      "2013-04-01T15:00:30.1234567-05:00",  // UTC-5での時刻
    };
    DateTimeStyles[] styles = new DateTimeStyles[] {
      DateTimeStyles.None,              // オプション指定なし (デフォルトの動作)
      DateTimeStyles.AdjustToUniversal, // 時刻をUTCに補正する
      DateTimeStyles.RoundtripKind,     // 時刻のタイムゾーン情報を維持する
    };

    // 文字列をDateTimeへ変換
    Console.WriteLine("DateTime.ParseExact");
    foreach (DateTimeStyles style in styles) {
      foreach (string input in inputs) {
        DateTime dt = DateTime.ParseExact(input, "o", null, style);

        Console.WriteLine("{0,-35} ({1,-18}) -> {2} ({3})", input, style, dt, dt.Kind);
      }
      Console.WriteLine();
    }
    Console.WriteLine();

    // 文字列をDateTimeOffsetへ変換
    Console.WriteLine("DateTimeOffset.ParseExact");
    foreach (DateTimeStyles style in styles) {
      foreach (string input in inputs) {
        try {
          DateTimeOffset dto = DateTimeOffset.ParseExact(input, "o", null, style);

          Console.WriteLine("{0,-35} ({1,-18}) -> {2}", input, style, dto);
        }
        catch (FormatException) {
          Console.WriteLine("{0,-35} ({1,-18}) -> (FormatException)", input, style);
        }
      }
      Console.WriteLine();
    }
    Console.WriteLine();
  }
}

§2.3.3 NoCurrentDateDefault

NoCurrentDateDefaultは、文字列中の日付に欠損がある場合、現在の日付ではなく1年1月1日(DateTime.Min)を表すものとして変換を行います。 なお、このオプションはDateTimeOffset.Parseでは無効で、指定した場合は例外ArgumentExceptionがスローされます。

なお、日付のみで時刻がない場合は常に 00:00:00 を表すものとして変換を行います。 これはDateTimeおよびDateTimeOffsetともに同じ動作となります。

実行結果例
2013/04/03 15:00:30  (None                  ) -> 2013/04/03 15:00:30
2013/04/03 15:00:30  (NoCurrentDateDefault  ) -> 2013/04/03 15:00:30

15:00:30             (None                  ) -> 2013/03/02 15:00:30
15:00:30             (NoCurrentDateDefault  ) -> 0001/01/01 15:00:30

15:00                (None                  ) -> 2013/03/02 15:00:00
15:00                (NoCurrentDateDefault  ) -> 0001/01/01 15:00:00
using System;
using System.Globalization;

class Sample {
  static void Main()
  {
    string[] inputs = new string[] {
      "2013/04/03 15:00:30",  // 日付と時刻
      "15:00:30",             // 時刻のみ
      "15:00",                // 時分のみ
    };
    DateTimeStyles[] styles = new DateTimeStyles[] {
      DateTimeStyles.None,                  // オプション指定なし (デフォルトの動作)
      DateTimeStyles.NoCurrentDateDefault,  // 日付がない場合、1年1月1日を設定する
    };

    // 文字列をDateTimeへ変換
    foreach (string input in inputs) {
      foreach (DateTimeStyles style in styles) {
        DateTime dt = DateTime.Parse(input, null, style);

        Console.WriteLine("{0,-20} ({1,-22}) -> {2}", input, style, dt);
      }
      Console.WriteLine();
    }
  }
}