Converting from a UTC-based SYSTEMTIME directly to a local-time-based SYSTEMTIME


Last year, I presented this commutative diagram

A 2-by-2 grid of boxes. The top row is labeled FILE­TIME; the bottom row is labeled SYSTEM­TIME. The first column is labeled UTC; the second column is labeled Local. The upper left box is labeled Get­System­Time­As­File­Time. There is an outgoing arrow to the right labeled File­Time­To­Local­File­Time leading to the box in the second column labeled None. There is an outgoing arrow downward labeled File­Time­To­System­Time leading to the box in the second row, first column, labeled Get­System­Time. From the box in the upper right corner labeled None, there is an outgoing arrow downward labeled File­Time­To­System­Time leading to the box in the second row, second column, labeled Get­Local­Time.
UTC
Local
FILE­TIME
Get­System­Time­As­File­Time
File­Time­To­Local­File­Time
(None)
File­Time­To­System­Time
File­Time­To­System­Time
SYSTEM­TIME
Get­System­Time
Get­Local­Time

I claimed that there was no function to complete the commutative diagram by connecting the bottom two boxes.

I was wrong, but I'm going to try to get off on a technicality.

You can connect the two boxes by calling System­Time­To­Tz­Specific­Local­Time with NULL as the time zone parameter, which means "Use the current time zone."

The same diagram as above, but there is a new arrow connecting Get­System­Time to Get­Local­Time labeled System­Time­To­Tz­Specific­Local­Time.
UTC
Local
FILE­TIME
Get­System­Time­As­File­Time
File­Time­To­Local­File­Time
(None)
File­Time­To­System­Time
File­Time­To­System­Time
SYSTEM­TIME
Get­System­Time
System­Time­To­Tz­Specific­Local­Time
Get­Local­Time

This works here because the time being converted always refers to the current time.

Here comes the technicality.

This technique doesn't work in general because System­Time­To­Tz­Specific­Local­Time uses the time zone in effect at the time being converted, whereas the File­Time­To­Local­File­Time function uses the time zone in effect right now. Furthermore, it doesn't take into account changes in daylight savings rules that may have historically been different from the current set of rules. (Though this is easily repaired by switching to System­Time­To­Tz­Specific­Local­Time­Ex.) The trick works here because the time we are converting is right now.

In other words, the more general diagram does not commute. Instead, it looks more like this:

Same as before, but this time the boxes are unlabeled, and the bottom right box is split in two. The inbound arrow from the left goes to one box and the inbound arrow from the top goes to another box. The two halves of the split boxes are marked as not equal.
UTC
Local
FILE­TIME
File­Time­To­Local­File­Time
File­Time­To­System­Time
File­Time­To­System­Time
SYSTEM­TIME
System­Time­To­Tz­Specific­Local­Time­Ex

This is why the documentation for File­Time­To­Local­File­Time tells you that if you want to get from the upper left corner to the upper right corner while accounting for daylight saving time relative to the time being converted, then you need to take the long way around.

So what we have is not so much a commutative diagram as a something like covering space: If you start at any box and travel around the diagram, you won't necessarily end up where you started. Let's start at the upper left corner for the sake of example.

Back to the four-box diagram, with empty boxes. The arrows follow a clockwise path. From the upper left, we go to the upper right via File­Time­To­Local­File­Time, then to the bottom right via File­Time­To­System­Time, then to the bottom left via Tz­Specific­Local­Time­To­System­Time­Ex, then back to the upper left via Local­File­Time­To­File­Time.
UTC
Local
FILE­TIME
File­Time­To­Local­File­Time
System­Time­To­File­Time
File­Time­To­System­Time
SYSTEM­TIME
Tz­Specific­Local­Time­To­System­Time

When you return to the upper left box, you might end up somewhere else, probably an hour ahead of or behind where you started. Each time you take a trip around the diagram, you drift another hour further away. Well, until you hit another daylight saving time changeover point.

Comments (18)
  1. steven says:

    Time gives me a headache.

  2. Stay away from the top right corner and you'll be OK.

  3. Mc says:

    We've had so much trouble with various databases / drivers messing around with our times  depending on if we're in or out of DST or not and if the date is in DST or not,  we now store our timestamps as strings.    At least then what you put in is what you then get out and 99% of the time (lol) that's what we want.   It's very rare that we actually want to account for DST.

  4. Dave says:

    So who should have been credited for the correction/clarification here? Zf-12? Maurits?

  5. Well, this is the virtual digital version of <i>Around the World in Eighty Days</i>.

  6. Anon says:

    @Fleet Comand

    Around the World in Eighty Days, Give or Take an Hour.

  7. I conclude Anon has not read Around the World in Eighty Days. I will not spoil it for them.

  8. Myria says:

    Love this, Raymond.  I was definitely wrong about this, too.

    GetSystemTimeAsFileTime + FileTimeToLocalFileTime + FileTimeToSystemTime is still correct if you want to know the local time for *right now*, though.  That is, unless you're *really* unlucky and it happens to be 2014/03/09 09:59:59 UTC when you call GetSystemTimeAsFileTime and 2014/03/09 10:00:00 UTC a few instructions later when you call FileTimeToLocalTime, and your local time zone is America/Los_Angeles.

  9. voo says:

    @Maurits: That reminds me of http://www.penny-arcade.com/…/05 ;) But agreed no reason to spoil an awesome book – probably my favorite Jules Vernes novel and imo by far the most approachable.

  10. Neil says:

    Should I let Anon know that the Titanic sinks?

    ["We started out like Romeo and Juliet, but it ended up in tragedy!" -Raymond]
  11. @Neil: Now, that was not nice. In fact, if you guys think he hasn't read Around the World, it shows you didn't get his joke: The "Give or Take an Hour" part is a component of daylight saving fiasco, which was not part of Around the World.

  12. Nectarine says:

    >In fact, if you guys think he hasn't read Around the World, it shows you didn't get his joke: The "Give or Take an Hour" part is a component of daylight saving fiasco, which was not part of Around the World.

    Amazingly, this makes sense. Fogg was a man of extreme precision. He met his wager with precision of seconds (although out of pure luck). He would be most offended to see Windows's imprecise treatment of date and time.

  13. voo says:

    @Fleet Command: Fair point, fair point!

  14. Engywuck says:

    According to Philip Jose Farmer's 'The Other Log of Phileas Fogg' Fogg had a reason to travel around the world, not just that whimsy excuse of a wager (which the man described at the beginning of the book never would have taken), plus it explains why Fogg trusted Passepartout so much and some other oddities. At the expense of introducing aliens, but that's a minor inconvenience :-)

  15. Duke of New York says:

    ***ing time zones, how do they work?

  16. Anon says:

    Well, I'm glad SOME of you got the joke, at least…

  17. JDT says:

    I live in Cambridge, England.

    One of the roads here is one-way but the direction changes depending on the time of day; the signs at either end rotate to show either "no entry" or "head straight on".

    Your bottom line in the diagram reminded me of this: "entry depending on the date".

  18. Anonymous Coward says:

    So you can in effect summarise the most important point in one sentence:

    File­Time­To­Local­File­Time does not yield the right answer.

Comments are closed.