日付や時刻の文字列をDateTime/DateTimeOffsetオブジェクトに変換するには?.NET TIPS

DateTime/DateTimeOffset構造体のParseExactメソッドを使い、独自形式の文字列で表現されている日時をDateTime/DateTimeOffsetオブジェクトに変換する方法を説明する。

» 2018年09月12日 05時00分 公開
「.NET TIPS」のインデックス

連載「.NET TIPS」

本稿は2004/09/03に初版公開した記事を改訂し、Visual Studio 2017でコードの動作検証、DateTimeOffset構造体について追記、図版の追加、全般的な構成の変更などを行ったものです。


 「TIPS:日付や時刻を文字列に変換するには?」では、DateTime構造体(System名前空間)のオブジェクトを、カスタム書式指定により独自の形式の文字列に変換する方法について示した。

 本稿では、これとは逆に、独自形式の日付や時刻の文字列をDateTimeオブジェクトに変換する方法について示す。なお、本稿の大部分で扱う文字列は、その形式が完全に固定されたもののみを想定している(例えば「2018/08/24 20:23:06」や「201808242123」など)。

POINT 独自形式の日付や時刻の文字列をDateTimeオブジェクトに変換する方法

独自形式の日付や時刻の文字列をDateTimeオブジェクトに変換する方法 独自形式の日付や時刻の文字列をDateTimeオブジェクトに変換する方法


 特定のトピックをすぐに知りたいという方は以下のリンクを活用してほしい。

ParseExactメソッドによる文字列の変換

 DateTime構造体には、文字列を変換するためのメソッドとして、ParseメソッドとParseExactメソッドが用意されているが、前者のParseメソッドはカルチャ情報を使用して出力された文字列を、同じカルチャ情報を使用してDateTimeオブジェクトに変換するためのものである。独自の形式を持った文字列を扱う場合には、後者のParseExactメソッドを使用しなければならない。

 ParseExactメソッドでは、次のように、第1パラメーターには入力文字列を、第2パラメーターには入力文字列の形式をカスタム書式指定文字列により指定する。メソッドの戻り値はDateTime型である。

DateTime.ParseExact("2018/08/24 20:23:06", "yyyy/MM/dd HH:mm:ss", null);

 第3パラメーターには、カルチャ固有の書式情報を提供するIFormatProviderオブジェクトを指定するが、第2パラメーターで指定したカスタム書式指定文字列にカルチャ依存の書式指定子が含まれていない場合はnull(VB.NETの場合にはNothing)でよい*1

*1 実際には、これはデフォルトのカルチャ情報(System.Globalization.CultureInfo.CurrentCulture.DateTimeFormat)を指定した場合と同じである。


 ParseExactメソッドの第2パラメーターで指定するカスタム書式指定文字列を記述するために、よく利用されると思われる書式指定子を次にまとめた。

書式指定子 説明 入力例
yyyy 4けたの年 2018
yy 0埋め2けたの年 04
MM 0埋め2けたの月 08
dd 0埋め2けたの日 24
HH 0埋め2けたの時間(24時間表記) 20
hh 0埋め2けたの時間(12時間表記) 08
mm 0埋め2けたの分 23
ss 0埋め2けたの秒 06
代表的な書式指定子

 ここでは固定幅でかつカルチャ情報に依存しない書式指定子のみを挙げている(書式指定子の完全な一覧は、リファレンスマニュアルの「カスタム日時書式指定文字列」などを参照)。

日時文字列をDateTimeオブジェクトに変換するサンプルプログラム

 ParseExactメソッドを利用して、日時文字列をDateTimeオブジェクトに変換する簡単なサンプルプログラムを次に示す。Visual Studio 2017でVBプロジェクトを新規作成して、以下のコードを試す場合には、Mainプロシージャ内のコードをコピー&ペーストしてほしい。

// dateparse.cs

using System;

public class DateTimeParse {
  static void Main() {

    string d, f;
    DateTime dt;

    d = "2018/08/24 20:23:06";
    f = "yyyy/MM/dd HH:mm:ss";
    dt = DateTime.ParseExact(d, f, null);

    Console.WriteLine(dt.ToString("F"));
    // 出力:2018年8月24日 20:23:06

    d = "20180824202306";
    f = "yyyyMMddHHmmss";
    dt = DateTime.ParseExact(d, f, null);

    Console.WriteLine(dt.ToString("F"));
    // 出力:2018年8月24日 20:23:06

    d = "2018年08月24日20時23分06秒";
    f = "yyyy年MM月dd日HH時mm分ss秒";
    dt = DateTime.ParseExact(d, f, null);

    Console.WriteLine(dt.ToString("F"));
    // 出力:2018年8月24日 20:23:06
  }
}

// コンパイル方法:csc dateparse.cs

' dateparse.vb

Imports System

Public Class CustomDateTime 
  Shared Sub Main()

    Dim d, f As String
    Dim dt As DateTime

    d = "2018/08/24 20:23:06"
    f = "yyyy/MM/dd HH:mm:ss"
    dt = DateTime.ParseExact(d, f, Nothing)

    Console.WriteLine(dt.ToString("F"))
    ' 出力:2018年8月24日 20:23:06

    d = "20180824202306"
    f = "yyyyMMddHHmmss"
    dt = DateTime.ParseExact(d, f, Nothing)

    Console.WriteLine(dt.ToString("F"))
    ' 出力:2018年8月24日 20:23:06

    d = "2018年08月24日20時23分06秒"
    f = "yyyy年MM月dd日HH時mm分ss秒"
    dt = DateTime.ParseExact(d, f, Nothing)

    Console.WriteLine(dt.ToString("F"))
    ' 出力:2018年8月24日 20:23:06
  End Sub
End Class

' コンパイル方法:vbc dateparse.vb

日時文字列をDateTimeオブジェクトに変換するサンプルプログラム(上:C#、下:VB)
C#版サンプルプログラムのダウンロード
VB版サンプルプログラムのダウンロード

 なお、指定した形式に合致しない日時文字列をパラメーターとして渡した場合には例外が発生する(次のコード)。

// 冒頭に「using System;」が必要

string d, f;
DateTime dt;

f = "yyyy年MM月dd日HH時mm分ss秒";

// 指定した形式に合致しない場合
d = "2018年08月24日20時23分6秒"; // 秒の前0がない
try
{
  dt = DateTime.ParseExact(d, f, null);
}
catch (Exception ex)
{
  Console.WriteLine(string.Format("{0}: {1}", ex.GetType().Name, ex.Message));
  // 出力:FormatException: 文字列は有効な DateTime ではありませんでした。
}

// あり得ない日時
d = "2018年02月29日20時23分06秒"; // 2018年はうるう年ではない
try
{
  dt = DateTime.ParseExact(d, f, null);
}
catch (Exception ex)
{
  Console.WriteLine(string.Format("{0}: {1}", ex.GetType().Name, ex.Message));
  // 出力:FormatException: 文字列で表される DateTime がカレンダー System.Globalization.GregorianCalendar でサポートされていません 。
}

Dim d, f As String
Dim dt As DateTime

f = "yyyy年MM月dd日HH時mm分ss秒"

' 指定した形式に合致しない場合
d = "2018年08月24日20時23分6秒" ' 秒の前0がない
Try
  dt = DateTime.ParseExact(d, f, Nothing)
Catch ex As Exception
  Console.WriteLine(String.Format("{0}: {1}", ex.GetType().Name, ex.Message))
  ' 出力:FormatException: 文字列は有効な DateTime ではありませんでした。
End Try

' あり得ない日時
d = "2018年02月29日20時23分06秒" ' 2018年はうるう年ではない
Try
  dt = DateTime.ParseExact(d, f, Nothing)
Catch ex As Exception
  Console.WriteLine(String.Format("{0}: {1}", ex.GetType().Name, ex.Message))
  ' 出力:FormatException: 文字列で表される DateTime がカレンダー System.Globalization.GregorianCalendar でサポートされていません 。
End Try

解釈できない日時文字列を与えると例外が発生する(上:C#、下:VB)

例外を出さないTryParseExactメソッド[.NET 2.0以降]

 DateTime構造体に.NET Framework 2.0で導入されたTryParseExactメソッドは、日時文字列が解釈できなかったときに、例外を発生させることなくメソッドの返値としてfalseを返す(次のコード)。例外処理の煩雑なコードを書かなくて済む。

// 冒頭に「using System;」と「using System.Globalization;」が必要

string d, f;
DateTime dt;

d = "2016/02/29 01:23:45";
f = "yyyy/MM/dd HH:mm:ss";
if (DateTime.TryParseExact(d, f, null, DateTimeStyles.AssumeLocal, out dt))
  Console.WriteLine($"{dt:F} ({dt.Kind})");
// 出力:2016年2月29日 1:23:45 (Local)
// 2016年はうるう年

d = "2018/02/29 01:23:45";
if (!DateTime.TryParseExact(d, f, null, DateTimeStyles.AssumeLocal, out dt))
  Console.WriteLine($"\"{d}\"は日時として正しくありません");
// 出力:"2018/02/29 01:23:45"は日時として正しくありません

d = "2018-08-24T11:23:06.000Z"; // 末尾の「Z」はUTC(世界協定時)を示す
f = "yyyy-MM-ddTHH:mm:ss.fffZ";
if (DateTime.TryParseExact(d, f, null, DateTimeStyles.AssumeLocal, out dt))
  Console.WriteLine($"{dt:F} ({dt.Kind})");
// 出力:2018年8月24日 20:23:06 (Local)

d = "2018-08-24T20:23:06.000+09:00";
f = "yyyy-MM-ddTHH:mm:ss.fffzzz"; //「zzz」はUTCからのオフセットを表す
if (DateTime.TryParseExact(d, f, null, DateTimeStyles.AssumeLocal, out dt))
  Console.WriteLine($"{dt:F} ({dt.Kind})");
// 出力:2018年8月24日 20:23:06 (Local)

' 冒頭に「Imports System.Globalization」が必要

Dim d, f As String
Dim dt As DateTime

d = "2016/02/29 01:23:45"
f = "yyyy/MM/dd HH:mm:ss"
If (DateTime.TryParseExact(d, f, Nothing, DateTimeStyles.AssumeLocal, dt)) Then
  Console.WriteLine($"{dt:F} ({dt.Kind})")
  ' 出力:2016年2月29日 1:23:45 (Local)
  ' 2016年はうるう年
End If

d = "2018/02/29 01:23:45"
If (Not DateTime.TryParseExact(d, f, Nothing, DateTimeStyles.AssumeLocal, dt)) Then
  Console.WriteLine($"""{d}""は日時として正しくありません")
  ' 出力:"2018/02/29 01:23:45"は日時として正しくありません
End If

d = "2018-08-24T11:23:06.000Z" ' 末尾の「Z」はUTC(世界協定時)を示す
f = "yyyy-MM-ddTHH:mm:ss.fffZ"
If (DateTime.TryParseExact(d, f, Nothing, DateTimeStyles.AssumeLocal, dt)) Then
  Console.WriteLine($"{dt:F} ({dt.Kind})")
  ' 出力:2018年8月24日 20:23:06 (Local)
End If

d = "2018-08-24T20:23:06.000+09:00"
f = "yyyy-MM-ddTHH:mm:ss.fffzzz" '「zzz」はUTCからのオフセットを表す
If (DateTime.TryParseExact(d, f, Nothing, DateTimeStyles.AssumeLocal, dt)) Then
  Console.WriteLine($"{dt:F} ({dt.Kind})")
  ' 出力:2018年8月24日 20:23:06 (Local)
End If

日時文字列をDateTime構造体のTryParseExactメソッドで読み取る例(上:C#、下:VB)
第3パラメーターまでは、ParseExactメソッドと同じである。
第4パラメーターのDateTimeStyles列挙型は、特に指定するスタイルがないときはDateTimeStyles.Noneにすればよい。ここで指定しているDateTimeStyles.AssumeLocalは、日時文字列にUTCからのオフセットが含まれていないときにローカル時刻と見なす。他には、日時文字列中の空白文字を読み飛ばすためのDateTimeStyles.AllowInnerWhiteなどがある。
最後のパラメーターには、出力用としてDateTimeオブジェクトを渡す。
3番目の例では、UTCの日時文字列を渡しているが、正しくローカル時刻に変換されている。
なお、このコードでは、C# 6.0/VB 14(Visual Studio 2015)で導入された補間文字列を使っている。詳しくは「数値を右詰めや0埋めで文字列化するには?[C#、VB]」の後半をご覧いただきたい。

UTCオフセットを考慮するならDateTimeOffset構造体[.NET 2.0以降]

 ここまで紹介してきたDateTime構造体は、UTC(世界協定時)とのオフセット(時差)を持てない。オフセットも扱うなら、.NET Framework 2.0で導入されたDateTimeOffset構造体(System名前空間)を利用する。

 日時文字列をDateTimeOffsetオブジェクトに変換するには、DateTimeOffset構造体のTryParseExactメソッドを使用する。その使い方はDateTime構造体のTryParseExactメソッドと同様である(次のコード)。

// 冒頭に「using System;」と「using System.Globalization;」が必要

string d, f;
DateTimeOffset dto;

d = "2016/02/29 01:23:45";
f = "yyyy/MM/dd HH:mm:ss";
if (DateTimeOffset.TryParseExact(d, f, null, DateTimeStyles.AssumeLocal, out dto))
  Console.WriteLine($"{dto} (local time={dto.ToLocalTime():F})");
// 出力:2016/02/29 1:23:45 +09:00 (local time=2016年2月29日 1:23:45)

d = "2018/02/29 01:23:45";
if (!DateTimeOffset.TryParseExact(d, f, null, DateTimeStyles.AssumeLocal, out dto))
  Console.WriteLine($"\"{d}\"は日時として正しくありません");
// 出力:"2018/02/29 01:23:45"は日時として正しくありません

d = "2018-08-24T11:23:06.000Z";
f = "yyyy-MM-ddTHH:mm:ss.fffZ";
if (DateTimeOffset.TryParseExact(d, f, null, DateTimeStyles.AssumeLocal, out dto))
  Console.WriteLine($"{dto} (local time={dto.ToLocalTime():F})");
// 出力:2018/08/24 11:23:06 +00:00 (local time=2018年8月24日 20:23:06)

d = "2018-08-24T20:23:06.000+09:00";
f = "yyyy-MM-ddTHH:mm:ss.fffzzz";
if (DateTimeOffset.TryParseExact(d, f, null, DateTimeStyles.AssumeLocal, out dto))
  Console.WriteLine($"{dto} (local time={dto.ToLocalTime():F})");
// 出力:2018/08/24 20:23:06 +09:00 (local time=2018年8月24日 20:23:06)

' 冒頭に「Imports System.Globalization」が必要

Dim d, f As String
Dim dto As DateTimeOffset

d = "2016/02/29 01:23:45"
f = "yyyy/MM/dd HH:mm:ss"
If (DateTimeOffset.TryParseExact(d, f, Nothing, DateTimeStyles.AssumeLocal, dto)) Then
  Console.WriteLine($"{dto} (local time={dto.ToLocalTime():F})")
  ' 出力:2016/02/29 1:23:45 +09:00 (local time=2016年2月29日 1:23:45)
End If

d = "2018/02/29 01:23:45"
If (Not DateTimeOffset.TryParseExact(d, f, Nothing, DateTimeStyles.AssumeLocal, dto)) Then
  Console.WriteLine($"""{d}""は日時として正しくありません")
  ' 出力:"2018/02/29 01:23:45"は日時として正しくありません
End If

d = "2018-08-24T11:23:06.000Z"
f = "yyyy-MM-ddTHH:mm:ss.fffZ"
If (DateTimeOffset.TryParseExact(d, f, Nothing, DateTimeStyles.AssumeLocal, dto)) Then
  Console.WriteLine($"{dto} (local time={dto.ToLocalTime():F})")
  ' 出力:2018/08/24 11:23:06 +00:00 (local time=2018年8月24日 20:23:06)
End If

d = "2018-08-24T20:23:06.000+09:00"
f = "yyyy-MM-ddTHH:mm:ss.fffzzz"
If (DateTimeOffset.TryParseExact(d, f, Nothing, DateTimeStyles.AssumeLocal, dto)) Then
  Console.WriteLine($"{dto} (local time={dto.ToLocalTime():F})")
  ' 出力:2018/08/24 20:23:06 +09:00 (local time=2018年8月24日 20:23:06)
End If

日時文字列をDateTimeOffset構造体のTryParseExactメソッドで読み取る例(上:C#、下:VB)
DateTimeOffset構造体のTryParseExactメソッドの使い方はDateTime構造体のTryParseExactメソッドと同様である。パラメーターの意味などは、前項のサンプルコードの説明をご覧いただきたい。
3番目の例では、UTCの日時文字列を渡しているので、DateTimeOffsetオブジェクトのオフセットも0になっている。ローカル時刻を得るには、DateTimeOffset構造体のToLocalTimeメソッドを使う。
なお、このコードでは、C# 6.0/VB 14(Visual Studio 2015)で導入された補間文字列を使っている。詳しくは「数値を右詰めや0埋めで文字列化するには?[C#、VB]」の後半をご覧いただきたい。

 なお、オフセットとタイムゾーンは別のものだ。同じタイムゾーンでも、サマータイムでオフセットが変わることがある。また、同じオフセットを持つ複数のタイムゾーンが存在することもある(次の画像)。DateTimeOffsetオブジェクトは、オフセットは持てるがタイムゾーンは持てない。タイムゾーン情報も必要なときは、DateTimeOffsetオブジェクトとは別に保存しなければならない。

タイムゾーンを選択するドロップダウンリストの例(Windows 10) タイムゾーンを選択するドロップダウンリストの例(Windows 10)
オフセットが+9時間であるタイムゾーンが、ソウルから平壌までの5つ並んでいる。オフセットが+9時間だからといって、そのタイムゾーンが日本標準時であるとは限らない。

ISO 8601形式ならTryParseメソッドでよい

 ここまでは、入力される日時文字列のフォーマットが固定であるとしてきた。ところが、Webサービスなどではそうはいかない。クライアントのJavaScriptが生成する日時文字列のパターンは1つではないからだ。ただし、Webでやりとりする日時文字列のフォーマットISO 8601形式と定められている(正確にはISO 8601形式の部分集合)。前項のサンプルコードで末尾に「Z」や「+9:00」と付いている日時文字列が、ISO 8601形式だ。

 日時文字列に多くのパターンがある場合、それをTryParseExactメソッドで解析するのは大変だ。ISO 8601形式であると分かっているなら、TryParseメソッドを使えばよい。

// 冒頭に「using System;」が必要

string d;
DateTimeOffset dto;

d = "2018-08-24T11:23Z";
if (DateTimeOffset.TryParse(d, out dto))
  Console.WriteLine($"\"{d}\" ⇒ {dto} (local time={dto.ToLocalTime():F})");
// 出力:"2018-08-24T11:23Z" ⇒ 2018/08/24 11:23:00 +00:00 (local time=2018年8月24日 20:23:00)

d = "2018-08-24T16:23:06+05:00";
if (DateTimeOffset.TryParse(d, out dto))
  Console.WriteLine($"\"{d}\" ⇒ {dto} (local time={dto.ToLocalTime():F})");
// 出力:"2018-08-24T16:23:06+05:00" ⇒ 2018/08/24 16:23:06 +05:00 (local time=2018年8月24日 20:23:06)

d = "2018-08-24T20:23:06.000+09:00";
if (DateTimeOffset.TryParse(d, out dto))
  Console.WriteLine($"\"{d}\" ⇒ {dto} (local time={dto.ToLocalTime():F})");
// 出力:"2018-08-24T20:23:06.000+09:00" ⇒ 2018/08/24 20:23:06 +09:00 (local time=2018年8月24日 20:23:06)

Dim d As String
Dim dto As DateTimeOffset

d = "2018-08-24T11:23Z"
If (DateTimeOffset.TryParse(d, dto)) Then
  Console.WriteLine($"""{d}"" ⇒ {dto} (local time={dto.ToLocalTime():F})")
  ' 出力:"2018-08-24T11:23Z" ⇒ 2018/08/24 11:23:00 +00:00 (local time=2018年8月24日 20:23:00)
End If

d = "2018-08-24T16:23:06+05:00"
If (DateTimeOffset.TryParse(d, dto)) Then
  Console.WriteLine($"""{d}"" ⇒ {dto} (local time={dto.ToLocalTime():F})")
  ' 出力:"2018-08-24T16:23:06+05:00" ⇒ 2018/08/24 16:23:06 +05:00 (local time=2018年8月24日 20:23:06)
End If

d = "2018-08-24T20:23:06.000+09:00"
If (DateTimeOffset.TryParse(d, dto)) Then
  Console.WriteLine($"""{d}"" ⇒ {dto} (local time={dto.ToLocalTime():F})")
  ' 出力:"2018-08-24T20:23:06.000+09:00" ⇒ 2018/08/24 20:23:06 +09:00 (local time=2018年8月24日 20:23:06)
End If

ISO 8601形式の日時文字列をDateTimeOffset構造体のTryParseメソッドで読み取る例(上:C#、下:VB)
TryParseメソッドでは、パラメーターにフォーマット文字列もDateTimeStyles列挙型も指定しなくてよい。1つの日時を表すISO 8601形式の文字列であれば、きちんと解釈してくれる。
なお、このコードでは、C# 6.0/VB 14(Visual Studio 2015)で導入された補間文字列を使っている。詳しくは「数値を右詰めや0埋めで文字列化するには?[C#、VB]」の後半をご覧いただきたい。

カテゴリ:クラスライブラリ 処理対象:文字列
カテゴリ:クラスライブラリ 処理対象:日付と時刻
使用ライブラリ:DateTime構造体(System名前空間)
使用ライブラリ:DateTimeOffset構造体(System名前空間)
関連TIPS:日付や時刻を文字列に変換するには?
関連TIPS:DateTimeとDateTimeOffsetの違いとは?[C#、VB]
関連TIPS:タイムゾーンから時差を求めるには?[C#、VB]
関連TIPS:サマータイムを処理するには?[.NET 3.5、C#/VB]
関連TIPS:数値を右詰めや0埋めで文字列化するには?[C#、VB]


更新履歴

【2018/09/12】Visual Studio 2017でコードの動作検証、DateTimeOffset構造体について追記、図版の追加、全般的な構成の変更などを行いました。

【2004/09/03】初版公開。


「.NET TIPS」のインデックス

.NET TIPS

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。