如何在 .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 < p2.End && p2.Start < p1.End
。使用<=
来品尝。
非常有效的问题。 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)