考虑时区的夏令时,将 UTC 日期时间转换为未来时间
Converting UTC date time to a time in the future, considering daylight savings time of a time zone
我们有一个系统表示星期,UTC 时间表示 America/Chicago 时区的开始和结束日期时间。周从中部时间星期六上午的午夜开始,到中部时间星期五晚上的 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 之间。
我需要从给定的一周开始 return N 周。我们的系统是 C# Azure worker & web 角色,以及 Azure 云中的 运行(所有计算节点均为 UTC)。我的逻计算出的未来日期。
var weeks = repository.Fetch(x => x.BeginDate <= nWeeksAheadUtc && x.BeginDate > week.BeginDate)l=;
除非结果答案中发生夏令时更改,否则此方法有效。由于时间的变化,根据第 1 周的开始日期加上 21 天来请求从第 1 周开始的接下来的 3 周只会导致第 2 周和第 3 周被 returned,因为计算的未来值为 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# date/time)来做到这一点?
添加请求的示例 - 我刚刚为此和这三个创建了一个 UnitTest 项目 类:
Week.cs
using System;
namespace NodaTimeTest
{
public class Week
{
public int Id { get; set; }
public DateTime BeginDate { get; set; }
public DateTime EndDate { get; set; }
}
}
WeekService.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;
}
}
}
WeekServiceTest:
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);
}
}
}
好的,如果我没听错的话,我想你可能想要这样的东西:
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();
我们有一个系统表示星期,UTC 时间表示 America/Chicago 时区的开始和结束日期时间。周从中部时间星期六上午的午夜开始,到中部时间星期五晚上的 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 之间。
我需要从给定的一周开始 return N 周。我们的系统是 C# Azure worker & web 角色,以及 Azure 云中的 运行(所有计算节点均为 UTC)。我的逻计算出的未来日期。
var weeks = repository.Fetch(x => x.BeginDate <= nWeeksAheadUtc && x.BeginDate > week.BeginDate)l=;
除非结果答案中发生夏令时更改,否则此方法有效。由于时间的变化,根据第 1 周的开始日期加上 21 天来请求从第 1 周开始的接下来的 3 周只会导致第 2 周和第 3 周被 returned,因为计算的未来值为 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# date/time)来做到这一点?
添加请求的示例 - 我刚刚为此和这三个创建了一个 UnitTest 项目 类:
Week.cs
using System;
namespace NodaTimeTest
{
public class Week
{
public int Id { get; set; }
public DateTime BeginDate { get; set; }
public DateTime EndDate { get; set; }
}
}
WeekService.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;
}
}
}
WeekServiceTest:
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);
}
}
}
好的,如果我没听错的话,我想你可能想要这样的东西:
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();