从通用或本地DateTime添加/减去的最佳实践

时间:2017-07-11 10:00:09

标签: c# .net datetime timezone dst

我试图在DateTime周围添加一个包装来包含时区信息。这就是我到目前为止所拥有的:

public struct DateTimeWithZone {
    private readonly DateTime _utcDateTime;
    private readonly TimeZoneInfo _timeZone;

    public DateTimeWithZone(DateTime dateTime, TimeZoneInfo timeZone) {
        _utcDateTime = TimeZoneInfo.ConvertTimeToUtc(DateTime.SpecifyKind(dateTime, DateTimeKind.Unspecified), timeZone);
        _timeZone = timeZone;
    }

    public DateTime UniversalTime { get { return _utcDateTime; } }

    public TimeZoneInfo TimeZone { get { return _timeZone; } }

    public DateTime LocalTime { get { return TimeZoneInfo.ConvertTimeFromUtc(_utcDateTime, _timeZone); } }

    public DateTimeWithZone AddDays(int numDays) {
        return new DateTimeWithZone(TimeZoneInfo.ConvertTimeFromUtc(UniversalTime.AddDays(numDays), _timeZone), _timeZone);
    }

    public DateTimeWithZone AddDaysToLocal(int numDays) {
        return new DateTimeWithZone(LocalTime.AddDays(numDays), _timeZone);
    }
}

这是根据之前的问题中提供的@Jon Skeet答案改编的。

由于夏令时问题,我正在努力增加/减少时间。根据以下内容,最佳做法是添加/减去通用时间:

https://msdn.microsoft.com/en-us/library/ms973825.aspx#datetime_topic3b

我遇到的问题是,如果我说:

var timeZone = TimeZoneInfo.FindSystemTimeZoneById("Romance Standard Time");            
var date = new DateTimeWithZone(new DateTime(2003, 10, 26, 00, 00, 00), timeZone);
date.AddDays(1).LocalTime.ToString();

这将于26/10/2003 23:00:00返回。正如你所看到的那样,当地的时间已经减少了一个小时(由于夏令时结束),所以如果我要显示这个,那么就会说它是在它加入一天的同一天至。但是,如果我要说:

date.AddDaysToLocal(1).LocalTime.ToString();

我会在27/10/2003 00:00:00回来并保留时间。这对我来说是正确的,但它违背了加入世界时的最佳做法。

如果有人可以帮助澄清正确的方法,我会很感激。请注意,我已经查看了Noda Time,它目前需要做太多工作才能转换成它,我也希望能更好地理解这个问题。

2 个答案:

答案 0 :(得分:4)

这两种方式都是正确的(或不正确的),具体取决于您需要做什么。

我喜欢将这些视为不同类型的计算:

  1. 按时间顺序计算。

  2. Calendrical 计算。

  3. 按时间顺序计算涉及以物理时间为常规单位的时间算术。例如,添加秒,纳秒,小时或天。

    calendrical 计算涉及时间算术,单位是人类觉得方便,但并不总是具有相同的物理时间长度。例如,增加数月或数年(每个都有不同的天数)。

    当您想要添加一个不一定具有固定秒数的粗单位时,日历计算很方便,但您仍然希望在日期中保留更精细的字段单位,例如天,小时,分钟和秒钟。

    在您的本地时间计算中,您添加一天,并假设日历计算是您的意图,您保留当地时间,尽管在本地日历中1天并非总是24小时。请注意,本地时间的算术可能会导致本地时间与两个映射到UTC,甚至映射到UTC。因此,您的代码应该构建为使您知道这种情况永远不会发生,或者能够检测到它的作用并以适合您的应用程序的任何方式做出反应(例如,消除模糊映射的歧义)。

    在您的UTC时间计算(按时间顺序计算)中,您总是添加86400秒,然后本地日历会做出反应,但这可能是由于UTC偏移更改(夏令时相关或其他)。 UTC偏移量变化可能大到24小时,因此添加按时间顺序排列的日期可能甚至不会使当月日历日突然加1。按时间顺序计算的结果总是具有唯一的UTC< - >。局部映射(假设输入具有唯一的映射)。

    两种计算都很有用。两者都是常见的。知道您需要哪些,并知道如何使用API​​来计算您需要的任何内容。

答案 1 :(得分:3)

只是为了补充霍华德的最佳答案,了解最佳实践"你所指的是关于以已过去的时间递增。实际上,如果你想增加24小时,你可以用UTC做到这一点,并且你发现你最终会在23:00结束,因为当天还有一个小时的时间。

我通常会考虑添加一天作为日历计算(使用霍华德的术语),因此当天没有多少小时也不重要 - 你在当地增加了一天时间。

然后你必须验证结果是那天的有效时间,因为它很可能会让你找到一个无效的值,在" gap"向前转型你必须决定如何调整。同样,当您转换为UTC时,您应该测试不明确的时间并相应地进行调整。

了解通过不对自己进行任何调整,您依赖于TimeZoneInfo方法的默认行为,这些方法会在模糊时间内调整向后(即使通常需要的行为是调整前进),ConvertTimeFromUtc会在无效时间内抛出异常。

这就是为什么Noda Time中的ZonedDateTime具有" resolvers"的概念。允许您更具体地控制此行为。您的代码缺少任何类似的概念。

我还要补充说,当你说你看过Noda Time并且转换成它的工作太多时 - 我鼓励你再看一遍。一个人不一定需要改进他们的整个应用程序来使用它。你可以,但你也可以在需要的地方介绍它。例如,您可能希望在此DateTimeWithZone类中内部使用它,以迫使您沿着正确的路径前进。

还有一件事 - 当您在输入中使用SpecifyKind时,您基本上会说要忽略输入类型。由于您正在设计用于重用的通用代码,因此您可能会发现错误的可能性。例如,我可能会传递DateTime.UtcNow,您将认为它是基于时区的时间。 Noda Time通过使用单独的类型而不是" kind"来避免这个问题。如果您要继续使用DateTime,那么您应该评估应用适当操作的类型。只是忽略它会让你遇到麻烦。