Introduction

Windows uses different time formats, with only selected conversion paths between them. This article provides comprehensive conversions between the most common ones, and a discussion why date and time are complicated and what to do to preserve your sanity. Text representation of dates and times are not covered by this article.

That was supposed to become the title for my article.
Since Joseph M. Newcomer already has an
article with that name, I'm stuck with the one you see above.

Date and Time handling is one of the areas that attracts bugs because the
complexity of the problem is often underestimated. From the
Zune bug to numerous unnamed oddities, our measurements of date and time
carry a lot of conventions that resist simple calculations and suggest
assumptions that don't always hold true. I'll start with the oddities:

Keeps the calendar day in sync with the earth day. A second is
inserted at June 30 or December 31. The last time was December 31, 2008,
23:59:60. This happens the same time worldwide, so in Japan that would be Jan 1st,
08:59:60. Yes, these are valid times. Since the underlying effects are not
predictable, leap seconds are announced 6 month in advance.

Most time APIs ignore leap seconds - i.e. continuous formats
like time_t jump back a second, or have a twice-as-long second.

Helps everyone agree that 6:30 is too early. The world is
split into time zones that roughly correlate to longitude, corrected for
political borders. That's good, otherwise it might be 6:30 in your bedroom, but
7:30 in the office already.

The "Time Zone Free" - standard time is
Universal Coordinated Time (UTC). Time Zones are described by an offset to
this time.

The Prime or Zero Meridian goes through Greenwich near
London, so London has no time zone offset to UTC. Berlin has +1 hour - that is,
it's one hour later in Berlin than in London. Some regions have half-hour
offsets (e.g. India +5 ½). China, which
geographically spans 4 time zones, just has a single time zone.

At
the opposite of the prime meridian is the dateline which, when crossed, propels
you one day into the future, or one day back. Doesn't help against the hair
loss, though.

Don't get me started on this one - suffice to say, it was suggested by an
insect collector so you get an hour of after-work daylight. Supported and
opposed for many reasons, the clock is set forward in spring by one hour, and
back again in autumn. When and at what time varies by location, and has changed
frequently. Israel settled the date and time, as well as whether have DST at all
this year, by a parliament debate. This means that static definitions of DST
rules can be outdated quickly. YOu best rely on your platforms update mechanisms
for correctness.

The UTC offset is the difference between local time and
Universal Coordinated Time (UTC): local time = UTC +
offset. UTC offset usually consists of both the time zone offset
and any additional offset like Daylight Saving Time.

All that's said here is true only for one calendar - the Gregorian Calendar,
fortunately the international de facto standard, even though other calendars are
still in common use. The Gregorian calendar was introduced in 1582 as a
correction to the then-common Julian Calendar, one of the changes being a direct
jump from October 4th to October 15th, to correct the
inaccuracies of the Julian calendar that have accumulated over the centuries. If
you want to work with dates before that, additional trouble awaits.

Some still in use calendars are the Hebrew, Persian and Hindu calendars,
featuring days of varying lengths, leap months and other features that make the
above problems look simple.

Common time span expressions like "two weeks" have different actual duration
depending on when they take place.

Around DST changes, the same local time describe two points in time, one hour
apart - and you can't say which one it means. There's an hour worth of local
time stamps that don't exist.

Most environments have problems keeping up with the changes, and accurately
store and consider previous ones, so certain calculations - especially time
spans between two points - suffer from inaccuracies.

Local vs. UTC awareness. Local timestamps are not comparable
and some values are ambiguous. UTC timestamps are hard to relate to by users. A
common mistake is to mix up local timestamp values with UTC. Document for
variables, API's and file formats whether a timestamp value is UTC or local
time.

Compare UTC times. If you want to "sort by
date", or find out which of two event took place earlier, do
store and use UTC. Even if your data occurs in a single time
zone, DST can mess up order.

Local time without location is meaningless.
I would recommend to "know" the UTC offset for every local time
you carry around. When persisting or transmitting timestamps,
include the UTC and the local offset when and where the
timestamp was generated. Associated with this are three time
representations that may be relevant:

UTC for comparison and sorting

local time of the sender - e.g. the logs of
your transatlantic partners reporting problems
"always around midnight"

local time of the receiver - e.g. looking
for an even that occurred "half an hour ago" on
the other side of the world

Do not rely on constant fixed time spans. Not
only the duration of a month varies, but also the duration of
days, hours and minutes. More exactly: the conversion between
units of timespans depends on the actual timestamp they are
applied to. This needs to be considered when allowing to enter
colloquial time spans such as "two months".

1) Smallest unit that can be represented. Methods that get the current time may rely on an underlying counter that increases in larger steps.
E.g. GetSystemTimeAsFileTime may return current time in time slice increments of
~15ms.2) Typically, the day when the calendar begins. For continuous formats, this is what the date/time represented by 0, in UTC. Note that some allow negative values that are before the epoch. 3) In a struct of 2x32 bit unsigned integer 4) Some APIs may have more stringent limits, e.g. some conversion functions don't accept negative time_t values, 64 bit time_t is frequently limited to Dec 31, 3000. 5) OLE Date/Time is a little crazy, see below.

OLE Dates (VT_DATE) could have been a great: they specify days
since the epoch (Dec 30, 1899), the fractional part giving the fraction of the
day (e.g. 0.5 for noon). The floating point allows to trade of resolution near
the epoch for long term calculations.

Like other linear formats, DST and leap seconds are glanced over. The first
can be avoided by storing only UTC, the second is a problem of all formats.
However, there are two oddities in the implementation that you need to be aware
of:

Negative values need to be split up into integer and
fractional part: e.g. -2.5 means two days back and half a day forward
from the epoch (i.e. the sign is applied to the integer part, but not the
fractional part). This makes numeric errors especially troublesome, -2 means
"two days back from the epoch", ending up at Dec 28, 1899. -1.9999999 means go
back one day and almost an entire day forward again, ending you up just a bit
before Dec 30, 1899 - almost two days apart.

The epoch was chosen to make Excel compatible with a bug in
Lotus 1-2-3, leading to some inaccurate conversions in January and February
1900. For details, See link list below.

FILETIME actually looks promising for typical needs: high resolution, a sufficient range and
very simple calculations using an int64 for conversions and time spans. Portability is good enough with the simple conversion from/to time_t.

time_t (64 bit) is default choice, but you get only one second resolution which may not be enough for some applications. It's probably the most portable.
Range is limited to Year 3000 by some API's.
Avoid 32 bit time_t's at all cost.

I would avoid SYSTEMTIME because of being a platform-dependent struct, OLE DATE/TIME – while conceptually interesting – only offers half-second resolution, has broken
arithmetic for negative values, and you have to be prepared for numeric precision issues.

Another option would be storing a string in a culture-neutral format. Requires variable length, and the extra cost of formatting and parsing may be prohibitive for large amounts of data, but you are more independent of the actual date/time API's and formats supported.

My current bet is on FILETIME. The API shows a way to pack a FILETIME and the
UTC offset into an int64 without tangible losses, allowing for rather easy
handling.

Argument converters to implement methods that accept all of the supported types(You don't really need to know them, but then, the TimeConvert API looks weird)

TimeConvert

Converts between time formats

FileTime, FileTime64

Conversion between FILETIME structure and unsigned __int64

GetUTCOffset

returns the offset between local time and UTC (in seconds)

FmtUTCOffset

Format a UTC offset as s a string (e.g. -7 or +5:30)

PackFileTime64
UnpackFileTime
GetPackedFileTimeNow

combines time and UTC offset in a 64 bit integer.
Gets current time and UTC offset as 64 bit integer

time_ref, time_const_ref

This structure acts as an argument adapter, allowing to write functions that accept references to the types listed above. It is typically used only for an argument list, you don't create instances of this type. See TimeConvert for a method that uses it.

FileTime, FileTime64

GetUTCOffset

GetUTCOffset calculates the current difference between local time and UTC. This includes both time zone and DST, if applicable.

There is no official standard method to retrieve the data, I am using the following algorithm:

get time_t as UTC

use gmtime to convert it to a struct tm

pass this struct to mktime which assumes it is a local time, subtracts UTC offset and converts back to time_t

the difference between this result and the UTC is the UTC offset, just with the wrong sign.

(I surely hope this algorithm doesn't have any problems.)

There is an annoying little problem with that, though: between getting UTC and UTC offset, the UTC offset may change. I am not sure how relevant this is in practice, I can imagine there is a small race condition during change of DST, but I am not taking any chances.

For this reason, GetUTCOffset can optionally return the UTC time for which this offset applies. To guarantee consistency, the calculation is repeated at least two times, more if the UTC offset changes between the last two calculations. There is no repetition when the time_t * pNow_utc argument is NULL.

What if you need the current time and the UTC offset, but in a different format? Use a loop, as demonstrated here for SYSTEMTIME:

The worst was not the actual conversions but writing the tests, figuring out reliable conversion paths, and checking unreliable ones against them. This is the conversion matrix I used:

Conversion Matrix

The following conversion matrix was used. green indicates the safe conversions (simple copy, or provided by a system library), red indicates custom calculations. The conversions between the continuous formats (time_t, FILETIME, OLE DATE / TIME) are linear, i.e.timeB = timeA * factor + offset. Thus, checks were made at two points for each conversion.

This code was triggered by running into a nasty bug that a unit test would have uncovered only in one half of each year (settings the isdst member of a struct tm).

January 2011 - First release

November 2011 - fixed an incorrect value in the text, added
support for UTC offset

December 2011 - cleaned up text flow a bit

April 2012 - what happens if you let a coder deal with half a dozen time formats? He
invents a new one!
Added PackFileTime64, UnpackFileTime, GetPackedFileTimeNow

In Closing

Since this is a very dry topic, I give you a quote about time from a movie I happen to like (suitably redacted for international audiences).

Let me explain a couple of things.

Time is short. That's the first thing.

For the weasel, time is a weasel.For the hero, time is heroic.If you're gentle, your time is gentle.If you're in a hurry, time flies.Time is a servant if you are its master.Time is your god if you are its dog.We are the creators of timethe victims of timeand the killers of time.

Time is timeless. That's the second thing.

You are the clock, Cassiel.From: Faraway so close by Wim Wenders, 1993

Share

About the Author

Peter is tired of being called "Mr. Chen", even so certain individuals insist on it. No, he's not chinese.

Peter has seen lots of boxes you youngsters wouldn't even accept as calculators. He is proud of having visited the insides of a 16 Bit Machine.

In his spare time he ponders new ways of turning groceries into biohazards, or tries to coax South American officials to add some stamps to his passport.

Beyond these trivialities Peter works for Klippel[^], a small german company that wants to make mankind happier by selling them novel loudspeaker measurement equipment.

Where are you from?[^]Please, if you are using one of my articles for anything, just leave me a comment. Seeing that this stuff is actually useful to someone is what keeps me posting and updating them.Should you happen to not like it, tell me, too

I had to calculate the timezone offset for some timestamps, and thought it would be easy to calculate using gmtime and localtime, but I quickly realized that it gets tricky with the international date line.
your GetUTCOffset makes it so easy!

Using a high performance counter for time spans: Acquiring high-resolution time stamps[^] - however, they are not synchronized with any external time source, and there is no resolution guarantee (other then "the best you can get").

Time handling is a difficult topic and it gets even tougher when working with data stored (and time stamped) in a different time zone. Correctly converting the time while taking the Daylight Saving properties into account requires additional thought.

I like the limits of time_t (64 bits). It is always good to have a smile.
Nice quote about time as well.

BTW, +5 for the article

Regards.
--------
M.D.V.

If something has a solution... Why do we have to worry about?. If it has no solution... For what reason do we have to worry about?
Help me to understand what I'm saying, and I'll explain it better to you
Rating helpfull answers is nice, but saying thanks can be even nicer.

Great job! And I totally agree, that time is much more complex than many people think.

I faced many time problems on my last assignement where I had to handle temperature loggings with timestamps in local time including DST. So there has been a double hour in spring and a missing hour in fall. And the programmer of the temeprature logging system doesn't wanted to use UTC. He found that to "complicated" and was proud on his algorithem to detect the DST "switch" date.

I fought with time calculations for 12 months, basically 3 cycles of switching with DST for some device management software.

There were always other little gotchas, and it didn't help that the software was based on a home grown implementation of the standard C time libraries where the time_t was defined as an unsigned value.

Your article thoroughly describes just about every issue that I had to tackle, except I was able to stay far away from OLE Date Time struct.

This should be a great reference for anyone trying to work with timestamps on multiple computers across multiple timezones.

The purpose of this is not to store times, but to only provide conversions. That's why it's only a function (it does not have any state). I work with quite a few different libraries that use different formats.

I am using `time_ref` as parameter for other utilities (such as FmtTime) so I can pass any of the supported types. The implementation uses TimeConvert to work with a suitable format.

trotwa wrote:

You dropped the microseconds in your function.

For which conversion?
Many formats don't have microseconds, so they can't preserve it. Also, OLE Date/Time is defined to have 0.5s resolution (since resolution decays the furhter you get away from the epoch).

Other than that, you should consider having tables with borders, instead of giving jaggy look! Use bullets when you are making points. In my opinion, whatever you say, say in casual manner, not in documentation manner. Give more of code samples. And no, please dont put images where proper table/code should be pasted.

I got chasticed by Shog9 for using to many bulleted lists in another article already, so I have to tread a fine line

Regarding formatting: I already had some trouble with the formatting, correcting tags by hand. I haven't figured out how to import the additional styles in the submission wizard, that's why tables go with the defaults from the style sheet (Maybe I should send the original HTML to an editor - it looks a tad better).

I donÄt expect this to attract much attention anyway - but it might help a poor wretched soul or two one day.