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

本文关键字:时区 周期 之间 两个 查找 | 更新日期: 2023-09-27 18:02:25

我的代码将为两个日期之间的所有周期提供目标时区和源时区之间以秒为单位的偏移量。

下面的代码将这样做:

// 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 ║
║          ...          ║          ...          ║   ...  ║
╚═══════════════════════╩═══════════════════════╩════════╝

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

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

下面是一个使用Noda时间的解决方案:

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输出将更加清晰。