日付や時刻の文字列をDateTime/DateTimeOffsetオブジェクトに変換するには?:.NET TIPS
DateTime/DateTimeOffset構造体のParseExactメソッドを使い、独自形式の文字列で表現されている日時をDateTime/DateTimeOffsetオブジェクトに変換する方法を説明する。
本稿は2004/09/03に初版公開した記事を改訂し、Visual Studio 2017でコードの動作検証、DateTimeOffset構造体について追記、図版の追加、全般的な構成の変更などを行ったものです。
「TIPS:日付や時刻を文字列に変換するには?」では、DateTime構造体(System名前空間)のオブジェクトを、カスタム書式指定により独自の形式の文字列に変換する方法について示した。
本稿では、これとは逆に、独自形式の日付や時刻の文字列をDateTimeオブジェクトに変換する方法について示す。なお、本稿の大部分で扱う文字列は、その形式が完全に固定されたもののみを想定している(例えば「2018/08/24 20:23:06」や「201808242123」など)。
特定のトピックをすぐに知りたいという方は以下のリンクを活用してほしい。
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
なお、指定した形式に合致しない日時文字列をパラメーターとして渡した場合には例外が発生する(次のコード)。
// 冒頭に「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
例外を出さない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
第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メソッドの使い方はDateTime構造体のTryParseExactメソッドと同様である。パラメーターの意味などは、前項のサンプルコードの説明をご覧いただきたい。
3番目の例では、UTCの日時文字列を渡しているので、DateTimeOffsetオブジェクトのオフセットも0になっている。ローカル時刻を得るには、DateTimeOffset構造体のToLocalTimeメソッドを使う。
なお、このコードでは、C# 6.0/VB 14(Visual Studio 2015)で導入された補間文字列を使っている。詳しくは「数値を右詰めや0埋めで文字列化するには?[C#、VB]」の後半をご覧いただきたい。
なお、オフセットとタイムゾーンは別のものだ。同じタイムゾーンでも、サマータイムでオフセットが変わることがある。また、同じオフセットを持つ複数のタイムゾーンが存在することもある(次の画像)。DateTimeOffsetオブジェクトは、オフセットは持てるがタイムゾーンは持てない。タイムゾーン情報も必要なときは、DateTimeOffsetオブジェクトとは別に保存しなければならない。
タイムゾーンを選択するドロップダウンリストの例(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
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】初版公開。
Copyright© Digital Advantage Corp. All Rights Reserved.