如何在.NET中获取夏令时期间的UTC偏移量

7

我试图获取一个时区在一个时间段内的所有偏移量。下面是我用来实现这一点的函数,我知道你可以使用TimeZoneInfo.BaseUtcOffset来获取时区在标准时间的UTC偏移量,但是除非您将特定的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 Passant
嗨,汉斯,考虑到微软有规则来确定DaylightDelta,我假设为了使此方法通用,我必须考虑一个间隔或使用DateTime.Min和DateTime.Max作为间隔来获取所有过去看到的偏移量。 - vksp
使用情况:我使用此偏移信息传递给 SQL Server 数据库存储过程,驱动报告的 UI 具有下拉列表,列出一天中所有 24 个“1 小时:时间间隔(例如下午 1 点至下午 2 点),我将其转换为偏移量以传递给保存在 UTC 中的 dateTime 的存储过程。如果不是为了这种用例,我仍在尝试了解是否有其他方法来解决我的原始问题。 - vksp
要检查两个时间段是否有重叠,可以使用 p1.Start < p2.End && p2.Start < p1.End。使用 <= 进行测试。 - Rawling
非常有价值的问题。99%的时间你会有两个偏移量:标准和夏令时。我也需要同样的东西。 - MikeJansen
3个回答

6
我正在尝试获取一个时区中在某个时间间隔内出现的所有偏移量。强烈建议您避免直接使用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();

假设您所说的“offset”与我所指的相同(即UTC和本地时间之间的差异)。

谢谢帮忙!@Jon。不幸的是,在我们距离开发发布还有3周的阶段,我没有添加第三方库的余地,现在我必须仅依赖于.NET BCL方法,就像我在问题中提到的那样,我的要求是使用类似“8am-9am”的时间过滤器在UTC日期时间在SQL Server中筛选历史数据。最初用于转换的时区信息在配置中提供,因此,给定一个时区,我需要知道它所看到的偏移量,以便我可以使用这些信息来筛选数据库中的历史数据。 - vksp
我能够在“答案”中使用我的帖子让它工作,至少对于当前感兴趣的时区(EST、CST、PST、GBT)是工作的。你提到了一些时区在某些年份上的调整规则有些尴尬。你认为从Timezoneinfo获取信息什么时候会感到尴尬?当你认为它是什么依赖于你的包装器? - vksp

0

我想出了一种方法,可以获取指定时区信息下一年时间间隔内需要考虑的偏移量。我将这个集合传递给数据库,以过滤保存在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);
        }
    }*/
}

-1
如果你要处理任何复杂的日期问题,建议你看看Noda Time(这是Joda Time Java库的一个版本)。它有一个专门用于解决棘手的时区问题的命名空间。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接