一勞永逸,解決.NET發布雲服務器的時區問題


國內大多數開發者使用的電腦,都是使用的北京時間,日常開發的過程中其實並沒有什么不便;不過,等遇到了阿里雲等雲服務器,系統默認使用的時間大多為UTC時間,這個時候,時區和時間的問題,就是不容忽視的大問題。

概念

首先明確一點,對於一個時刻,不管你用UTC時間還是UTC+8的時間來表示,本質上是一個時刻,就是一樣的。我們處理日期和時間的目標,也是為了保證這個時刻不會因為時區的不同出現對不上的情況。

DateTime與DateTimeOffset

.NET中表示時刻的數據類型有這兩個(新出的Date和Time不作討論),關於這兩個數據類型,已經有同學寫的很清楚了,阿里雲很多服務器使用的時間為UTC時間,這個時候,如果使用DateTime,是很難說清楚時區(Kind只有UTC、Local還有未指定,不支持特定的某個時區),因此我們應當優先使用DateTimeOffset。

TimeZoneInfo

用於跨時區的情況下,時區的信息是很重要的,.NET中使用TimeZoneInfo這個類表示時區的信息。該類提供了一些靜態方法,可以用於查找時區和創建時區等等。最早我是傾向於使用這些方法找到東八區的信息的,但是我發現諸如ConvertTimeBySystemTimeZoneIdFindSystemTimeZoneById的方法,都依賴於系統中的定義,不同的系統可能還不一樣,自己定義是比較保險的,於是,我使用了CreateCustomTimeZone來新建一個時區。

Unix時間戳是比較於1970年的UTC標准時間,因此在處理的過程中,DateTime的時間表示應當將它轉換為UTC時間,以下的代碼,是使用TimeZoneInfo實現時間轉換的,使用的是DateTime數據類型。如果改用DateTimeOffset,這個類型對轉換為Unix時間戳更加友好。

internal static class DateTimeExtension
{
    private static readonly TimeZoneInfo gmt8 = TimeZoneInfo.CreateCustomTimeZone("GMT+8", TimeSpan.FromHours(8), "China Standard Time", "(UTC+8)China Standard Time");
    
    public static long ToUnixTime(this DateTime datetime)
    {
        DateTime dateTimeUtc = datetime;
        if (datetime.Kind != DateTimeKind.Utc)
        {
            dateTimeUtc = datetime.ToUniversalTime();
        }

        if (dateTimeUtc.ToUniversalTime() <= DateTime.UnixEpoch)
        {
            return 0;
        }

        return (long)(dateTimeUtc - DateTime.UnixEpoch).TotalMilliseconds;
    }

    public static DateTime ToDateTime(this long unixTimestamp)
    {
        DateTime time = DateTime.UnixEpoch.AddMilliseconds(unixTimestamp);
        return TimeZoneInfo.ConvertTimeFromUtc(time, gmt8);
    }

    public static DateTime ToDateTime(this long unixTimestamp, int timezone)
    {
        DateTime time = DateTime.UnixEpoch.AddMilliseconds(unixTimestamp);
        return time.AddHours(timezone);
    }
}

其實,只要時區是正確的,那么可以也可以使用網友提供的方法進行轉換。

// Code from https://stackoverflow.com/questions/5615538/parse-a-date-string-into-a-certain-timezone-supporting-daylight-saving-time
public DateTimeOffset ParseDateExactForTimeZone(string dateTime, TimeZoneInfo timezone)
{
    var parsedDateLocal = DateTimeOffset.ParseExact(dateTime, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
    var tzOffset = timezone.GetUtcOffset(parsedDateLocal.DateTime);
    var parsedDateTimeZone = new DateTimeOffset(parsedDateLocal.DateTime, tzOffset);
    return parsedDateTimeZone;
}

實踐指南

處理日期與時間的過程中,如果加入了TimeZoneInfo的情況下會使得程序變得非常麻煩,特別是各種TimeZone的Id和名稱,不同系統也不統一的情況下,容易出現各種各樣的問題。我想的就是避免用它,說說我的處理原則吧。

  1. 日期時間不使用DateTime類,全部使用DateTimeOffset類型
  2. 系統的內部處理,全部使用UTC標准時間進行數據表示
  3. 對於字符串的轉換為DataTimeOffset的情況,顯式指定時區的小時偏移量
  4. 直接使用時間的加減,避免使用時區的信息轉換導致的代碼復雜度增加
  5. 【可選】如果不用考慮2038年的情況下,可以考慮Unix時間戳簡化時間表示

直接貼上我現在使用的代碼段,思路就是在強制給字符串表示的時間,加上UTC標准時區信息,然后再修正時差。

public static class DateTimeExtension
{
    public static long? ParseUnixTimeMillisecondsWithTimeZone(string datetimeString, string format = "yyyyMMddHHmmss", int timezoneOffset = 8)
    {
    	//注意這里非常關鍵的參數DateTimeStyles.AssumeUniversal,就是設定數據都是UTC的,不管是不是,都強行指定為UTC,然后再按照時區的信息調整為正確的時間。
    	//給定的數據是東八區時間,但是加上這個參數,實際上的時間就會提前了8個小時,因此需要在后面的數據中直接減去8個小時,如果是其他地區的時間,那么也是一樣操作。
        if (!DateTimeOffset.TryParseExact(datetimeString, format, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out DateTimeOffset time)) return null;
        DateTimeOffset dateTimeUtcOffset = time.AddHours(-timezoneOffset);
        return dateTimeUtcOffset.ToUnixTimeMilliseconds();
    }

    public static DateTimeOffset ToDateTime(this long unixTimestamp) => DateTimeOffset.FromUnixTimeMilliseconds(unixTimestamp);
}

對於ASP.NET CORE,JSON.NET會自動處理符合ISO8601規范的日期格式,只要指定數據類型為DateTimeOffset,就能夠准確轉換了。

參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM