views:

185

answers:

3

In my application I need to calculate shifts using a pattern described in a file. Recently, at one of my customers the application was hanging because of the following reason:

If you fill in a 'struct tm' with the exact moment at the end of the wintertime (non-DST) _mktime seems to return an incorrect result.

The code looks like this:

struct tm tm_start;
tm_start.tm_mday  = startday;
tm_start.tm_mon   = startmonth-1;
tm_start.tm_year  = startyear-1900;
tm_start.tm_hour  = starthour;
tm_start.tm_min   = startmin;
tm_start.tm_sec   = startsec;
tm_start.tm_isdst = -1;             // Don't know if DST is active at this moment

_int64 contTime = _mktime64(&tm_start);

Suppose that there is a switch from winter- to summer-time on the 5th of April at 2:00. In practice, this means we have the following time-moments:

5 April, 1:58
5 April, 1:59
5 April, 3:00

Since I don't know in the application when DST starts or ends (do I really want to know this?) I pass the date "5 april, 2:00" to _mktime64 using the code shown above.

I would expect _mktime64 to give me the time_t value that corresponds to 5 April, 3:00 (which is exactly the same moment as 5 April, 2:00).

However, this isn't what's happening. _mktime64 changes tm_start to 5 April, 1:00 and returns the corresponding time_t value. Which means that I get a totally different moment. (in fact: every moment between 2:00 and 3:00 causes _mktime64 to return a moment between 1:00 and 2:00)

I thought this was a bug in Visual Studio 2005, but apparently Visual Studio 2010 (Release Candidate) has the same behavior.

The problem appears on both XP and Windows7 (didn't check Vista).

Is this a known bug? Or are there other tips to tackle this problem?

+1  A: 

Trying to convert 5 April, 2:30 to a time_t is similar to trying to convert 29 February 2011 to a time_t. In neither case do you expect a valid result, because the input is not a valid date. On April 5th, even 2:00 doesn't exist. 1:59.59 is followed directly by 3:00:00

MSalters
I already figured that out. Question is how to check for this.Strange enough, mktime is smart enough to convert 29 february to 1 march, and even 32 february to 4 march (and I think this is even guaranteed), but not 2:00 to 3:00. Why?
Patrick
I just read the POSIX description (http://www.opengroup.org/onlinepubs/9699919799/functions/mktime.html). It does allow values outside the "normal" range, but doesn't specify the result. It's a rather poor spec, yes. It also states that `tm_yday` is ignored, and then goes on to describe how it's actually calculated - using `tm_yday` and ignoring `tm_mon`, `tm_mday` etc!
MSalters
+3  A: 

This is an inevitable consequence of working with local time instead of UTC. The end of DST is a doozy too, timestamps are ambivalent during the change-over since local time matches two times.

This is easily solved by working with UTC instead, that's what the operating system does as well. Check your CRT implementation, something like _gmtime64() or the native GetFileTime().

Hans Passant
You're right, but only regarding all internal calculations. Name it as you wish: UTC, time_t, ... this is the type to use for internal calculations. But if my customer wants to indicate that there is a working time between 8:00 and 16:00, he really means local time, not UTC. Also if you're application has to accept dates/times as user-input you have to deal with local time.
Patrick
@Patrick: I don't understand how your code could hang if all your internal time processing is in UTC. Convert from local time to UTC at user input, convert back at output.
Hans Passant
It's a bit difficult to explain but it has to do with processing 'shifts'. The shifts are described in terms of hours, excluding day information (e.g. shift from 2:00 to 3:00, every day). This requires me to convert this time to a UTC moment, and this for every day. If the end of a shift then falls in the 'missing hour' and the CRT returns one hour less, and the end of the shift then falls before the beginning of the shift, the next shift it will find is exactly the same shift, and it hangs. OK, I could solve the problem by adding some checks, but the strangeness of mktime surprised me.
Patrick
Try planning a meeting in Outlook at the night the DST goes into effect. Choose new appointment, choose 1:00 as start. Then choose 3:00 as end. In the dropdown, Outlook 2007 tells you this is a meeting of 2 hours (while in reality, there is only 1 hour there). So even Microsoft/Office is unable to do handle it perfectly.
Patrick
+2  A: 

I guess it simply doesn't know how to correctly handle the invalid time you pass in. And it is certainly invalid for your time zone - you're thinking of it as '1 minute after 1:59 in the winter' implying that you wanted it to return '3:00' with DST back in operation, which sounds reasonable. However it could equally well treat it as 'an hour before 3:00 in the summer' meaning it must have been from the winter period and thus returns '1:00'. Thus I don't think it's a bug - it's more likely to be undefined behaviour about how to handle a non-existent time.

I expect the safest way to approach this is to use UTC throughout as then there are no ambiguities. Obviously this may not map cleanly to your application domain, but such is the murky world of pushing time backwards and forwards!

Kylotan
+1 for the original view of thinking that 2:00 is 1 hour before 3:00. Indeed this yields 1:00. Still a strange result, but at least it's an original point of view.
Patrick