将 UTC 日期时间转换为将来的时间,考虑时区的夏令时

本文关键字:时间 夏令时 时区 将来 UTC 日期 转换 | 更新日期: 2023-09-27 18:36:51

我们有一个系统,它代表周,UTC时间代表美国/芝加哥时区的开始和结束日期时间。 周从中部时间星期六早上的午夜开始,到星期五晚上中部时间的 23:59:59 结束,因此它们在数据库中的 UTC 条目是:

Week 1 - begin: 2015-10-24 05:00:00, end 2015-10-31 04:59:59
Week 2 - begin: 2015-10-31 05:00:00, end 2015-11-07 05:59:59
Week 3 - begin: 2015-11-07 06:00:00, end 2015-11-14 05:59:59
Week 4 - begin: 2015-11-14 06:00:00, end 2015-11-21 05:59:59
Week 5 - begin: 2015-11-21 06:00:00, end 2015-11-28 05:59:59
因此,从

上面的几周示例中,您可以看到从白天到标准时间的时间变化反映在 10/31 和 11/7 之间。

我需要从给定的一周返回 N 周。 我们的系统是C# Azure工作线程和Web角色,并在Azure云中运行(所有计算节点都是UTC)。 我的逻辑是,采用开始周,并将 N 周的工作天数添加到一周的开始日期/时间,并要求开始日期大于原始开始日期且小于或等于计算的未来日期的周。

var weeks = repository.Fetch(x => x.BeginDate <= nWeeksAheadUtc && x.BeginDate > week.BeginDate)l=;

除非在生成的答案中发生夏令时更改,否则此操作有效。 由于时间变化,基于在第 1 周的开始日期上添加 21 天来请求从第 1 周开始的接下来 3 周只会导致返回第 2 周和第 3 周,因为计算的未来值是 2015-11-14 05:00:00,其中不包括第 4 周。

我已经通过以下方式使用Nodatime解决了这个问题:

LocalDateTime localDateTime = LocalDateTime.FromDateTime(week.BeginDate);
ZonedDateTime zonedDateTime = localDateTime.InZoneStrictly(DateTimeZoneProviders.Tzdb["UTC"]);
zonedDateTime = zonedDateTime.WithZone(DateTimeZoneProviders.Tzdb["America/Chicago"]);
DateTime centralDateTime = zonedDateTime.ToDateTimeUnspecified();
DateTime futureDateTime = centralDateTime.Add(TimeSpan.FromDays(weekCount*7));
localDateTime = LocalDateTime.FromDateTime(futureDateTime);
zonedDateTime = localDateTime.InZoneStrictly(DateTimeZoneProviders.Tzdb["America/Chicago"]);
DateTime nWeeksAheadUtc = zonedDateTime.ToDateTimeUtc();
var weeks = repository.Fetch(x => x.BeginDate <= nWeeksAheadUtc && x.BeginDate > week.BeginDate).OrderBy(x => x.RetailerWeekNumber).ToList();

虽然它起作用,但对于跟随我维护此代码的开发人员来说,它似乎很麻烦且不是很直观。 有没有一种更干净的方法可以通过我缺少的Nodatime API或(基本C#日期/时间)来做到这一点?

添加请求的示例 - 我刚刚为此和这三个类创建了一个 UnitTest 项目:

周.cs

using System;
namespace NodaTimeTest
{
    public class Week
    {
        public int Id { get; set; }
        public DateTime BeginDate { get; set; }
        public DateTime EndDate { get; set; }
    }
}

周服务.cs

using NodaTime;
using System;
using System.Collections.Generic;
using System.Linq;
namespace NodaTimeTest
{
    public class WeekService
    {
        private readonly List<Week> repository;
        public WeekService()
        {
            this.repository = this.InitWeeks();
        }
        public List<Week> GetNextWeeks(int weekId, int weekCount)
        {
            Week week = this.repository.First(x => x.Id == weekId);
            // the meat - how to do this the right way?
            LocalDateTime localDateTime = LocalDateTime.FromDateTime(week.BeginDate);
            ZonedDateTime zonedDateTime = localDateTime.InZoneStrictly(DateTimeZoneProviders.Tzdb["UTC"]);
            zonedDateTime = zonedDateTime.WithZone(DateTimeZoneProviders.Tzdb["America/Chicago"]);
            DateTime centralDateTime = zonedDateTime.ToDateTimeUnspecified();
            DateTime futureDateTime = centralDateTime.Add(TimeSpan.FromDays(weekCount * 7));
            localDateTime = LocalDateTime.FromDateTime(futureDateTime);
            zonedDateTime = localDateTime.InZoneStrictly(DateTimeZoneProviders.Tzdb["America/Chicago"]);
            DateTime nWeeksAheadUtc = zonedDateTime.ToDateTimeUtc();
            var weeks = repository.Where(x => x.BeginDate <= nWeeksAheadUtc && x.BeginDate > week.BeginDate).OrderBy(x => x.Id).ToList();
            return weeks;
        }
        private List<Week> InitWeeks()
        {
            // sets up our list of 10 example dates in UTC encompassing America/Chicago daylight savings time change on 11/1
            // this means that all weeks are 168 hours long, except week "4", which is 169 hours long.
            var weeks = new List<Week>();
            DateTime beginDate = new DateTime(2015, 10, 10, 5, 0, 0, DateTimeKind.Utc);
            for (int i = 1; i <= 10; i++)
            {
                DateTime endDate = beginDate.AddDays(7).AddSeconds(-1);
                if (endDate.Date == new DateTime(2015, 11, 7, 0, 0, 0, DateTimeKind.Utc))
                {
                    endDate = endDate.AddHours(1);
                }
                weeks.Add(new Week { Id = i, BeginDate = beginDate, EndDate = endDate });
                beginDate = endDate.AddSeconds(1);
            }
            return weeks;
        }
    }
}

周服务测试:

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Linq;
namespace NodaTimeTest
{
    [TestClass]
    public class WeekServiceTest
    {
        private readonly WeekService weekService = new WeekService();
        [TestMethod]
        public void TestGetNextThreeWeeksOverDaylightTimeChange()
        {
            var result = this.weekService.GetNextWeeks(2, 3);
            Assert.AreEqual(3, result.ElementAt(0).Id);
            Assert.AreEqual(4, result.ElementAt(1).Id);
            Assert.AreEqual(5, result.ElementAt(2).Id);
        }
        [TestMethod]
        public void TestGetNextThreeWeeksWithNoDaylightTimeChange()
        {
            var result = this.weekService.GetNextWeeks(5, 3);
            Assert.AreEqual(6, result.ElementAt(0).Id);
            Assert.AreEqual(7, result.ElementAt(1).Id);
            Assert.AreEqual(8, result.ElementAt(2).Id);
        }
    }
}

将 UTC 日期时间转换为将来的时间,考虑时区的夏令时

好吧,如果我理解正确,我想你可能想要这样的东西:

var zone = DateTimeZoneProviders.Tzdb["America/Chicago"];
var instantStart = Instant.FromDateTimeUtc(week.BeginDate);
var chicagoStart = instantStart.InZone(zone);
var localEnd = chicagoStart.LocalDateTime.PlusWeeks(weekCount);
var chicagoEnd = localEnd.InZoneLeniently(zone);
var bclEnd = chicagoEnd.ToDateTimeUtc();
var result = repository
    .Fetch(x => x.BeginDate >= week.BeginDate && x.BeginDate < bclEnd)
    .OrderBy(x => x.RetailerWeekNumber)
    .ToList();

请注意,我已经将下限包含和上限排除 - 这通常是最简单的做事方法。

当然,如果你真的想,你可以把很多东西链接在一起:

var zone = DateTimeZoneProviders.Tzdb["America/Chicago"];
var bclEnd = Instant.FromDateTimeUtc(week.BeginDate)
    .InZone(zone)
    .LocalDateTime
    .PlusWeeks(weekCount)
    .InZoneLeniently(zone)
    .ToDateTimeUtc();

编辑:以上是如果您的BeginDate真的是您想要开始获取数据的时刻。听起来在现实中,你想增加一个星期开始。那时它将是:

var zone = DateTimeZoneProviders.Tzdb["America/Chicago"];
var instantNow = Instant.FromDateTimeUtc(week.BeginDate);
var chicagoNow = instantStart.InZone(zone);
var localStart = chicagoNow.LocalDateTime.PlusWeeks(1);
var localEnd = localEnd(weekCount);
var bclStart = localStart.InZoneLeniently(zone).ToDateTimeUtc();
var bclEnd = localEnd.InZoneLeniently(zone).ToDateTimeUtc();
var result = repository
    .Fetch(x => x.BeginDate >= bclStart && x.BeginDate < bclEnd)
    .OrderBy(x => x.RetailerWeekNumber)
    .ToList();