查找两个时区之间的所有时区偏移期

时间:2015-07-13 19:42:13

标签: c# date timezone dst

我有代码可以在两个日期之间的所有时间段内为目标时区和源时区之间的秒数提供偏移量。

以下代码将执行此操作:

// example start and end date
DateTime startDate = DateTime.SpecifyKind(DateTime.Now.Date, DateTimeKind.Unspecified);
DateTime endDate = startDate.AddYears(10);

// the timezones to use
var sourceZone = TimeZoneInfo.FindSystemTimeZoneById("GMT Standard Time");
var destinationZone = TimeZoneInfo.FindSystemTimeZoneById("Mountain Standard Time");

// the periods and timezone offsets
var results = new List<Tuple<DateTime, DateTime, double>>();

// loop variables
DateTime currentDate = startDate;
DateTime periodStartDate = currentDate;

// the current period offset (in seconds)
double? currentOffset = null;

while (currentDate < endDate)
{
    DateTime destTime;

    // if the current time is invalid for the source timezone
    // then advance time until it is not invalid
    while (sourceZone.IsInvalidTime(currentDate))
        currentDate = currentDate.AddMinutes(30d);

    destTime = TimeZoneInfo.ConvertTime(currentDate, sourceZone, destinationZone);

    // calculate the offset for this iteration
    double iterationOffset = destTime.Subtract(currentDate).TotalSeconds;

    if (currentOffset == null)
        // the current offset is null so use the iteration offset
        currentOffset = iterationOffset;
    else if (iterationOffset != currentOffset.Value)
    {
        // the current offset doesn't equal the iteration offset
        // that means that a period has been identified

        // add the period to the list
        results.Add(Tuple.Create(periodStartDate, currentDate, currentOffset.Value));

        // the start of the period is the new current date
        periodStartDate = currentDate;

        // the current offset becomes the iteration offset
        currentOffset = iterationOffset;
    }

    // increment the day by 30 minutes
    currentDate = currentDate.AddMinutes(30d);
}

foreach (var item in results)
    Console.WriteLine("{0}\t{1}\t{2}", item.Item1, item.Item2, item.Item3);

结果:

╔═══════════════════════╦═══════════════════════╦════════╗
║      PeriodStart      ║       PeriodEnd       ║ Offset ║
╠═══════════════════════╬═══════════════════════╬════════╣
║ 7/13/2015 12:00:00 AM ║ 10/25/2015 1:00:00 AM ║ -25200 ║
║ 10/25/2015 1:00:00 AM ║ 11/1/2015 8:00:00 AM  ║ -21600 ║
║ 11/1/2015 8:00:00 AM  ║ 3/13/2016 9:00:00 AM  ║ -25200 ║
║ 3/13/2016 9:00:00 AM  ║ 3/27/2016 2:00:00 AM  ║ -21600 ║
║ 3/27/2016 2:00:00 AM  ║ 10/30/2016 1:00:00 AM ║ -25200 ║
║ 10/30/2016 1:00:00 AM ║ 11/6/2016 8:00:00 AM  ║ -21600 ║
║ 11/6/2016 8:00:00 AM  ║ 3/12/2017 9:00:00 AM  ║ -25200 ║
║ 3/12/2017 9:00:00 AM  ║ 3/26/2017 2:00:00 AM  ║ -21600 ║
║ 3/26/2017 2:00:00 AM  ║ 10/29/2017 1:00:00 AM ║ -25200 ║
║ 10/29/2017 1:00:00 AM ║ 11/5/2017 8:00:00 AM  ║ -21600 ║
║ 11/5/2017 8:00:00 AM  ║ 3/11/2018 9:00:00 AM  ║ -25200 ║
║          ...          ║          ...          ║   ...  ║
╚═══════════════════════╩═══════════════════════╩════════╝

现在虽然这似乎有效,但我知道这不是最有效的方法。我基本上坚持将此转换为一种方法,该方法不必使用硬编码时间增量来查找偏移发生变化的所有这些时段。

任何帮助都将不胜感激。

1 个答案:

答案 0 :(得分:1)

以下是使用Noda Time的解决方案:

using System;
using System.Linq;
using NodaTime;

...

// Get some time zones.  You can use Tzdb or Bcl zones here.
DateTimeZone sourceZone = DateTimeZoneProviders.Bcl["GMT Standard Time"]; // London
DateTimeZone destinationZone = DateTimeZoneProviders.Bcl["Mountain Standard Time"]; // Denver

// Determine the period of time we're interested in evaluating.
// I'm taking today in the source time zone, up to 10 years in the future.
Instant now = SystemClock.Instance.Now;
Instant start = sourceZone.AtStartOfDay(now.InZone(sourceZone).Date).ToInstant();
Instant end = start.InZone(sourceZone).LocalDateTime.PlusYears(10).InZoneLeniently(sourceZone).ToInstant();

// Get the intervals for our each of the zones over these periods
var sourceIntervals = sourceZone.GetZoneIntervals(start, end);
var destinationIntervals = destinationZone.GetZoneIntervals(start, end);

// Find all of the instants we care about, including the start and end points,
// and all transitions from either zone in between
var instants = sourceIntervals.Union(destinationIntervals)
    .SelectMany(x => new[] {x.Start, x.End})
    .Union(new[] {start, end})
    .OrderBy(x => x).Distinct()
    .Where(x => x >= start && x < end)
    .ToArray();

// Loop through the instants
for (int i = 0; i < instants.Length -1; i++)
{
    // Get this instant and the next one
    Instant instant1 = instants[i];
    Instant instant2 = instants[i + 1];

    // convert each instant to the source zone
    ZonedDateTime zdt1 = instant1.InZone(sourceZone);
    ZonedDateTime zdt2 = instant2.InZone(sourceZone);

    // Get the offsets for instant1 in each zone 
    Offset sourceOffset = zdt1.Offset;
    Offset destOffset = destinationZone.GetUtcOffset(instant1);

    // Calc the difference between the offsets
    int deltaSeconds = (destOffset.Milliseconds - sourceOffset.Milliseconds)/1000;

    // Convert to the same types you had in your example (optional)
    DateTime dt1 = zdt1.ToDateTimeUnspecified();
    DateTime dt2 = zdt2.ToDateTimeUnspecified();

    // emit output
    Console.WriteLine("{0}\t{1}\t{2}", dt1, dt2, deltaSeconds);
}

请注意,输出与您自己的输出相同,除了第一个日期。当我运行它时,你的7/13/2015给了7/14/2015,我给了DateTimeOffset。这是因为您的代码存在错误,因为您的开始日期不是基于今天的源时区,而是从今天的本地时区开始。

另外,我假设您希望所有输出都是 source 时区,因为那是您的示例所给出的。

此外,您可能需要考虑输出对于源区域中的转换不是特别清楚。在回退过渡期间,您无法告诉输出所代表的两次,并且在春季前进过渡期间,间隙< gap < / em>实际上是。在这方面,active == false输出会更加清晰。