围绕 utc 日期的问题 - TimeZoneInfo.ConvertTimeToUtc 导致日期更改

Posted

技术标签:

【中文标题】围绕 utc 日期的问题 - TimeZoneInfo.ConvertTimeToUtc 导致日期更改【英文标题】:Issue around utc date - TimeZoneInfo.ConvertTimeToUtc results in date change 【发布时间】:2016-06-06 21:08:12 【问题描述】:

如果用户选择的时区早于 x 小时数,则我希望保存的日期会从屏幕上选择的日期发生变化。 例如。他们从日历弹出窗口中选择 UTC+2 Athens 和日期25/02/2016,那么记录的日期将是24/02/2016。 我已将推理范围缩小到所选日期时间记录为例如 25/02/2016 00:00:00 并且具有 2 小时偏移量的事实,这将其带到 24/02/2016 22:00:00 以前从未使用过时区或 UTC 日期/时间,这非常令人困惑。

这是代码 -

     oObject.RefDate = itTimeAndDate.ParseDateAndTimeNoUTCMap(Request, TextBox_RefDate.Text);
        if (!string.IsNullOrEmpty(oObject.TimeZoneDetails))
        
TimeZoneInfo oTimeZone = TimeZoneInfo.FindSystemTimeZoneById(oObject.TimeZoneDetails);
            oObject.RefDate = itTimeAndDate.GetUTCUsingTimeZone(oTimeZone, oObject.RefDate);  
        

RefDate 将等同于 25/02/2016 00:00:00 一旦从 ParseDateAndTimeNoUTCMap 返回 *(下面的代码)*

static public itDateTime ParseDateAndTimeNoUTCMap(HttpRequest oTheRequest, string sValue)
        
            DateTime? oResult = ParseDateAndTimeNoUTCMapNull(oTheRequest, sValue);
            if (oResult != null)
                return new itDateTime(oResult.Value);
            return null;
        

        /// <summary>
        /// Translate a string that has been entered by a user to a UTC date / time - mapping using the
        /// current time zone
        /// </summary>
        /// <param name="oTheRequest">Request context</param>
        /// <param name="sValue">Date / time string entered by a user</param>
        /// <returns>UTC date / time object</returns>
        static public DateTime? ParseDateAndTimeNoUTCMapNull(HttpRequest oTheRequest, string sValue)
        
            try
            
                if (string.IsNullOrEmpty(sValue))
                    return null;
                sValue = sValue.Trim();
                if (string.IsNullOrEmpty(sValue))
                    return null;

                if (oTheRequest != null)
                
                    const DateTimeStyles iStyles = DateTimeStyles.AllowInnerWhite | DateTimeStyles.AllowLeadingWhite | DateTimeStyles.AllowTrailingWhite;
                    // Create array of CultureInfo objects
                    CultureInfo[] aCultures = new CultureInfo[oTheRequest.UserLanguages.Length + 1];
                    for (int iCount = oTheRequest.UserLanguages.GetLowerBound(0); iCount <= oTheRequest.UserLanguages.GetUpperBound(0);
                         iCount++)
                    
                        string sLocale = oTheRequest.UserLanguages[iCount];
                        if (!string.IsNullOrEmpty(sLocale))
                        

                            // Remove quality specifier, if present.
                            if (sLocale.Contains(";"))
                                sLocale = sLocale.Substring(0, sLocale.IndexOf(';'));
                            try
                            
                                aCultures[iCount] = new CultureInfo(sLocale, false);
                            
                            catch (Exception)  
                        
                        else
                        
                            aCultures[iCount] = CultureInfo.CurrentCulture;
                        
                    
                    aCultures[oTheRequest.UserLanguages.Length] = CultureInfo.InvariantCulture;
                    // Parse input using each culture.
                    foreach (CultureInfo culture in aCultures)
                    
                        DateTime oInputDate;
                        if (DateTime.TryParse(sValue, culture.DateTimeFormat, iStyles, out oInputDate))
                            return oInputDate;
                    
                
                return DateTime.Parse(sValue);
            
            catch (Exception)
            
            
            return null;
        

一旦从上面返回,下面的行就会被执行-

TimeZoneInfo oTimeZone = TimeZoneInfo.FindSystemTimeZoneById(oObject.TimeZoneDetails);
        oObject.RefDate = itTimeAndDate.GetUTCUsingTimeZone(oTimeZone, oObject.RefDate);  

我似乎在GetUTCUsingTimeZone 内遇到了问题。

static public itDateTime GetUTCUsingTimeZone(TimeZoneInfo oTimeZone, itDateTime oDateTime)
    
        if (oDateTime == null || oTimeZone == null)
         return oDateTime;
         DateTime oLocal = DateTime.SpecifyKind(oDateTime.Value, DateTimeKind.Unspecified);
        DateTime oResult = TimeZoneInfo.ConvertTimeToUtc(oLocal, oTimeZone);

        return new itDateTime(oResult);
    

我检查了TimezoneInfo 的偏移值,oResult 始终等于oLocal param - 偏移量。所以 25/02/2016 00:00:00 有 3 小时的偏移量将等同于 24/02/2016 21:00:00 当偏移量为 -hours 时,它会直接进入另一个方向,所以 oResult = oLocal + the offset,如果这有意义的话。因此,在这些情况下不会发生日期更改的主要问题。

显然这不是我想要的。我希望日期是用户为其时区选择的日期。 有没有人见过这样的东西?任何可能的解决方案?

我不完全确定我做错了什么。

【问题讨论】:

itTimeAndDate 结构/类是什么样的? 不清楚。 Date 并没有真正的 TimeZone ... DateTime 值。您使用哪种 UI 组件,包含 TZ 的预期用途是什么? 如果您找到答案,您应该“回答您自己的问题”...(即自己添加答案) 三双三秒@rogerdpack 的评论 - 尽管很多人这样做(他们不应该),但将答案放入问题中是不好的做法,不鼓励。另一方面,鼓励自我回答,因此请将答案作为答案 通常,我发现“你应该使用 X”cmets 很烦人,但是在 C# 中编写了很多 DateTime 处理代码后,我发现 NodaTime 非常有用。可能值得一看。 -- nodatime.org 【参考方案1】:

如果您需要保持正确的时区,您应该使用DateTimeOffset 类型而不是DateTime 类型。

DateTimeOffset 维护与 UTC 的偏移量,因此您永远不会丢失时区信息,并且有很多有用的方法,例如 UtcDateTime

来自马口:

https://msdn.microsoft.com/en-us/library/system.datetimeoffset(v=vs.110).aspx

https://docs.microsoft.com/en-us/dotnet/standard/datetime/choosing-between-datetime

【讨论】:

【参考方案2】:

修复方法是在从数据库中获取值之后并重新显示它之前运行以下命令 -

static public itDateTime FixUTCUsingTimeZone(TimeZoneInfo oTimeZone, itDateTime oDateTime)

    if (oDateTime == null || oTimeZone == null)
        return oDateTime;

    DateTime oTime = DateTime.SpecifyKind(oDateTime.Value, DateTimeKind.Unspecified);
    DateTime oResult = TimeZoneInfo.ConvertTimeFromUtc(oTime, oTimeZone);

    return new itDateTime(oResult);

所以本质上只是执行之前执行的 ConvertTimeToUtc 的相反操作。不知道为什么最初没有这样做,但你去吧。

【讨论】:

以上是关于围绕 utc 日期的问题 - TimeZoneInfo.ConvertTimeToUtc 导致日期更改的主要内容,如果未能解决你的问题,请参考以下文章

GMT 与 UTC 日期

在 PHP 中将 UTC 日期转换为本地时间

Javascript将日期转换为UTC [重复]

需要当前日期在具有日期格式的“UTC”时区[重复]

日期字符串到纪元秒 (UTC)

JavaScript:将 UTC 日期时间转换为传递时区的日期时间