Creating a CTime @ 2am doesn't return the correct value (Windows CE, C++)

181 views Asked by At

This goes against all reasonnable logic but when I try to construct a CTime containing '2' for hours, it will return a 1 instead of 2 in the hour field. Only for this hour value. Platform is WindowsCE 6.0, plain c++ (VS2005)

void main(void)
{
    CTime       myTime;
    int         myMonth;

    for(myMonth=1; myMonth < 13; myMonth++)
    {
        myTime = CTime(2017, 03, 12, myMonth, 0, 0, 0); 
        printf("Month: %d", myMonth);
        printf("\r");
        printf("CTime: %d, %d, %d, %d, %d, %d", myTime.GetYear(), myTime.GetMonth(), myTime.GetDay(), myTime.GetHour(), myTime.GetMinute(), myTime.GetSecond());
        printf("\r");
    }
}

Month: 1

CTime: 2017, 3, 12, 1, 0, 0

Month: 2

CTime: 2017, 3, 12, 1, 0, 0 // !!!

Month: 3

CTime: 2017, 3, 12, 3, 0, 0

Month: 4

CTime: 2017, 3, 12, 4, 0, 0

Month: 5

CTime: 2017, 3, 12, 5, 0, 0

Month: 6

CTime: 2017, 3, 12, 6, 0, 0

Month: 7

CTime: 2017, 3, 12, 7, 0, 0

Month: 8

CTime: 2017, 3, 12, 8, 0, 0

Month: 9

CTime: 2017, 3, 12, 9, 0, 0

Month: 10

CTime: 2017, 3, 12, 10, 0, 0

Month: 11

CTime: 2017, 3, 12, 11, 0, 0

Month: 12

CTime: 2017, 3, 12, 12, 0, 0

Adding minutes and seconds works but still, hour is not good.

nDST can be -1, 0 or 1, or omitted (-1 by constructor default) it won't change anything, hour will still be put to 1 if entered at 2.

Quite frustrating.

Also, i tried something else:

CTime    myCurrentTime = CTime::GetCurrentTime();   

And guess what ? If it is indeed 2am, this constructor works correctly and put '2' in the hours.

-> I am currently scanning a DST transition table i built and I need to construct a time reference to it compare against current time and decide if i take the current entry in my table or i skip to the next.

Using any external addon libraries is not a solution because of memory footprint. I'm stucked with CE 6.0 and the board it was designed on 10 years ago. It would be nice to have a 'vanilla' c++ solution.

2

There are 2 answers

3
Lightness Races in Orbit On

From the documentation:

CTime( int, int, ...);
Constructs a CTime object from local time components with each component constrained to the following ranges …

On the night of the 12th March 2017, as the clock hits 2am, DST kicks in and your clock actually goes back to 1am. The function is interpreting your input as 2am winter time (because it doesn't know whether you mean DST or not, but has to pick one somehow!), but when you go to GetHour() the hour component of the resulting date, it's now 1am ... because you're in summer time.

This is why working in local times is a nightmare! Try to find a UTC alternative if you can. Standard C++ has some quite nice time handling functions that you may be able to use instead of proprietary Microsoft stuff.

1
Howard Hinnant On

Actually (assuming a US timezone) The clock jumps forward, not back at 2017-03-12 02:00:00, making CTime's output quite curious.

Assuming a modern compiler (e.g. VS-2013 or better), here is a C++11/14 library which can be used to clarify the situation.

#include "chrono_io.h"
#include "tz.h"
#include <iostream>
#include <exception>

int
main()
{
    try
    {
        using namespace date;
        using namespace std::chrono_literals;;
        for (auto myHour = 1h; myHour < 13h; ++myHour)
        {
            auto myTime = make_zoned(current_zone(),
                                     local_days{2017_y/mar/12} + myHour);
            std::cout << "Hour: " << myHour << '\n';
            std::cout << "Time: " << myTime << '\n';
        }
    }
    catch (const std::exception& e)
    {
        std::cout << e.what() << '\n';
    }
}

I've tried to keep the structure of the original program, but I've changed a few misleading variable names and labels.

The output of this program (running on computer with a US timezone) is:

Hour: 1h
Time: 2017-03-12 01:00:00 EST
2017-03-12 02:00:00 is in a gap between
2017-03-12 02:00:00 EST and
2017-03-12 03:00:00 EDT which are both equivalent to
2017-03-12 07:00:00 UTC

The "gap" mentioned in the error message above is a half-open range of local time including the begin, but excluding the end ([2:00:00, 3:00:00)).

The error message says what's going wrong, but not how to fix it if you don't want an exception. If you don't want an exception you can slightly alter the above program to "pre-decide" how you want to handle discontinuities in local time.

If when local time is ambiguous (occurs twice on the same date), you can specify that you want to the earlier or later time. This happens (for example) when transitioning from daylight saving to standard. When transitioning from standard to daylight saving, local time jumps forward (typically skipping an hour). When this happens, both sides of the gap correspond to the same instant in time. And since the gap is half-open, local time is said to jump (for example) from 1:59:59.999999 to 3:00:00.

In the "spring-forward" scenario you can specify that you want either the "earlier" or "later" mapping and both map to the UTC-instant that lies on both sides of the gap, which when converted back to local time is 3:00:00 in this example.

In code this looks like:

#include "chrono_io.h"
#include "tz.h"
#include <iostream>
#include <exception>

int
main()
{
    try
    {
        using namespace date;
        using namespace std::chrono_literals;;
        for (auto myHour = 1h; myHour < 13h; ++myHour)
        {
            auto myTime = make_zoned(current_zone(),
                                     local_days{2017_y/mar/12} + myHour,
                                     choose::earliest); // only change
            std::cout << "Hour: " << myHour << '\n';
            std::cout << "Time: " << myTime << '\n';
        }
    }
    catch (const std::exception& e)
    {
        std::cout << e.what() << '\n';
    }
}

and outputs:

Hour: 1h
Time: 2017-03-12 01:00:00 EST
Hour: 2h
Time: 2017-03-12 03:00:00 EDT
Hour: 3h
Time: 2017-03-12 03:00:00 EDT
Hour: 4h
Time: 2017-03-12 04:00:00 EDT
Hour: 5h
Time: 2017-03-12 05:00:00 EDT
Hour: 6h
Time: 2017-03-12 06:00:00 EDT
Hour: 7h
Time: 2017-03-12 07:00:00 EDT
Hour: 8h
Time: 2017-03-12 08:00:00 EDT
Hour: 9h
Time: 2017-03-12 09:00:00 EDT
Hour: 10h
Time: 2017-03-12 10:00:00 EDT
Hour: 11h
Time: 2017-03-12 11:00:00 EDT
Hour: 12h
Time: 2017-03-12 12:00:00 EDT

Now 3am is repeated instead of 1am (CTime), but at least you know why, and get a detailed error message about it by default.

Dealing with all the chicanery of local time can be a tricky business, but this library can help you deal with it. And by-the-way we've got a leap second coming up in several days. This library can also deal with that detail should it be important to your time computations.

Video introduction: https://www.youtube.com/watch?v=Vwd3pduVGKY