简体   繁体   English

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

[英]Best practice for adding/subtracting from universal or local DateTime

I'm trying to add a wrapper around DateTime to include the time zone information. 我正在尝试在DateTime周围添加一个包装器以包含时区信息。 Here's what I have so far: 这是我到目前为止所拥有的:

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);
    }
}

This has been adapted from an answer @Jon Skeet provided in an earlier question. 这是根据先前问题中提供的@Jon Skeet的答案改编的。

I am struggling with with adding/subtracting time due to problems with daylight saving time. 由于夏令时问题,我正在努力增加/减少时间。 According to the following it is best practice to add/subtract the universal time: 根据以下内容,最佳做法是添加/减去通用时间:

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

The problem I have is that if I say: 我遇到的问题是,如果我说:

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();

This will return 26/10/2003 23:00:00. 这将返回26/10/2003 23:00:00。 As you can see the local time has lost an hour (due to daylight saving time ending) so if I was to display this, it would say it's the same day as the day it's just added a day to. 正如你所看到的那样,当地时间已经减少了一个小时(由于夏令时结束),所以如果我要显示它,它会说它与它刚刚添加一天的那天相同。 However if i was to say: 但是,如果我要说:

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

I would get back 27/10/2003 00:00:00 and the time is preserved. 我会回来27/10/2003 00:00:00并保留时间。 This looks correct to me but it goes against the best practice to add to the universal time. 这看起来对我来说是正确的,但它违背了加入世界时的最佳做法。

I'd appreciate it if someone could help clarify what's the correct way to do this. 如果有人能帮助澄清正确的方法,我会很感激。 Please note that I have looked at Noda Time and it's currently going to take too much work to convert to it, also I'd like a better understanding of the problem. 请注意,我已经看过Noda Time,它目前需要做太多工作才能转换成它,我也想更好地理解这个问题。

Both ways are correct (or incorrect) depending upon what you need to do. 两种方式都是正确的(或不正确的)取决于您需要做什么。

I like to think of these as different types of computations: 我喜欢将这些视为不同类型的计算:

  1. Chronological computation. 按时间顺序计算。

  2. Calendrical computation. 日历计算。

A chronological computation involves time arithmetic in units that are regular with respect to physical time. 按时间顺序计算涉及以物理时间为常规单位的时间算术。 For example the addition of seconds, nanoseconds, hours or days. 例如,添加秒,纳秒,小时或天。

A calendrical computation involves time arithmetic in units that humans find convenient, but which don't always have the same length of physical time. 日历计算涉及人类发现方便的单位时间算术,但并不总是具有相同的物理时间长度。 For example the addition of months or years (each of which have a varying number of days). 例如,添加数月或数年(每个都有不同的天数)。

A calendrical computation is convenient when you want to add a coarse unit that does not necessarily have a fixed number of seconds in it, and yet you still want to preserve the finer field units in the date, such as days, hours, minutes and seconds. 当您想要添加一个不一定具有固定秒数的粗单位时,日历计算很方便,但您仍希望在日期中保留更精细的字段单位,例如天,小时,分钟和秒。

In your local time computation, you add a day, and presuming a calendrical computation is what you intended, you preserve the local time of day, despite the fact that 1 day is not always 24 hours in the local calendar. 在您的本地时间计算中,您添加一天,并假设日历计算是您的意图,您保留当地时间,尽管在本地日历中1天并非总是24小时。 Be aware that arithmetic in local time has the potential to result in a local time that has two mappings to UTC, or even zero mappings to UTC. 请注意,本地时间的算术可能会导致本地时间与UTC有两次映射,甚至是映射到UTC的映射。 So your code should be constructed such that you know this can never happen, or be able to detect when it does and react in whatever way is correct for your application (eg disambiguate an ambiguous mapping). 因此,您的代码应该构建为使您知道这种情况永远不会发生,或者能够检测到它何时发生并以适合您的应用程序的任何方式做出反应(例如消除模糊映射的歧义)。

In your UTC time computation (a chronological computation), you always add 86400 seconds, and the local calendar can react however it may due to UTC offset changes (daylight saving related or otherwise). 在您的UTC时间计算(按时间顺序计算)中,您总是添加86400秒,然后本地日历可能会做出反应,但这可能是由于UTC偏移更改(夏令时相关或其他)。 UTC offset changes can be as large as 24h, and so adding a chronological day may not even bump the local calendar day of the month by one. UTC偏移量变化可能大到24小时,因此添加按时间顺序排列的日期可能甚至不会使当月日历日突然加1。 Chronological computations always have a result which has a unique UTC <-> local mapping (assuming the input has a unique mapping). 按时间顺序计算的结果总是具有唯一的UTC < - >局部映射(假设输入具有唯一的映射)。

Both computations are useful. 两种计算都很有用。 Both are commonly needed. 两者都是常见的。 Know which you need, and know how to use the API to compute whichever you need. 知道您需要哪些,并知道如何使用API​​来计算您需要的任何内容。

Just to add to Howard's great answer, understand that the "best practice" you refer to is about incrementing by an elapsed time. 只是为了补充霍华德的好答案,要明白你所指的“最佳实践”是关于经过一段时间的增量。 Indeed, if you wanted to add 24 hours, you'd do that in UTC and you'd find you'd end up on 23:00 due to there being an extra hour in that day. 实际上,如果你想增加24小时,你会在UTC中这样做,你会发现你最终会在23:00结束,因为当天还有一个小时。

I typically consider adding a day to be a calendrical computation (using Howard's terminology), and thus it doesn't matter how many hours there are on that day or not - you increment the day in local time. 我通常会考虑添加一天作为日历计算(使用霍华德的术语),因此无论当天有多少小时都没关系 - 你在当地时间增加一天。

You do then have to verify that the result is a valid time on that day, as it very well may have landed you on an invalid value, in the "gap" of a forward transition. 然后你必须验证结果是那天的有效时间,因为很可能在前向转换的“间隙”中找到了无效值。 You'll have to decide how to adjust. 你必须决定如何调整。 Likewise, when you convert to UTC, you should test for ambiguous time and adjust accordingly. 同样,当您转换为UTC时,您应该测试不明确的时间并相应地进行调整。

Understand that by not doing any adjusting on your own, you're relying on the default behavior of the TimeZoneInfo methods, which adjust backward during an ambiguous time (even though the usually desired behavior is to adjust forward ), and that ConvertTimeFromUtc will throw an exception during an invalid time. 要知道,不这样做你自己的任何调整,你依靠的默认行为TimeZoneInfo方法,在不明确的时间(尽管通常期望的行为是调整 ),它向后调整,并ConvertTimeFromUtc将抛出无效时间内的例外情况。

This is the reason why ZonedDateTime in Noda Time has the concept of "resolvers" to allow you to control this behavior more specifically. 这就是为什么Noda Time中的ZonedDateTime具有“解析器”的概念,以允许您更具体地控制此行为。 Your code is missing any similar concept. 您的代码缺少任何类似的概念。

I'll also add that while you say you've looked at Noda Time and it's too much work to convert to it - I'd encourage you to look again. 我还要补充说,虽然你说你已经看过Noda Time而且转换成它的工作太多了 - 我鼓励你再看一遍。 One doesn't necessarily need to retrofit their entire application to use it. 人们不一定需要改进他们的整个应用程序来使用它。 You can, but you can also just introduce it where it's needed. 你可以,但你也可以在需要的地方介绍它。 For example, you might want to use it internally in this DateTimeWithZone class, in order to force you down the right path. 例如,您可能希望在此DateTimeWithZone类中内部使用它,以强制您沿着正确的路径前进。

One more thing - When you use SpecifyKind in your input, you're basically saying to ignore whatever the input kind is. 还有一件事 - 当你在输入中使用SpecifyKind时,你基本上就是要忽略输入类型。 Since you're designing general purpose code for reuse, you're inviting the potential for bugs. 由于您正在设计用于重用的通用代码,因此您可能会发现错误的可能性。 For example, I might pass in DateTime.UtcNow , and you're going to assume it's the timezone-based time. 例如,我可能会传入DateTime.UtcNow ,您将假设它是基于时区的时间。 Noda Time avoids this problem by having separate types instead of a "kind". Noda Time通过使用单独的类型而不是“kind”来避免这个问题。 If you're going to continue to use DateTime , then you should evaluate the kind to apply an appropriate action. 如果您要继续使用DateTime ,那么您应该评估应用适当操作的类型。 Just ignoring it is going to get you into trouble for sure. 只是忽略它肯定会让你陷入困境。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM