在各時區間轉換時間

對於使用日期和時間來處理時區差異的任何應用程式,這一點會變得越來越重要。 應用程式無法再假設所有時間都是以當地時間表示,這是可從 DateTime 結構取得的時間。 例如,顯示美國東部目前時間的網頁對東亞地區的客戶不具公信力。 本文說明如何將時間從某個時區轉換為另一個時區,以及轉換具有有限時區感知的 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

如果 DateTime 物件的 Kind 屬性與時區不相符,則 TimeZoneInfo.ConvertTimeToUtc 方法會擲回 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 屬性設為 UnspecifiedUtcDateTime 值。

  • 要將 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 方法,將任何指定時區中的時間轉換為當地時間。 下一節將討論這個技術。

在兩個時區之間轉換

您可以使用 TimeZoneInfo 類別的下列兩個 static (在 Visual Basic 中為 Shared) 方法之一,在任兩個時區之間轉換:

  • 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 的兩個不同位移來轉換日期和時間,而不是根據特定時區的時間。 若要執行這項轉換,您可以呼叫目前執行個體的 ToOffset 方法。 方法的單一參數是方法將傳回之新日期和時間值的位移。

例如,如果網頁使用者要求的日期和時間已知且序列化為字串 (格式為 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

如果該方法傳遞字串 "9/1/2007 5:32:07 -05:00" (表示比 UTC 早 5 小時的時區中的日期和時間,那麼對於位於美國的伺服器,它會傳回 "9/1/2007 3:32:07 AM -07:00"太平洋標準時區。

TimeZoneInfo 類別也包含 TimeZoneInfo.ConvertTime(DateTimeOffset, TimeZoneInfo) 方法的多載,這個方法會執行具有 ToOffset(TimeSpan) 值的時區轉換。 方法的參數是 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

另請參閱