DateTime, DST, ‘Daylight Saving Time’, Time zone, Ambiguous time, Invalid Time

That are the most important words I remembered from my journey into making our application ready for users all over the world.

The question was simple: we now save datetimes in local format, but when we plan on implementing our application outside our own country, we have to change that.

So after a short discussion we decided to follow the best practice: save the dates in UTC format.

So when data is imported or entered via the screen, check where it comes from, calculate the right UTC time from it, and then save the data to the database.

And also: when a user asks for data for august 15th, first determine where the user is located, calculate the right UTC time for ‘his’ august 15th, and then get the data from the database with the UTC time as selection criteria.

timezone_map

So where to start?

First I’ve checked the DateTime class, and it’s got a ToLocalTime() method and a ToUniversalTime() method.

That looked promising , but it doesn’t take the users time zone into account.

It doesn’t allow me to ask: what is the UTC time for a user in a time zone which has a UTC offset of -9 (when you live in a place with UTC – 9, your local time is the UTC time (which is located in England) minus 9 hours. So when it’s 14.00 hours in the UTC area / England, you’re probably sleeping at 5 AM. I’ll come back at the summertime / wintertime differences later)

The better solution is the .Net TimeZoneInfo class.
This is designed for the kind of questions I want to ask.

So, how do I determine what the UTC time is for ‘his August 15th, 0.00 AM’?
First I want to know in which time zone he is in. Does he live in -9 or +9, that makes quite a difference. We decided to store that as a user preference in his profile.

For data import, we had to know the time zone the data supplier uses for our data imports.

Then, we have to know if the user’s local time was in Daylight Saving Time (DST) or not. Not all countries use DST, but for every different time zone there are different ones if there are DST and none-DST countries in it.
So there are multiple +1 time zones, some which have DST and some have not.

So how to start with your TimeZoneInfo?
First create one with the TimeZoneInfo.FindSystemTimeZoneById("W. Europe Standard Time") method. The name of the timezones can be retrieved like this:

TimeZoneInfo.GetSystemTimeZones() This returns a collection of TimeZoneInfo objects with an id property you can use.

Then you can ask whether this timezone supports / has DST:

timeZone.SupportsDaylightSavingTime() (this time timeZone is an object instantiated via TimeZoneInfo.FindSystemTimeZoneById())

So, then we can check if August 15th, 0.00 AM exists (when going from wintertime to summertime, the clock goes from 1.59.59 to 3.00.00, so on that day a time of 02.15 is not valid):

timeZoneInfo.IsInvalidTime(dateTime);

If it is a valid date, we have to check if it is an ambiguous date (when going from summertime to wintertime, the clock goes from 02.59.59 to 02.00.00, and then we have the hour from 02.00.00 to 02.59.59 twice).
Because if you get an ambiguous time, we have to know: was it the ‘first’ 02.15 or the ‘second’ 02.15.
In the latter case we are in wintertime and we don’t have to worry about the summertime extra hour.
When it is not an ambiguous time, we can ask if he is in summertime or not:

timeZoneInfo.IsDaylightSavingTime(dateTime)

Then we have to know what the base offset for this time zone is timeZoneInfo.BaseUtcOffset.Hours
and
timeZoneInfo.BaseUtcOffset.Minutes

Let’s say he is in -9: the UTC time is 0.00.00 + 9 hours = 0.09.00
Let’s say he is in summertime so he is actually in (-9 + 1) = -8.

So the UTC time is 0.00.00 + 8 hours = 0.08.00

Finally we got the UTC date, and we can get the data out of the database with this new UTC date.
The other way around (you have a UTC time, and want to know what time it is in ‘his’ time zone)?
It’s simpler, just use

TimeZoneInfo.ConvertTimeFromUtc(dateTime, timeZoneInfo);

Het technisch ontwerp

In het kader van Agile development wordt er gediscussieerd over de noodzaak van technische documentatie (het TO). En imho: dat is goed: want niets zo verschillend in kwaliteit, bruikbaarheid en gewicht als een technisch ontwerp, dus een beetje discussie er over kan mijns inziens geen kwaad.
Nou denk ik dat veel mensen die hebben gewerkt met TO’s van 8 kg of meer het wel met de Agile gedachte eens zullen zijn: daar heb je niet zo veel aan. Je kunt onmogelijk alles onthouden, en het is voor de makers van een dereglijk TO zo’n klus om alles te beschrijven dat voor sommige zaken toch nog wel eens een shortcut wordt genomen.
Nou zie als developer veel soorten TO’s, en het grappige is dat het bij elk bedrijf weer anders is. Bij het vorige project moest alles van te voren worden opgeschreven: de WSDL, de indexen, de triggers, het verwachte aantal records per tabel etc., en op het huidige project worden alleen de code wijzigingen achteraf gedocumenteerd. Wat echter niet vaak in een TO naar voren komt zijn design- en code keuzes: waarom zijn bepaalde beslissingen genomen, waarom zijn bepaalde stukken code op een bepaalde manier geschreven en wat betekenen bepaalde tabellen en velden in de database.

Nu, vers op een nieuw project, zit ik weer naar een stuk query te kijken waar een hele stapel joins wordt gebruikt, maar geen enkele documentatie beschikbaar is. Waarbij ik me dus afvraag: waarom moet de verbandcode gelijk zijn aan 8120995, de actie gelijk zijn aan 2 en de vId <> -1 om deze set van personen op te halen?
En wat is het verschil tussen de pId, de vId, de Mid, en de nId? De ‘antwoorden’ die ik wel hier op papier heb staan is dat de vorige developer vijf files heeft aangepast, compleet met de melding dat hij voor een class property een getter en een setter heeft geschreven, en een private field. En dat geeft me nou net weer geen enkele informatie waar ik wat aan heb.

Wat me een aardig uitgangspunt lijkt voor een TO is: documenteer je conclusies en waar nodig je keuzes en het denkproces: Wat is de verbandcode, en wat betekenen de codes, zijn die intern gekoppeld aan een enum? Gebruik gerust meer dan 3 karakters voor je veldnamen, en leg uit wat ze betekenen. Besteed geen tijd aan het documenteren van het veld ‘achternaam’ bij je class Persoon, en ook bij ‘huisnummertoevoeging’ kan ik me wel wat voorstellen. Leg wel uit waarom je bepaalde data cacht, en als je denkt: ik codeer hier een veldje bij dat het onderscheid maakt tussen A en B, typ dat er dan wel even bij.

Moraal van het verhaal: houd je niet vast aan een alles of niets strategie bij het schrijven van een TO, maar schrijf op wat je brainwaves waren om een ander de kans te gunnen je gedachten te volgen, en schrijf niet op dat het verwachte verloop van het aantal records in de tabel ‘geslacht’ in de aankomende 5 jaar waarschijnlijk nul zal zijn.