如何在 .NET 中的 DST 时间获取 UTC 偏移量

Posted

技术标签:

【中文标题】如何在 .NET 中的 DST 时间获取 UTC 偏移量【英文标题】:How to get UTC Offset during DST time in .NET 【发布时间】:2012-10-30 23:16:28 【问题描述】:

我正在尝试在一个时间间隔内获取一个时区中的所有偏移量。下面是我用来完成此操作的函数,我知道您可以在标准时间使用TimeZoneInfo.BaseUtcOffset 获取时区的UTC offset,但是在夏令时没有类似的方法可以获取一个,除非您通过特定的DST 时间点到GetUTCOffset() 方法。

static void GetOffsets(DateTime startTime, DateTime endTime, TimeZoneInfo tz)

    var result = new HashSet<int>();
    var adjRules = tz.GetAdjustmentRules();
    result.Add(tz.BaseUtcOffset);

    foreach (var adjustmentRule in adjRules)
    
        if ((startTime >= adjustmentRule.DateStart && startTime <= adjustmentRule.DateEnd) || (endTime >= adjustmentRule.DateStart && endTime <= adjustmentRule.DateEnd) ||
             (stTime <= adjustmentRule.DateStart && endTime >= adjustmentRule.DateEnd))
        
            if(adjustmentRule.DaylightDelta != TimeSpan.Zero)
            
                if (!result.Contains(tz.BaseUtcOffset + adjustmentRule.DaylightDelta))
                      result.Add((tz.BaseUtcOffset + adjustmentRule.DaylightDelta));
            
         
     

     foreach (var res in result)
     
         Console.WriteLine(res);
     

如果有更好的方法,请告诉我。

【问题讨论】:

代码很神秘。如果您想要所有可能的偏移量,那么只需删除 startTime 和 endTime 上的测试。很难看到结果如何有用。 嗨 Hans,考虑到 Microsoft 有确定 DaylightDelta 的规则这一事实,我认为要使这种方法通用,我必须考虑一个间隔或使用 DateTime.Min 和 DateTime.Max 作为间隔获取所有过去的偏移量。 用例:我使用此偏移信息传递给 sql server db 存储过程,驱动报告的 UI 有一个下拉列表,其中列出了一天中的所有 24“1 hr: timeintervals (ex 1PM - 2PM),我将其转换为offSet,以传递给以UTC保存日期时间的存储过程。如果不是这个用例,我仍然想知道是否有其他方法可以解决我原来的问题。 注意检查两个时间段是否完全重叠,您可以使用p1.Start &lt; p2.End &amp;&amp; p2.Start &lt; p1.End。使用&lt;=来品尝。 非常有效的问题。 99% 的时间你会有两个偏移量:标准和夏令时。我也需要同样的东西。 【参考方案1】:

我试图在一个时区中看到所有偏移量,在一个时间间隔内。

强烈建议您避免尝试直接使用TimeZoneInfo。在某些年份,某些区域的调整规则可能出人意料地尴尬,正如我自己发现的那样。

虽然我有偏见,但我建议使用Noda Time,它可以包装TimeZoneInfo 并通过BclDateTimeZone.FromTimeZoneInfo 为您完成艰苦的工作。你的问题在要求方面并不完全清楚,但如果你能多说一点你想要做什么,我可以为你编写适当的 Noda Time 代码。

您的初始描述可以这样实现:

public IEnumerable<ZoneInterval> GetZoneIntervalsInInterval
    (this DateTimeZone zone, Interval interval)

    Instant current = interval.Start;
    while (current < interval.End && current != Instant.MaxValue)
    
        ZoneInterval zi = zone.GetZoneInterval(current);
        yield return zi;
        current = zi.End;
    

然后:

var zone = BclDateTimeZone.FromTimeZoneInfo(tzInfo);
var offsets = zone.GetZoneIntervalsInInterval(interval)
                  .Select(zi => zi.WallOffset)
                  .Distinct();

这是假设你和我所说的“偏移”意思相同(即UTC和本地时间之间的差异)。

【讨论】:

感谢您的帮助! @乔恩。不幸的是,我没有在项目的这个阶段添加第三方库的奢侈,而我们距离开发版本还有 3 周的时间,我现在必须依赖 .NET bcl 方法,就像我在问题中提到的那样,我的要求是使用“8am -9am”等时间过滤器过滤 sql server 中日期时间为 UTC 的历史数据。配置中提供了最初用于转换的时区,因此给定一个时区,我需要知道它看到的偏移量,以便我可以使用这些信息来过滤数据库中的历史数据。 我能够在“答案”中使用我的帖子进行此操作,至少它适用于当前感兴趣的区域(EST、CST、PST、GBT),您提到调整规则对于在某些年份的某些区域。你什么时候认为来自 Timezoneinfo 的信息很尴尬,当你认为它的时候你的包装器依赖什么?【参考方案2】:

我确实想出了这种方法来获取指定时区信息的一年间隔内的 UTC 间隔需要考虑的偏移量。我将此集合传递给数据库以过滤保存在 UTC 中的日期时间字段,以获取间隔过滤器输入,例如(“1am - 2am”),我确实对系统中的所有时区进行了测试,并且效果很好。尽管这不是我最初问题的答案,因为我仍在使用调整规则来获取偏移量,但这次我尝试使其可用。

class Program

    static void Main(string[] args)
    
        foreach (var tz in TimeZoneInfo.GetSystemTimeZones())
        
            var result = GetUTCOffsetsByUTCIntervals(1900, 2012, tz);
            Console.WriteLine(tz.DisplayName);
            foreach (var tuple in result)
            
                Console.WriteLine(tuple.Item1 + "     " + tuple.Item2 + "   " + tuple.Item3);
            
            Console.WriteLine("------------------------------------------------------------");
        
        Console.Read();
    

    public static List<Tuple<TimeSpan, DateTime, DateTime>> GetUTCOffsetsByUTCIntervals(int stYear, int endYear, TimeZoneInfo tz)
    
        var cal = CultureInfo.CurrentCulture.Calendar;
        var offsetsByUTCIntervals = new List<Tuple<TimeSpan, DateTime, DateTime>>();
        var adjRules = tz.GetAdjustmentRules();
        for (var year = stYear; year <= endYear && year < DateTime.MaxValue.Year && year >= DateTime.MinValue.Year; year++)
        
            var adjRule =
                adjRules.FirstOrDefault(
                    rule =>
                    rule.DateStart.Year == year || rule.DateEnd.Year == year ||
                    (rule.DateStart.Year < year && rule.DateEnd.Year > year));

            var yrStTime = new DateTime(year, 1, 1);
            var yrEndTime = yrStTime.AddYears(1).AddTicks(-1);

            if (adjRule != null)
            
                var tStDate = GetTransitionDate(adjRule.DaylightTransitionStart, year);
                var tEnddate = GetTransitionDate(adjRule.DaylightTransitionEnd, year);

                var stTsp = adjRule.DaylightTransitionStart.TimeOfDay.TimeOfDay;

                var endTsp = adjRule.DaylightTransitionEnd.TimeOfDay.TimeOfDay;

                if (yrStTime.Date == tStDate && yrStTime.TimeOfDay == stTsp)
                    yrStTime = yrStTime.Add(adjRule.DaylightDelta);

                if (yrEndTime.Date == tEnddate && yrEndTime.TimeOfDay == endTsp)
                    yrEndTime = yrEndTime.Subtract(adjRule.DaylightDelta);

              if (tStDate.Month > tEnddate.Month)
                
                    offsetsByUTCIntervals.Add(new Tuple<TimeSpan, DateTime, DateTime>(tz.BaseUtcOffset + adjRule.DaylightDelta, ConvertTimeToUtc(yrStTime, tz), ConvertTimeToUtc(tEnddate.AddTicks(endTsp.Ticks - 1), tz)));
                    offsetsByUTCIntervals.Add(new Tuple<TimeSpan, DateTime, DateTime>(tz.BaseUtcOffset, ConvertTimeToUtc(tEnddate.Add(endTsp.Subtract(adjRule.DaylightDelta)), tz), ConvertTimeToUtc(tStDate.AddTicks(stTsp.Ticks - 1), tz)));
                    offsetsByUTCIntervals.Add(new Tuple<TimeSpan, DateTime, DateTime>(tz.BaseUtcOffset + adjRule.DaylightDelta, ConvertTimeToUtc(tStDate.Add(stTsp.Add(adjRule.DaylightDelta)), tz), ConvertTimeToUtc(yrEndTime, tz))); 
                
                else
                
                    offsetsByUTCIntervals.Add(new Tuple<TimeSpan, DateTime, DateTime>(tz.BaseUtcOffset, ConvertTimeToUtc(yrStTime, tz), ConvertTimeToUtc(tStDate.AddTicks(stTsp.Ticks - 1), tz)));
                    offsetsByUTCIntervals.Add(new Tuple<TimeSpan, DateTime, DateTime>(tz.BaseUtcOffset + adjRule.DaylightDelta, ConvertTimeToUtc(tStDate.Add(stTsp.Add(adjRule.DaylightDelta)), tz), ConvertTimeToUtc(tEnddate.AddTicks(endTsp.Ticks - 1), tz)));
                    offsetsByUTCIntervals.Add(new Tuple<TimeSpan, DateTime, DateTime>(tz.BaseUtcOffset, ConvertTimeToUtc(tEnddate.Add(endTsp.Subtract(adjRule.DaylightDelta)), tz), ConvertTimeToUtc(yrEndTime, tz))); 
                
            
            else
            
                offsetsByUTCIntervals.Add(new Tuple<TimeSpan, DateTime, DateTime>(tz.BaseUtcOffset, ConvertTimeToUtc(yrStTime, tz), ConvertTimeToUtc(yrEndTime, tz)));
            
        
        return offsetsByUTCIntervals;
    

    public static DateTime ConvertTimeToUtc(DateTime date, TimeZoneInfo timeZone)
    
        if (date == null || timeZone == null)
        
            return date;
        
        DateTime convertedDate = TimeZoneInfo.ConvertTimeToUtc(date, timeZone);
        return convertedDate;
    

    //copy from msdn http://msdn.microsoft.com/en-us/library/system.timezoneinfo.transitiontime.isfixeddaterule.aspx
    private static DateTime GetTransitionDate(TimeZoneInfo.TransitionTime transition, int year)
    
        if (transition.IsFixedDateRule)
            return new DateTime(year, transition.Month, transition.Day);

        int transitionDay;
        var cal = CultureInfo.CurrentCulture.Calendar;
        var startOfWeek = transition.Week * 7 - 6;
        var firstDayOfWeek = (int)cal.GetDayOfWeek(new DateTime(year, transition.Month, 1));
        var changeDayOfWeek = (int)transition.DayOfWeek;

        if (firstDayOfWeek <= changeDayOfWeek)
            transitionDay = startOfWeek + (changeDayOfWeek - firstDayOfWeek);
        else
            transitionDay = startOfWeek + (7 - firstDayOfWeek + changeDayOfWeek);

        if (transitionDay > cal.GetDaysInMonth(year, transition.Month))
            transitionDay -= 7;
        return new DateTime(year, transition.Month, transitionDay);
    

   /* static void GetOffsets(DateTime startTime, DateTime endTime, TimeZoneInfo tz)
    
        var result = new HashSet<string>();
        var adjRules = tz.GetAdjustmentRules();
        result.Add(tz.BaseUtcOffset.ToString());

        foreach (var adjustmentRule in adjRules)
        
            if ((startTime >= adjustmentRule.DateStart && startTime <= adjustmentRule.DateEnd)
                    || (endTime >= adjustmentRule.DateStart && endTime <= adjustmentRule.DateEnd)
                 || (startTime <= adjustmentRule.DateStart && endTime >= adjustmentRule.DateEnd))
            
                if(adjustmentRule.DaylightDelta != TimeSpan.Zero)
                
                    if (!result.Contains((tz.BaseUtcOffset + adjustmentRule.DaylightDelta).ToString()))
                      result.Add((tz.BaseUtcOffset + adjustmentRule.DaylightDelta).ToString());
                
            
        
        Console.Write(tz.DisplayName + "   ");
        foreach (var res in result)
        
            Console.Write(res);
        
    */

【讨论】:

【参考方案3】:

如果您对日期做任何复杂的事情,您应该查看Noda Time(它是 Joda Time Java 库的一个端口)。它有一个完整的命名空间来处理棘手的时区问题。

【讨论】:

以上是关于如何在 .NET 中的 DST 时间获取 UTC 偏移量的主要内容,如果未能解决你的问题,请参考以下文章

Python 中给定 TimeZone 的标准 UTC 偏移量(无 DST)

在 Python 中获取计算机的 UTC 偏移量

Python:如何在不知道 DST 是不是生效的情况下将时区感知时间戳转换为 UTC

Python - 从 DST 调整的本地时间到 UTC

检测 C++ 中的 DST 更改

在没有 DST 的情况下将时间保存为 UTC(时刻)