Remarks:
1. In the
previous note, "
time zone ", I translated it as a "time zone", since it was about the time zones of the United States that have a specific name. In this case it is more correct to use the "
time zone ". It uses a more correct translation.
2. A small inset from Wikipedia will give you an understanding of what UTC is and how it differs from GMT -
Coordinated Universal Time (UTC) is the standard by which society regulates hours and time. It differs by an integer number of seconds from atomic time and by a fractional number of seconds from UT1 universal time.
')
UTC was introduced instead of the obsolete Greenwich Mean Time (GMT). The new UTC time scale was introduced because the GMT scale is an uneven scale and is associated with the daily rotation of the Earth. The UTC scale is based on a uniform atomic time scale (TAI) and is more convenient for civilian use.
Time zones around the globe are expressed as positive and negative offset from UTC.
It should be remembered that the time in UTC is not translated neither in winter nor in summer. Therefore, for those places where there is a daylight saving time shift relative to UTC changes.
Now let's continue to deal with the structures that serve entities such as date and time.
Some time after the publication of the tweet about
Noda Time , they started asking me what was the point in using Noda Time - people believed that the support for dates and times in .NET is quite good. I certainly did not see their code, but I suspect that almost any code base dealing with dates will become clearer if you use Noda Time, and also quite possibly become more correct thanks to the approach by which Noda Time forces you to accept some not obvious in .NET solutions. In this post we will discuss the shortcomings of the .NET API, which provides work with dates and times. My attitude to this topic looks somewhat biased, but I hope that this note does not look disrespectful to the team working on the BCL (because Base Class Library is a
translator’s note ) - because, among other things, they work in conditions that force them take into account the interaction with COM, etc.
What does DateTime mean?
When I stumble on the Stack Overflow site to a question that says that DateTime does not do what is expected of it, I often find myself in a state of reflection - what exactly should the specified value represent? The answer is simple - date and time, right? But everything is much more complicated as soon as you start to deal with the problem more thoroughly. For example, suppose a
clock has not ticked between calls to two properties in the code below. So what value will the variable "mystery" take as a result?
DateTime utc = DateTime.UtcNow;<br>DateTime local = DateTime.Now;<br>bool mystery = local == utc;
I honestly don’t know where this code will lead to. There are three versions, each of which has its more or less reasonable justification:
- The value will always be true : two values ​​are associated with the same moment in time, only one is expressed locally, and the second is universally
- The value will always be false : two values ​​represent two different dates, i.e. automatically unequal
- And once again true - if your local time zone is synchronized with UTC ( Coordinated Universal Time ), i.e. when time zones are not taken into account at all - both values ​​will be equal
I don't care much what answer is correct - the non-obviousness of the logic of code behavior is a sign of deeper problems. In fact, everything returns to the DateTime.Kind property, which allows the DateTime to represent three types of values:
DateTimeKind.Utc : UTC Date and Time
DateTimeKind.Local : date and time local to the system in which the code is executed
DateTimeKind.Unspecified : Mmm, slyly. It depends on what you do with it.
The value of a property has a different effect on different operations. For example, if you apply the ToUniversalTime () method to an “unspecified” DateTime value, the method will assume that you are trying to convert a local value. On the other hand, if you apply the ToLocalTime () method to the “unspecified” DateTime value, it will be assumed that initially you had a value in the form of UTC. This is one behavior pattern.
If you create a DateTimeOffset from DateTime and TimeSpan, the behavior is slightly different:
- everything is simple with the UTC value - we pass UTC, we want to get the “UTC + specified offset” representation
- local value is true only occasionally: the designer checks that the offset from UTC coincides at the specified local time in the time zone used by the system by default with the offset specified by you
- The unspecified value is always true and represents a local time in some unspecified time zone, so that the offset is correct at that time.
I do not know about you, but in my case this situation causes a slight semantic hysteria. It's like having a “numeric” type that contains a sequence of numbers, but you have to use a different property to find out this decimal or hexadecimal number, and the answer will sometimes be “Well, what do you think?”.
Of course, in .NET 1.1, the DateTimeKind property was completely absent. This does not mean that the problem did not exist. This means confusing behavior that tries to give meaning to a type that stores different kinds of values ​​and has not tried to be any consistent. It was based on the assumption that the date value permanently has the form Unspecified.
Does using the DateTimeOffset structure fix the problem?
Good. Now we know that we don’t really like DateTime. Will DateTimeOffset help us? Yes, in part. A DateTimeOffset value has a clear meaning: it contains the local date and time with the specified offset from UTC. Perhaps now I have to retreat and explain to you what I mean by "local" date and time, as well as (time) moments.
Local date and time are not tied to a specific time zone. The present moment is later or earlier “16:00 March 13, 2012”? It depends on where you are in this world (this, by the way, I still do not take into account non-ISO calendars). Therefore, DateTimeOffset contains a component that is independent of the time zone (this is “16:00 ...”), but also an offset from UTC - which indicates the possibility of converting it at a
point in time on the time line. If we ignore the theory of relativity, then all people on the planet perceive the current moment at the same time. If I click my fingers (infinitely fast), then any event in the universe will happen before this event or after it. Whether you were in a particular time zone or not - it does not matter. In this regard, the moments are global, compared with the local date and time that each specific individual can observe at a particular point in time.
(Are you still here? Let's continue - I hope that the previous paragraph was the hardest in this article. Although it contains a very important concept.)
So DateTimeOffset refers to a (global) point in time, but also works with a local date and time. This means that this structure is not an ideal type for representing local dates and times — but this is not DateTime. DateTime with the DateTimeKind.Local property set is not really local for the same reasons — it is tied to the default time zone on the system in which it is used. DateTime of the DateTimeKind.Unspecified look fits a little better in some cases - for example, when creating a DateTimeOffset - but the semantics look strange in other cases, as described above. As a result, neither DateTimeOffset nor DateTime are good types for displaying truly local dates and times.
The DateTimeOffset structure is also not a good choice to bind to a specific time zone, since it has no idea which time zone gave the corresponding offset first. In .NET 3.5 there is a completely adequate class TimeZoneInfo, but there is no type that speaks of "local time in a specific time zone." Therefore, having a variable of type DateTimeOffset, you know the specific time in a certain time zone, but you do not know what the local time will be a minute later, since the offset for this zone could change (usually due to daylight saving time).
What about dates and times?
Before that, we discussed only values ​​that store "date and time." And what about the types in which either the date or only the time is stored? Often, of course, you need to store only the date, but there are cases when it is necessary to store only time.
And yes, you can use the DateTime type to store the date - hell, yes, there is even a DateTime.Date property that returns the date for a specific date and time ... but only as a different DateTime value with the time set at midnight. This is not the same as having a separate type that is easily identified as “only date” (or “only time” - .NET uses TimeSpan for this, which again does not seem to me to be absolutely correct).
And what about time zones? Here TimeZoneInfo looks pretty decent.
As I said, TimeZoneInfo is not bad. True, he has two big problems and a few smaller ones.
Firstly, Windows time zone identifiers are taken as the basis. On the one hand it is logical, but on the other - this is not what the rest of the world uses. All non-Windows systems that I have seen use the Olson time zone database (also known as tz or zoneinfo), respectively, it has its own identifiers. Perhaps you saw them - “Europe / London” or “America / Los_Angeles” - these are Olson's identifiers. Work with a web service that provides geo-information - there are chances that it uses Olson IDs. Work with another calendar system - there are chances that it also uses Olson identifiers. There are problems here too. For example, with the stability of identifiers that the
Unicode Consortium is trying to solve with
CLDR ... but at least you have a good chance. It would be great if TimeZoneInfo offered some way to establish a match between two identifier schemes or it would be implemented somewhere else in .NET. (Noda Time is aware of both sets of identifiers, although mapping is not yet available to everyone. This will be fixed before the final release.)
Secondly, this class is based on the use of DateTime and DateTimeOffset, i.e. you must be careful when using it - if you set one type of datetime, and transfer another, then you may have problems. The class is fairly well documented, but, to be honest, the explanation of this kind of things is inherently quite difficult without confusing the situation through the use of conflicting terms.
There are also problems with ambiguous or erroneous values ​​of local dates and times. They occur during the transition to summer / winter time: if the time is shifted forward (for example, from 1:00 to 2:00), then there is a chance to get the wrong local time (for example, that day 1:30 will not come). If the clock is reversed (for example, from 2:00 to 1:00), this leads to ambiguity: 1.30 happens twice. You can explicitly check with TimeZoneInfo when a particular value is incorrect or ambiguous, but it's easy to just forget about this possibility. If you try to convert the local time during UTC using the time zone, an exception will be thrown if the time is incorrect. But the ambiguous time will be taken as the default by default (and not as summer time). This kind of solution does not allow developers to even take into account the features used. Talk about which ...
Too hard
Now you might well think: “Blew out an elephant from a fly. I don't want to think about it - why are you trying to complicate things so much? I have been using the .NET API for years and had no problems. ” If you think so, I can offer three options:
- You are much, much smarter than me, and you understand all these difficulties on an intuitive level. You always use the correct view for a DateTime variable; where necessary, use DateTimeOffset and always do the right thing with incorrect or ambiguous local date / time. Without a doubt, you are also writing a non-blocking multithreaded code with shared access to the state of the object in the most efficient and at the same time reliable way. So why the hell are you reading this, let me know?
- You encountered these problems, but for the most part forgot about them - in the end they took only 10 minutes of your life while you were experimenting to get an acceptable result (or at least to pass the test; although such tests might well have been conceptually incorrect) . Maybe you were perplexed when faced with this problem, but decided that the problem was with you and not with the API.
- You did not encounter this problem because you consider testing the code as a boring task, because it works in one time zone, on computers that are always turned off at night (i.e., they are not affected by winter / summer time transitions). You are, to some extent, lucky, but still you forget about the time zone.
Jokes jokes, but the problem is really real. And if you have never thought about the difference between “local” time and “global” moment before this note, then that’s what you did. This is an important distinction - it is similar to the difference between binary floating-point numbers and floating-point decimal numbers. Errors may not be obvious, difficult to diagnose, poorly explained, poorly corrected and re-occurring elsewhere in the program.
Handling the values ​​of the variables storing the date / time is really not easy. There are such unpleasant incidents as days that do not begin at midnight — thanks to the transition to summer / winter time (for example, Sunday, October 17, 2010, in Brazil began at 1:00). If you are particularly unlucky, then you will have to work with multi-calendar systems (Gregorian, Julian, Coptic, Buddhist, etc.). If you dealt with dates and times of the beginning of the 20th century, you probably noticed very strange transitions of time zones as the passage of strictly longitudinal displacements to more “rounded” values ​​(for example, in Paris in 1911). You may encounter governments changing time zones with a warning about this a couple of weeks before the actual shift. You may also encounter a change in time zone identifiers (for example, Asia / Calcutta to Asia / Kolcata).
All this, of course, lies on the surface of those actual business rules that you are trying to implement. And they, too, can be difficult. Given all these difficulties, you should at least have an API that will allow you to express relatively clearly what you mean.
So is Noda Time perfect?
Of course not. Noda Time has several problems:
1. Despite all the above, I am an amateur when it comes to the theory of date and time.
The second of coordination confuses me. At the thought of the point of change of the Julian calendar to the Gregorian, I am seized with a desire to cry, and therefore I have not yet realized it. As far as I know, none of the participants in the Noda Time project is an expert, although Stephen Colebourne, the author of
Joda Time and the head of the
JSR-310, lurked on the mailing list. (By the way, he attended the first presentation of Noda Time. I asked if anyone in the hall knew the difference between the Gregorian calendar and the ISO-8601 standard calendar. He raised his hand and gave the correct answer. I asked how it happened that he knows the answer , and he replied: “I am Stephen Coleborn.” I almost fell).
2. We have not finished yet. Remarkable API design is useless if there is no implementation.
3. There will definitely be mistakes - the BCL command code is constantly executed on hundreds of thousands of machines around the world. Errors will appear quickly.
4. We do not have the resources - we are just an active group of developers working in pleasure. I don’t say this with regret (this is really great), but inevitable problems with the time that can be allocated to work on additional chips, documentation, etc.
5. We are not part of the BCL. Want to use Noda Time in LINQ to SQL queries (or even NHibernate)? Good luck. Even if we succeed beyond my expectations, I do not think that there will be other open source projects that will be dependent on us for centuries. I must say that I am satisfied with the resulting design. We tried to maintain a balance between flexibility and ease of achieving any specific goal (with great effort, of course). Somehow I will write another note about the design style used, comparing it with both the Joda Time design and the one used in .NET. The best result is the resulting set of types, each of which has its own quite clear role. I will not bore you with details - there will be documentation and other notes for this.
Oddly enough, it would be better for everyone if the team working on the BCL takes note of this article and decides to radically rework the .NET 6 API (I assume that the ship .NET 5 has already been launched). And while I do this, I am sure that there are many other projects that will give me pleasure - frankly, the questions of dates and times are too important for the .NET community to lie for a long time solely on my shoulders.
findings
I hope that I have convinced you that the .NET API has significant shortcomings. Perhaps I also convinced you that Noda Time deserves closer acquaintance, but this was not the main goal. — DateTime — . .
( , —
. ).
() .. aka hDrummer,
.