C++ timegm conversion DST to a certain timezone at

2019-03-03 09:54发布

I need an accurate conversion in my class from UTC time to local time of a given timezone, with or without DST in effect. My problem is, that when I use struct tm I have to provide the tm_isdst member, or leave it -1 to be determined automatically.

from mktime(3) - linux man page:
"The value specified in the tm_isdst field informs mktime() whether or not daylight saving time (DST) is in effect for the time supplied in the tm structure:
a positive value means DST is in effect;
zero means that DST is not in effect;
and a negative value means that mktime() should (use timezone information and system databases to) attempt to determine whether DST is in effect at the specified time.

Now here's my problem. I'm working with exchanges from all over the globe (from Chicago, New York, Sao Paolo, Melbourne, Buenos Aires, Johannesburg, Shanghai, Seoul ...). I have a table with the name of each timezone for each of the exchanges: for example Africa/Johannesburg, America/Chicago, America/Winnipeg.

The data I'm working on is expiration for some given futures and options financial instruments. The data I'm receiving is always in UTC, and I need to convert to the local time of the exchange.

Long story short, my current implementation is adding 1 extra hour to the expiration time of those assets that should not have the DST in effect (for example an instrument that expires in December and local time is America/Chicago, should add -360 minutes offset and DST 0, while another one which expires in June in the same timezone should have -360 timezone offset +60 DST offset, which would be -300 offset to the UTC timestamp. What I'm currently having the problem with, for example for the month of December I get 9:30 AM instead of 8:30 AM as the UTC timestamp contains the dst offset already.

Here is my function for doing the conversion, which is obviously broken:

#ifdef WIN32
#define timegm _mkgmtime
#endif

SimpleDateTime BusinessDateCalculator::UTC2TZ(const SimpleDateTime& utcDateTime, const int tz_utc_offset, const int tz_dst)
{
    struct tm stm;
    memset(&stm, 0, sizeof(stm));
    stm.tm_year = utcDateTime.getYear() - 1900;
    stm.tm_mon = utcDateTime.getMonth() - 1;
    stm.tm_mday = utcDateTime.getDay();
    stm.tm_hour = utcDateTime.getHour();
    stm.tm_min = utcDateTime.getMinute();
    stm.tm_sec = utcDateTime.getSecond();
    stm.tm_isdst = -1; //see note at the top of this file

    time_t tt = timegm( &stm );
    tt += (tz_utc_offset + tz_dst) * 60;

    struct tm ntm;
    memset(&ntm, 0, sizeof(ntm));
    gmtime_r( &tt, &ntm );
    return SimpleDateTime(ntm.tm_year + 1900, ntm.tm_mon + 1, ntm.tm_mday, ntm.tm_hour, ntm.tm_min, ntm.tm_sec, utcDateTime.getMillisecond());
}

Where SimpleDateTime is a date-time class with additional functionality, like conversion from different date/time formats (SQL, FIX, Timeonly, DateOnly).

I have the timezone information of a certain exchange available at all time. My question can I provide the timezone name somehow, and let the timegm perform a database search and determine whether in the America/Chicago timezone the DST is or is not in effect on 2014-12-19 and accordingly not add the extra 60 minutes to the UTC time, and on the same date for example in the southern hemisphere timezone America/Sao_Paulo it is in effect until 16 Feb 2015, and for this timestamp it should add 60 minutes to get the correct local time for the given date.

1条回答
祖国的老花朵
2楼-- · 2019-03-03 10:24

When you refer to a time zone by it's IANA identifier (eg "America/Chicago") - that already includes all of the DST information and full history of the time zone. At least, it does in the original source data in the IANA time zone database.

You mentioned Boost (in comments). While Boost does have support for these types of identifiers, it makes the mistake of assuming that they are permanently fixed in time. That is not true, as time zones of the world change their offsets and DST rules all the time. Consider that the USA changed its DST rules in 2007, and that Russia is changing its time zones significantly later this year (Oct. 2014). If you look at the Boost time zone data file, you'll see that its format strips away all of the history and just maps each identifier to a single set of rules. For this reason, I recommend you do not use Boost for local time zone conversions.

Instead, consider using ICU. Among other things, it includes time zone conversion functions, and uses the full copy of the IANA time zone database. You can read more here, and see sample code here. I'm not particularly skilled in C++, but it would appear that you can use ICU's Calendar class to project a UTC time to a local time in a particular time zone.

Another option would be to use the time zone functions built in to the GNU C library. It also uses the full time zone database. There's a simple example of converting UTC tor local time using an IANA/Olson identifier on this site, which I have also posted below:

/*
    C source code example: Convert UTC to local time zone, considering daylight
    savings. Uses mktime(), gmtime() and localtime(). Works for dates between
    years 1902 and 2037. Should compile and run with any recent GNU C if your
    tzdata is healthy. Written by Robert Larsson  http://rl.se

    Code put in public domain; do what you want with it.
*/

#include    <stdio.h>
#include    <stdlib.h>
#include    <time.h>

#define     DESTZONE    "TZ=Europe/Stockholm"       // Our destination time zone

int main(void)
{
    struct tm   i;
    time_t      stamp;                              // Can be negative, so works before 1970

    putenv("TZ=UTC");                               // Begin work in Greenwich …

    i.tm_year = 2009-1900;                          // Populate struct members with
    i.tm_mon  = 8-1;                                // the UTC time details, we use
    i.tm_mday = 29;                                 // 29th August, 2009 12:34:56
    i.tm_hour = 12;                                 // in this example
    i.tm_min  = 34;
    i.tm_sec  = 56;

    stamp = mktime(&i);                             // Convert the struct to a Unix timestamp

    putenv(DESTZONE);                               // Switch to destination time zone

    printf("UTC  : %s", asctime(gmtime(&stamp)));
    printf("Local: %s", asctime(localtime(&stamp)));

    return 0;                                       // That’s it, folks.
}
查看更多
登录 后发表回答