我的用户可以位于不同的时区,我希望确定他们的日期和月份的开头的UTC值。在一个对象里面,我有一个尝试这样做的方法;它看起来像这样:
private void SetUserStartTimesUTC()
{
DateTime TheNow = DateTime.UtcNow.ConvertUTCTimeToUserTime(this.UserTimezoneID);
DateTime TheUserStartDateUserTime = TheNow.Date;
DateTime TheUserStartMonthUserTime = new DateTime(TheNow.Year, TheNow.Month, 1);
DateTime TheUserEndMonthUserTime = TheUserStartMonthUserTime.AddMonths(1);
this.UserStartOfDayUTC = TheUserStartDateUserTime.ConvertUserTimeToUTCTime(this.UserTimezoneID);
this.UserStartOfMonthUTC = TheUserStartMonthUserTime.ConvertUserTimeToUTCTime(this.UserTimezoneID);
this.UserEndOfMonthUTC = TheUserEndMonthUserTime.ConvertUserTimeToUTCTime(this.UserTimezoneID);
}
此方法依赖于另外两种在用户时间和UTC时间之间进行转换的扩展方法
public static DateTime ConvertUserTimeToUTCTime(this DateTime TheUserTime, string TheTimezoneID)
{
TimeZoneInfo TheTZ = TimeZoneInfo.FindSystemTimeZoneById(TheTimezoneID);
DateTime TheUTCTime = new DateTime();
if (TheTZ != null)
{
DateTime UserTime = new DateTime(TheUserTime.Year, TheUserTime.Month, TheUserTime.Day, TheUserTime.Hour, TheUserTime.Minute, TheUserTime.Second);
TheUTCTime = TimeZoneInfo.ConvertTimeToUtc(UserTime, TheTZ);
}
return TheUTCTime;
}
public static DateTime ConvertUTCTimeToUserTime(this DateTime TheUTCTime, string TheTimezoneID)
{
TimeZoneInfo TheTZ = TimeZoneInfo.FindSystemTimeZoneById(TheTimezoneID);
DateTime TheUserTime = new DateTime();
if (TheTZ != null)
{
DateTime UTCTime = new DateTime(TheUTCTime.Year, TheUTCTime.Month, TheUTCTime.Day, TheUTCTime.Hour, TheUTCTime.Minute, 0, DateTimeKind.Utc);
TheUserTime = TimeZoneInfo.ConvertTime(UTCTime, TheTZ);
}
return TheUserTime;
}
现在我已经处理了一段时间的时区问题,我知道时区问题可能会引入一些难以发现的错误。
我的时区实现是否正确或是否存在会产生某种错误的边缘情况?
感谢您的建议。
答案 0 :(得分:4)
说实话,你的方法似乎不必要复杂。
为什么你会有一个名为TheUTCTime
的参数,然后创建它的UTC版本?它不应该已经拥有UTC的Kind
吗?即使它没有,你最好还是使用DateTime.SpecifyKind
- 目前转换一种方式你消灭秒,而转换另一种方式你没有...在两种情况下你擦除任何子 - 第二个值。
此外:
TimeZoneInfo.FindSystemTimeZoneById
永远不会返回null
new DateTime()
(即公元0001年1月1日)似乎是指示错误的一种非常糟糕的方式ConvertTime
的结果我个人强烈建议你完全避免使用BCL DateTime
。作为主要作者,我完全有偏见,但我至少希望你能找到Noda Time更加愉快的工作......它将“约会没有时间成分”的想法分开了,“没有日期组件的时间“,”没有特定时区的本地日期和时间“和”特定时区的日期和时间“......所以类型系统可以帮助您做出明智的事情。
编辑:如果你真的 在BCL类型中执行此操作,我会这样写:
private void SetUserStartTimesUTC()
{
DateTime nowUtc = DateTime.UtcNow;
TimeZoneInfo zone = TimeZoneInfo.FindSystemTimeZoneById(UserTimeZoneID);
// User-local values, all with a Kind of Unspecified.
DateTime now = TimeZoneInfo.ConvertTime(nowUtc, zone);
DateTime today = now.Date;
DateTime startOfThisMonth = todayUser.AddDays(1 - today.Day);
DateTime startOfNextMonth = startOfThisMonth.AddMonths(1);
// Now convert back to UTC... see note below
UserStartOfDayUTC = TimeZoneInfo.ConvertTimeToUtc(today, zone);
UserStartOfMonthUTC = TimeZoneInfo.ConvertTimeToUtc(startOfThisMonth, zone);
UserEndOfMonthUTC = TimeZoneInfo.ConvertTimeToUtc(startOfNextMonth, zone);
}
您可以看到,您添加的扩展方法实际上并没有带来太多好处。
现在,代码提到了一个“注释” - 你现在总是假设午夜总是存在并且是明确的。在所有时区都不是这样。例如,在巴西,夏令时向前变化,时间从午夜跳到凌晨1点 - 所以午夜本身基本无效。
在Noda Time中,我们通过DateTimeZone.AtStartOfDay(LocalDate)
来解决这个问题,但是对于BCL来说并不容易。
为了比较,等效的Noda Time代码如下所示:
private void SetUserStartTimesUTC()
{
// clock would be a dependency; you *could* use SystemClock.Instance.Now,
// but the code would be much more testable if you injected it.
Instant now = clock.Now;
// You can choose to use TZDB or the BCL time zones
DateTimeZone zone = zoneProvider.FindSystemTimeZoneById(UserTimeZoneID);
LocalDateTime userLocalNow = now.InZone(zone);
LocalDate today = userLocalNow.Date;
LocalDate startOfThisMonth = today.PlusDays(1 - today.Day);
LocalDate startOfNextMonth = startOfThisMonth.PlusMonths(1);
UserStartOfDayUTC = zone.AtStartOfDay(today);
UserStartOfMonthUTC = zone.AtStartOfDay(startOfThisMonth);
UserEndOfMonthUTC = zone.AtStartOfDay(startOfNextMonth);
}
...属性的类型为ZonedDateTime
(记住时区)。如果需要,您可以将它们更改为Instant
类型(这只是一个时间点),只需为每个属性设置器链接一个ToInstant
调用。