タイム ゾーン間での時刻の変換

日時を使用するすべてのアプリケーションで、タイム ゾーン間の違いを処理することの重要性が高まっています。 アプリケーションで、すべての時刻を DateTime 構造体から入手できる現地時刻で表現できると想定することはできなくなりました。 たとえば、米国東部の現在の時刻を表示する Web ページは、東アジアの顧客に対する信頼性に欠けます。 この記事では、時刻をあるタイム ゾーンから別のタイム ゾーンに変換し、タイム ゾーン対応に制限のある DateTimeOffset 値を変換する方法について説明します。

世界協定時刻への変換

世界協定時刻 (UTC) は、高精度の原子時標準です。 世界のタイム ゾーンは、UTC からの正または負のオフセットとして表現されます。 したがって、UTC ではタイム ゾーンの影響を受けない、またはタイム ゾーンに依存しない時刻が提供されます。 コンピューター間の日時の移植性が重要となる場合には、UTC の使用が推奨されます。 日時の使用に関する詳細およびその他のベスト プラクティスについては、.NET Framework での DateTime を使用したコーディングのベスト プラクティスに関するページをご覧ください。 個別のタイム ゾーンを UTC に変換すると、時間の比較が容易になります。

注意

また、ある時点を明確に表すように DateTimeOffset 構造体をシリアル化することもできます。 DateTimeOffset オブジェクトには日時値が UTC からのオフセットと共に格納されるので、UTC に関連する特定の時点が常に表されます。

時刻を UTC に変換する最も簡単な方法としては、static (Visual Basic では Shared) TimeZoneInfo.ConvertTimeToUtc(DateTime) メソッドを呼び出します。 次の表に示すように、このメソッドによって実行される実際の変換は、dateTime パラメーターの Kind プロパティの値によって異なります。

DateTime.Kind 変換
DateTimeKind.Local 現地時刻を UTC に変換します。
DateTimeKind.Unspecified dateTime パラメーターが現地時刻であることを前提とし、現地時刻を UTC に変換します。
DateTimeKind.Utc dateTime パラメーターを変更せずに返します。

次のコードでは、現在の現地時刻を UTC に変換し、コンソールに結果を表示します。

DateTime dateNow = DateTime.Now;
Console.WriteLine($"The date and time are {TimeZoneInfo.ConvertTimeToUtc(dateNow)} UTC.");
Dim dateNow As Date = Date.Now
Console.WriteLine("The date and time are {0} UTC.", _
                  TimeZoneInfo.ConvertTimeToUtc(dateNow))

日時値が現地時刻と UTC のどちらも表していない場合、ToUniversalTime メソッドから不正な結果が返される可能性があります。 それに対して、TimeZoneInfo.ConvertTimeToUtc メソッドを使用すると、指定したタイム ゾーンから日時を変換することができます 変換後のタイム ゾーンを表す TimeZoneInfo オブジェクトを取得する方法の詳細については、「ローカル システムで定義されているタイム ゾーンの検索」を参照してください。 次のコードでは、TimeZoneInfo.ConvertTimeToUtc メソッドを使用して東部標準時を UTC に変換します。

DateTime easternTime = new DateTime(2007, 01, 02, 12, 16, 00);
string easternZoneId = "Eastern Standard Time";
try
{
    TimeZoneInfo easternZone = TimeZoneInfo.FindSystemTimeZoneById(easternZoneId);
    Console.WriteLine($"The date and time are {TimeZoneInfo.ConvertTimeToUtc(easternTime, easternZone)} UTC.");
}
catch (TimeZoneNotFoundException)
{
    Console.WriteLine($"Unable to find the {easternZoneId} zone in the registry.");
}
catch (InvalidTimeZoneException)
{
    Console.WriteLine($"Registry data on the {easternZoneId} zone has been corrupted.");
}
Dim easternTime As New Date(2007, 01, 02, 12, 16, 00)
Dim easternZoneId As String = "Eastern Standard Time"
Try
    Dim easternZone As TimeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(easternZoneId)
    Console.WriteLine("The date and time are {0} UTC.", _
                      TimeZoneInfo.ConvertTimeToUtc(easternTime, easternZone))
Catch e As TimeZoneNotFoundException
    Console.WriteLine("Unable to find the {0} zone in the registry.", _
                      easternZoneId)
Catch e As InvalidTimeZoneException
    Console.WriteLine("Registry data on the {0} zone has been corrupted.", _
                      easternZoneId)
End Try

TimeZoneInfo.ConvertTimeToUtc メソッドでは、DateTime オブジェクトの Kind プロパティとタイム ゾーンが一致しない場合に、ArgumentException がスローされます。 Kind プロパティは DateTimeKind.Local であるが、TimeZoneInfo オブジェクトがローカル タイム ゾーンを表していない場合、または Kind プロパティは DateTimeKind.Utc であるが、TimeZoneInfo オブジェクトが TimeZoneInfo.Utc と等しくない場合、不一致が発生します。

これらのメソッドはすべて、パラメーターとして DateTime 値を受け取り、DateTime 値が返されます。 DateTimeOffset 値については、DateTimeOffset 構造体に ToUniversalTime インスタンス メソッドが含まれ、これにより、現在のインスタンスの日時が UTC に変換されます。 次の例では、ToUniversalTime メソッドを呼び出して、現地時刻およびその他のいくつかの時刻を UTC に変換します。

DateTimeOffset localTime, otherTime, universalTime;

// Define local time in local time zone
localTime = new DateTimeOffset(new DateTime(2007, 6, 15, 12, 0, 0));
Console.WriteLine("Local time: {0}", localTime);
Console.WriteLine();

// Convert local time to offset 0 and assign to otherTime
otherTime = localTime.ToOffset(TimeSpan.Zero);
Console.WriteLine("Other time: {0}", otherTime);
Console.WriteLine("{0} = {1}: {2}",
                  localTime, otherTime,
                  localTime.Equals(otherTime));
Console.WriteLine("{0} exactly equals {1}: {2}",
                  localTime, otherTime,
                  localTime.EqualsExact(otherTime));
Console.WriteLine();

// Convert other time to UTC
universalTime = localTime.ToUniversalTime();
Console.WriteLine("Universal time: {0}", universalTime);
Console.WriteLine("{0} = {1}: {2}",
                  otherTime, universalTime,
                  universalTime.Equals(otherTime));
Console.WriteLine("{0} exactly equals {1}: {2}",
                  otherTime, universalTime,
                  universalTime.EqualsExact(otherTime));
Console.WriteLine();
// The example produces the following output to the console:
//    Local time: 6/15/2007 12:00:00 PM -07:00
//
//    Other time: 6/15/2007 7:00:00 PM +00:00
//    6/15/2007 12:00:00 PM -07:00 = 6/15/2007 7:00:00 PM +00:00: True
//    6/15/2007 12:00:00 PM -07:00 exactly equals 6/15/2007 7:00:00 PM +00:00: False
//
//    Universal time: 6/15/2007 7:00:00 PM +00:00
//    6/15/2007 7:00:00 PM +00:00 = 6/15/2007 7:00:00 PM +00:00: True
//    6/15/2007 7:00:00 PM +00:00 exactly equals 6/15/2007 7:00:00 PM +00:00: True
Dim localTime, otherTime, universalTime As DateTimeOffset

' Define local time in local time zone
localTime = New DateTimeOffset(#6/15/2007 12:00:00PM#)
Console.WriteLine("Local time: {0}", localTime)
Console.WriteLine()

' Convert local time to offset 0 and assign to otherTime
otherTime = localTime.ToOffset(TimeSpan.Zero)
Console.WriteLine("Other time: {0}", otherTime)
Console.WriteLine("{0} = {1}: {2}", _
                  localTime, otherTime, _
                  localTime.Equals(otherTime))
Console.WriteLine("{0} exactly equals {1}: {2}", _
                  localTime, otherTime, _
                  localTime.EqualsExact(otherTime))
Console.WriteLine()

' Convert other time to UTC
universalTime = localTime.ToUniversalTime()
Console.WriteLine("Universal time: {0}", universalTime)
Console.WriteLine("{0} = {1}: {2}", _
                  otherTime, universalTime, _
                  universalTime.Equals(otherTime))
Console.WriteLine("{0} exactly equals {1}: {2}", _
                  otherTime, universalTime, _
                  universalTime.EqualsExact(otherTime))
Console.WriteLine()
' The example produces the following output to the console:
'    Local time: 6/15/2007 12:00:00 PM -07:00
'    
'    Other time: 6/15/2007 7:00:00 PM +00:00
'    6/15/2007 12:00:00 PM -07:00 = 6/15/2007 7:00:00 PM +00:00: True
'    6/15/2007 12:00:00 PM -07:00 exactly equals 6/15/2007 7:00:00 PM +00:00: False
'    
'    Universal time: 6/15/2007 7:00:00 PM +00:00
'    6/15/2007 7:00:00 PM +00:00 = 6/15/2007 7:00:00 PM +00:00: True
'    6/15/2007 7:00:00 PM +00:00 exactly equals 6/15/2007 7:00:00 PM +00:00: True   

UTC から指定したタイム ゾーンへの変換

UTC を現地時刻に変換するには、後の「UTC から現地時刻への変換」セクションを参照してください。 UTC を、指定した任意のタイム ゾーンの時刻に変換するには、ConvertTimeFromUtc メソッドを呼び出します。 メソッドには、

  • 変換対象の UTC。 これは、Kind プロパティが Unspecified または Utc に設定されている DateTime 値である必要があります。

  • UTC の変換先のタイム ゾーン。

次のコードでは、UTC を中部標準時に変換します。

DateTime timeUtc = DateTime.UtcNow;
try
{
    TimeZoneInfo cstZone = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time");
    DateTime cstTime = TimeZoneInfo.ConvertTimeFromUtc(timeUtc, cstZone);
    Console.WriteLine("The date and time are {0} {1}.",
                      cstTime,
                      cstZone.IsDaylightSavingTime(cstTime) ?
                              cstZone.DaylightName : cstZone.StandardName);
}
catch (TimeZoneNotFoundException)
{
    Console.WriteLine("The registry does not define the Central Standard Time zone.");
}
catch (InvalidTimeZoneException)
{
    Console.WriteLine("Registry data on the Central Standard Time zone has been corrupted.");
}
Dim timeUtc As Date = Date.UtcNow
Try
    Dim cstZone As TimeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time")
    Dim cstTime As Date = TimeZoneInfo.ConvertTimeFromUtc(timeUtc, cstZone)
    Console.WriteLine("The date and time are {0} {1}.", _
                      cstTime, _
                      IIf(cstZone.IsDaylightSavingTime(cstTime), _
                          cstZone.DaylightName, cstZone.StandardName))
Catch e As TimeZoneNotFoundException
    Console.WriteLine("The registry does not define the Central Standard Time zone.")
Catch e As InvalidTimeZoneException
    Console.WriteLine("Registry data on the Central Standard Time zone has been corrupted.")
End Try

UTC から現地時刻への変換

UTC を現地時刻に変換するには、時刻を変換する DateTime オブジェクトの ToLocalTime メソッドを呼び出します。 次の表に示すように、このメソッドの実際の動作は、オブジェクトの Kind プロパティの値によって異なります。

DateTime.Kind 変換
DateTimeKind.Local DateTime 値を変更せずに返します。
DateTimeKind.Unspecified DateTime 値が UTC であると仮定して、その UTC を現地時刻に変換します。
DateTimeKind.Utc DateTime 値を現地時刻に変換します。

注意

TimeZone.ToLocalTime メソッドの動作は、DateTime.ToLocalTime メソッドと同じです。 変換前の日時値である単一パラメーターを受け取ります。

static (Visual Basic では Shared) TimeZoneInfo.ConvertTime メソッドを使用して、指定したタイム ゾーンの時刻を現地時刻に変換することもできます。 この手法については、次のセクションで説明します。

任意の 2 つのタイム ゾーン間での変換

TimeZoneInfo クラスの次の 2 つの static (Visual Basic では Shared) メソッドのいずれかを使用して、任意の 2 つのタイム ゾーン間で変換できます。

  • ConvertTime

    このメソッドのパラメーターは、変換前の日時値、日時値のタイム ゾーンを表す TimeZoneInfo オブジェクト、および日時値の変換後のタイム ゾーンを表す TimeZoneInfo オブジェクトです。

  • ConvertTimeBySystemTimeZoneId

    このメソッドのパラメーターは、変換前の日時値、日時値のタイム ゾーンの識別子、および日時値の変換後のタイム ゾーンの識別子です。

どちらのメソッドも、変換前の日時値の Kind プロパティと、そのタイム ゾーンを表す TimeZoneInfo オブジェクトまたはタイム ゾーン識別子が、相互に対応している必要があります。 それ以外の場合は、ArgumentException がスローされます。 たとえば、日時値の Kind プロパティが DateTimeKind.Local である場合、このメソッドにパラメーターとして渡される TimeZoneInfo オブジェクトが TimeZoneInfo.Local と等しくない場合に例外がスローされます。 また、このメソッドにパラメーターとして渡される識別子が TimeZoneInfo.Local.Id と等しくない場合は例外がスローされます。

次の例では ConvertTime メソッドを使用して、ハワイ標準時を現地時刻に変換します。

DateTime hwTime = new DateTime(2007, 02, 01, 08, 00, 00);
try
{
    TimeZoneInfo hwZone = TimeZoneInfo.FindSystemTimeZoneById("Hawaiian Standard Time");
    Console.WriteLine("{0} {1} is {2} local time.",
            hwTime,
            hwZone.IsDaylightSavingTime(hwTime) ? hwZone.DaylightName : hwZone.StandardName,
            TimeZoneInfo.ConvertTime(hwTime, hwZone, TimeZoneInfo.Local));
}
catch (TimeZoneNotFoundException)
{
    Console.WriteLine("The registry does not define the Hawaiian Standard Time zone.");
}
catch (InvalidTimeZoneException)
{
    Console.WriteLine("Registry data on the Hawaiian Standard Time zone has been corrupted.");
}
Dim hwTime As Date = #2/01/2007 8:00:00 AM#
Try
    Dim hwZone As TimeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById("Hawaiian Standard Time")
    Console.WriteLine("{0} {1} is {2} local time.", _
                      hwTime, _
                      IIf(hwZone.IsDaylightSavingTime(hwTime), hwZone.DaylightName, hwZone.StandardName), _
                      TimeZoneInfo.ConvertTime(hwTime, hwZone, TimeZoneInfo.Local))
Catch e As TimeZoneNotFoundException
    Console.WriteLine("The registry does not define the Hawaiian Standard Time zone.")
Catch e As InvalidTimeZoneException
    Console.WriteLine("Registry data on the Hawaiian Standard Time zone has been corrupted.")
End Try

DateTimeOffset 値の変換

DateTimeOffset オブジェクトで表される日時値は、完全にはタイム ゾーン対応ではありません。これは、オブジェクトがインスタンス化された時点でそのタイム ゾーンとの関連付けが解除されるためです。 しかし、アプリケーションでは多くの場合、特定のタイム ゾーンの時刻ではなく、単に UTC からの 2 つの異なるオフセットに基づいて日時を変換する必要があります。 この変換を実行するには、現在のインスタンスの ToOffset メソッドを呼び出します。 このメソッドの単一のパラメーターは、メソッドから返される新しい日時値のオフセットです。

たとえば、Web ページに対するユーザー要求の日時が既知であり、MM/dd/yyyy hh:mm:ss zzzz の形式で文字列としてシリアル化される場合、次の ReturnTimeOnServer メソッドでは、この日時値を Web サーバー上の日時に変換します。

public DateTimeOffset ReturnTimeOnServer(string clientString)
{
   string format = @"M/d/yyyy H:m:s zzz";
   TimeSpan serverOffset = TimeZoneInfo.Local.GetUtcOffset(DateTimeOffset.Now);

   try
   {
      DateTimeOffset clientTime = DateTimeOffset.ParseExact(clientString, format, CultureInfo.InvariantCulture);
      DateTimeOffset serverTime = clientTime.ToOffset(serverOffset);
      return serverTime;
   }
   catch (FormatException)
   {
      return DateTimeOffset.MinValue;
   }
}
Public Function ReturnTimeOnServer(clientString As String) As DateTimeOffset
    Dim format As String = "M/d/yyyy H:m:s zzz"
    Dim serverOffset As TimeSpan = TimeZoneInfo.Local.GetUtcOffset(DateTimeOffset.Now)

    Try
        Dim clientTime As DateTimeOffset = DateTimeOffset.ParseExact(clientString, format, CultureInfo.InvariantCulture)
        Dim serverTime As DateTimeOffset = clientTime.ToOffset(serverOffset)
        Return serverTime
    Catch e As FormatException
        Return DateTimeOffset.MinValue
    End Try
End Function

このメソッドで、UTC よりも 5 時間早いタイム ゾーンの日時を表す文字列 "9/1/2007 5:32:07 -05:00" が渡された場合、米国の太平洋標準時ゾーンにあるサーバーに対して "9/1/2007 3:32:07 AM -07:00" が返されます。

TimeZoneInfo クラスには、ToOffset(TimeSpan) 値を使用してタイム ゾーン変換を実行する TimeZoneInfo.ConvertTime(DateTimeOffset, TimeZoneInfo) メソッドのオーバーロードも含まれます。 このメソッドのパラメーターは、DateTimeOffset 値と、時刻変換後のタイム ゾーンへの参照です。 このメソッドを呼び出すと、DateTimeOffset 値が返されます。 たとえば、前の例の ReturnTimeOnServer メソッドを次のように書き換えて、ConvertTime(DateTimeOffset, TimeZoneInfo) メソッドを呼び出すことができます。

public DateTimeOffset ReturnTimeOnServer(string clientString)
{
   string format = @"M/d/yyyy H:m:s zzz";

   try
   {
      DateTimeOffset clientTime = DateTimeOffset.ParseExact(clientString, format,
                                  CultureInfo.InvariantCulture);
      DateTimeOffset serverTime = TimeZoneInfo.ConvertTime(clientTime,
                                  TimeZoneInfo.Local);
      return serverTime;
   }
   catch (FormatException)
   {
      return DateTimeOffset.MinValue;
   }
}
Public Function ReturnTimeOnServer(clientString As String) As DateTimeOffset
    Dim format As String = "M/d/yyyy H:m:s zzz"

    Try
        Dim clientTime As DateTimeOffset = DateTimeOffset.ParseExact(clientString, format, CultureInfo.InvariantCulture)
        Dim serverTime As DateTimeOffset = TimeZoneInfo.ConvertTime(clientTime, TimeZoneInfo.Local)
        Return serverTime
    Catch e As FormatException
        Return DateTimeOffset.MinValue
    End Try
End Function

関連項目