[C#] DateTimeKind の変換方法と意識すること
DateTime
には Kind
プロパティがあり、Utc
や Local
という状態を持つ場合があります。
基本的には Unspecified
であり、Ticks
の日時のみが処理されます。
しかし一部の処理では Kind
を意識する必要があるため、変換方法や注意点をまとめます。
環境
- .NET 8.0.100
- C# 12.0
- Visual Studio 2022 Version 17.8.5
- Windows 11 Pro 22H2 22621.3007
- 日本標準時 UTC+0900
結果
DateTimeKind | 値 | 内容 |
---|---|---|
Unspecified | 0 | 未定義 |
Utc | 1 | 協定世界時 (UTC) |
Local | 2 | ローカル時刻 (日本は UTC に対して +09:00) |
DateTimeKind の変換
// コンストラクタで DateTimeKind を指定する
var dateTime = new DateTime(2024, 1, 2, 3, 4, 5, DateTimeKind.Utc);
// Kind のみを変換する (Ticks 日時はそのまま)
DateTime.SpecifyKind(dateTime, DateTimeKind.Utc);
// Kind を変換する (Ticks 日時も変わる)
dateTime.ToUniversalTime(); // Unspecified, Local は Utc に変換する
dateTime.ToLocalTime(); // Unspecified, Utc は Local に変換する
// Kind を変換する (Unspecified の場合、Ticks 日時はそのまま)
GetUtcDateTime(dateTime); // Local は Utc に変換する
GetLocalDateTime(dateTime); // Utc は Local に変換する
public static DateTime GetUtcDateTime(DateTime dateTime)
=> dateTime.Kind == DateTimeKind.Unspecified
? DateTime.SpecifyKind(dateTime, DateTimeKind.Utc)
: dateTime.ToUniversalTime();
public static DateTime GetLocalDateTime(DateTime dateTime)
=> dateTime.Kind == DateTimeKind.Unspecified
? DateTime.SpecifyKind(dateTime, DateTimeKind.Local)
: dateTime.ToLocalTime();
DateTimeKind の扱い
- ❗
==
,Equlas()
,CompareTo()
などの比較はKind
を無視します。 - ❗
Unspecified
で取得されることが多く、Ticks
のみで扱うことを基本とします。 - ❗
Kind
を必要とする場合は、Unspecified
をUtc
かLocal
どちらの扱いにするか注意します。 - 💡
Utc
を前提とするプロパティ名には、Utc
と命名に含めると明確です。 - 💡
Utc
を前提とする処理には、事前にUtc
にしておくと明確です。 - 💡
Local
を前提とする処理には、事前にLocal
にしておくと明確です。
DateTimeKind を指定して DateTime を生成する
コンストラクタで DateTimeKind
を指定します。
new DateTime(2024, 1, 2, 3, 4, 5); // 未指定の場合は Unspecified
new DateTime(2024, 1, 2, 3, 4, 5, DateTimeKind.Utc);
new DateTime(2024, 1, 2, 3, 4, 5, DateTimeKind.Local);
日本環境で確認してみます。
using System;
using System.Globalization;
Dump(new DateTime(2024, 1, 2, 3, 4, 5)); // 未指定の場合は Unspecified
Dump(new DateTime(2024, 1, 2, 3, 4, 5, DateTimeKind.Utc));
Dump(new DateTime(2024, 1, 2, 3, 4, 5, DateTimeKind.Local));
void Dump(DateTime value)
{
Console.WriteLine(value.Kind);
Console.WriteLine(value.ToString("yyyy-MM-dd HH:mm:ss.fffffff", CultureInfo.InvariantCulture));
Console.WriteLine(value.ToString("O"));
Console.WriteLine();
}
結果
Unspecified
2024-01-02 03:04:05.0000000
2024-01-02T03:04:05.0000000
Utc
2024-01-02 03:04:05.0000000
2024-01-02T03:04:05.0000000Z
Local
2024-01-02 03:04:05.0000000
2024-01-02T03:04:05.0000000+09:00
DateTime の比較は DateTimeKind を無視する
DateTime
の ==
, Equlas()
, CompareTo()
は Kind
を無視して日時のみで比較されます。
ドキュメントにも記載があります。
https://learn.microsoft.com/ja-jp/dotnet/api/system.datetime.equals
https://learn.microsoft.com/ja-jp/dotnet/api/system.datetime.compareto
var value1 = new DateTime(2024, 1, 2, 3, 4, 5, DateTimeKind.Utc);
var value2 = new DateTime(2024, 1, 2, 3, 4, 5, DateTimeKind.Local);
Console.WriteLine(value1 == value2); // True
Console.WriteLine(value1.CompareTo(value2)); // 0
参考に .NET 8.0.1 のソースコードも確認してみると、Ticks
のみを比較しています。
コード抜粋
public bool Equals(DateTime value)
{
return this == value;
}
public static bool operator ==(DateTime d1, DateTime d2) => ((d1._dateData ^ d2._dateData) << 2) == 0;
public int CompareTo(DateTime value)
{
return Compare(this, value);
}
public static int Compare(DateTime t1, DateTime t2)
{
long ticks1 = t1.Ticks;
long ticks2 = t2.Ticks;
if (ticks1 > ticks2) return 1;
if (ticks1 < ticks2) return -1;
return 0;
}
DateTimeKind を変換する
主な変換方法は、3 つあります。
項目 | 内容 | Kind 変換 | Ticks 変換 |
---|---|---|---|
ToUniversalTime() | Utc 以外は Utc に変換する | 変換する | 変換する |
ToLocalTime() | Local 以外は Local に変換する | 変換する | 変換する |
DateTime.SpecifyKind() | Kind のみを変更する | 変換する | 変換しない |
// 2024-01-02 09:00:00
var value = new DateTime(2024, 1, 2, 9, 0, 0, DateTimeKind.Unspecified);
// 2024-01-02 00:00:00 (-09:00 加算されて Utc に変換)
value.ToUniversalTime();
// 2024-01-02 18:00:00 (+09:00 加算されて Local に変換)
value.ToLocalTime();
// 2024-01-02 09:00:00 (日時そのままで Utc に変換)
DateTime.SpecifyKind(value, DateTimeKind.Utc);
メソッド | 変換前の Kind | 変換後の Kind | 変換前の Ticks | 変換後の Ticks | Ticks 加算分 |
---|---|---|---|---|---|
ToUniversalTime() | Unspecified | Utc | 2024-01-02 09:00:00 | 2024-01-02 00:00:00 | -09:00 |
ToUniversalTime() | Utc | Utc | 2024-01-02 09:00:00 | 2024-01-02 09:00:00 | 0 |
ToUniversalTime() | Local | Utc | 2024-01-02 09:00:00 | 2024-01-02 00:00:00 | -09:00 |
ToLocalTime() | Unspecified | Local | 2024-01-02 09:00:00 | 2024-01-02 18:00:00 | +09:00 |
ToLocalTime() | Utc | Local | 2024-01-02 09:00:00 | 2024-01-02 18:00:00 | +09:00 |
ToLocalTime() | Local | Local | 2024-01-02 09:00:00 | 2024-01-02 09:00:00 | 0 |
DateTimeKind を変換する (Unspecified の Ticks は変換しない)
ToUniversalTime()
, ToLocalTime()
では、Unspecified
の場合にも変換されてしまいます。Unspecified
の場合に日時はそのままで Kind
のみを変換するには DateTime.SpecifyKind()
を使用します。
public static DateTime GetUtcDateTime(DateTime dateTime)
=> dateTime.Kind == DateTimeKind.Unspecified
? DateTime.SpecifyKind(dateTime, DateTimeKind.Utc)
: dateTime.ToUniversalTime();
public static DateTime GetLocalDateTime(DateTime dateTime)
=> dateTime.Kind == DateTimeKind.Unspecified
? DateTime.SpecifyKind(dateTime, DateTimeKind.Local)
: dateTime.ToLocalTime();
例えば、DB 側の datetime 型が Kind
相当の TimeZone
や Offset
情報を持たないカラムのケースです。
- DB に INSERT する際は、
Kind
は無視されて日時のみが保存されます。 - DB から SELECT する際は、
Unspecified
で取得されます。
DB に DateTime.UtcNow (Utc)
で保存したら、その値を DB から取得した際に DateTimeKind.Utc
にしたい場合は、ToUniversalTime()
ではなく DateTime.SpecifyKind()
を使用して自分で変換する必要があります。
後述のファイルの日時をセットするメソッドも、Unspecified
の場合は変換されないようになっています。
DateTimeOffset への変換
Unspecified
は Local
と同じ扱いになります。
using System;
using System.Globalization;
Dump(new DateTime(2024, 1, 2, 9, 0, 0, DateTimeKind.Unspecified));
Dump(new DateTime(2024, 1, 2, 9, 0, 0, DateTimeKind.Utc));
Dump(new DateTime(2024, 1, 2, 9, 0, 0, DateTimeKind.Local));
void Dump(DateTime value)
{
DateTimeOffset dateTimeOffset = value;
Console.WriteLine($"{value.Kind} -> DateTimeOffset");
Console.WriteLine(dateTimeOffset.Offset);
Console.WriteLine(dateTimeOffset.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture));
Console.WriteLine(dateTimeOffset.ToString("u"));
Console.WriteLine();
}
結果
Unspecified -> DateTimeOffset
09:00:00
2024-01-02 09:00:00
2024-01-02 00:00:00Z
Utc -> DateTimeOffset
00:00:00
2024-01-02 09:00:00
2024-01-02 09:00:00Z
Local -> DateTimeOffset
09:00:00
2024-01-02 09:00:00
2024-01-02 00:00:00Z
変換前の Kind | 変換前の Ticks | 変換後の Ticks | 変換後の Offset |
---|---|---|---|
Unspecified | 2024-01-02 09:00:00 | 2024-01-02 09:00:00 | +09:00 |
Local | 2024-01-02 09:00:00 | 2024-01-02 09:00:00 | +09:00 |
Utc | 2024-01-02 09:00:00 | 2024-01-02 09:00:00 | 0 |
Utc と Local を意識する例
Utc
または Local
を返す代表的なメンバーは Now
です。
DateTime.UtcNow
(Utc)DateTime.Now
(Local)
ファイルの日時も、Utc
または Local
で取得するメソッドが用意されています。
File.GetLastWriteTimeUtc()
(Utc)File.GetLastWriteTime()
(Local)
また、ファイルの日時をセットする場合は、Utc
または Local
で設定するメソッドが用意されています。
File.SetLastWriteTimeUtc()
File.SetLastWriteTime()
Set するメソッドについては注意しておく必要があり、Unspecified
は Local
または Utc
と同じ扱いになります。
メソッド | 引数で指定した Kind | 引数で指定した Ticks | GetLastWriteTimeUtc() の結果 |
---|---|---|---|
SetLastWriteTimeUtc() | Unspecified | 2024-01-02 09:00:00 | 2024-01-02 09:00:00 |
SetLastWriteTimeUtc() | Utc | 2024-01-02 09:00:00 | 2024-01-02 09:00:00 |
SetLastWriteTimeUtc() | Local | 2024-01-02 09:00:00 | 2024-01-02 00:00:00 |
メソッド | 引数で指定した Kind | 引数で指定した Ticks | GetLastWriteTime() の結果 |
---|---|---|---|
SetLastWriteTime() | Unspecified | 2024-01-02 09:00:00 | 2024-01-02 09:00:00 |
SetLastWriteTime() | Utc | 2024-01-02 09:00:00 | 2024-01-02 18:00:00 |
SetLastWriteTime() | Local | 2024-01-02 09:00:00 | 2024-01-02 09:00:00 |
using System;
using System.Globalization;
Utc(DateTimeKind.Unspecified);
Utc(DateTimeKind.Utc);
Utc(DateTimeKind.Local);
Local(DateTimeKind.Unspecified);
Local(DateTimeKind.Utc);
Local(DateTimeKind.Local);
void Utc(DateTimeKind kind) => Set(kind, true);
void Local(DateTimeKind kind) => Set(kind, false);
void Set(DateTimeKind kind, bool useUtc)
{
const string path = @"C:\_DateTimeTest.txt";
var value = new DateTime(2024, 1, 2, 9, 0, 0, kind);
DateTime result;
var method = "";
if (useUtc)
{
method = nameof(File.SetLastWriteTimeUtc);
File.SetLastWriteTimeUtc(path, value);
result = File.GetLastWriteTimeUtc(path);
}
else
{
method = nameof(File.SetLastWriteTime);
File.SetLastWriteTime(path, value);
result = File.GetLastWriteTime(path);
}
Console.WriteLine($"{method} ({value.Kind})");
Console.WriteLine(result.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture));
Console.WriteLine();
}
結果
SetLastWriteTimeUtc (Unspecified)
2024-01-02 09:00:00
SetLastWriteTimeUtc (Utc)
2024-01-02 09:00:00
SetLastWriteTimeUtc (Local)
2024-01-02 00:00:00
SetLastWriteTime (Unspecified)
2024-01-02 09:00:00
SetLastWriteTime (Utc)
2024-01-02 18:00:00
SetLastWriteTime (Local)
2024-01-02 09:00:00
感謝
公式
- https://learn.microsoft.com/ja-jp/dotnet/api/system.datetimekind
- https://learn.microsoft.com/ja-jp/dotnet/api/system.datetime.specifykind
- https://learn.microsoft.com/ja-jp/dotnet/api/system.datetime.equals
- https://learn.microsoft.com/ja-jp/dotnet/api/system.datetime.compareto
- https://learn.microsoft.com/ja-jp/dotnet/api/system.datetimeoffset.-ctor
記事
関連記事
新着記事