Domanda

I am having to deal with some time conversions in my application. I would like to stick to using standard library functions as much as possible. Right now I am using a time_t structure as my system time base. However, some devices can sync time to my device, and that time may or may not be UTC. Also, my device will sync time to another device and that time WILL always be UTC.

Anyway, I can ask the user what the time zone is of the time that is being synced to my device and whether or not they use DST. When my device gets a time sync I can use mktime to directly generate my time stamp (my device system time needs to match the time they are syncing for time stamping purposes, otherwise I have to constantly do conversions), and then from that I can use gmtime() to get the UTC time if I know my time sync came from a non-UTC source. The trouble is by default localtime() and gmtime() are going to return the same value because the library is going to by default think it is in straight UTC time and there are not DST or timezone offsets.

So, I think the way to deal with this is to implement and overwrite the library __getzone function.

From page 106 of EW430_CompilerReference.pdf

To make the __time32, __time64, and date functions work, you must implement the functions clock, __time32, __time64, and __getzone. Whether you use __time32 or __time64 depends on which interface you use for time_t, see time.h, page 304.

...

The default implementation of __getzone specifies UTC (Coordinated Universal Time) as the time zone.

Q1: Am I on the right track in my reasoning, that the best way to do what I want is to implement this __getzone function?

The reason I am balking is because the value returned by __getzone is an oddball string w/ formatting like this:

:[XXX[:YYY[:NNN[:DST[:DST ...]]]]]

Where XXX is the standard timezone name (e.g. GMT-5 for EST), YYY is the daylight savings timezone name (e.g. GMT-4 for EST), NNN is a numeric offset from UTC in the form HHMM (and could have a - symbol)., and then DST specifies a string of options for daylight savings time rules that has its own irritating formatting.

Anyway, this should be pretty straight forward for me right now because I am only worried about Canada and the US who have the same DST rules.

Q2: Does anyone have any sample code for forming that string so I can check my understanding of this?

È stato utile?

Soluzione

Here is my implementation of __getzone(). So now my system time base will be UTC. When the user configures my system I ask them what their local time if the time source is NOT providing UTC. Then when they supply a time sync to my system, the time they supply will get converted to UTC by a call to MKTIME (which will take into account DST rules). Then when the time is rendered back to the user it will be done through a call to localtime().

Another thing we learned in the process of implementing this is that IAR's implementation of MKTIME() will call __getzone(), but the DST rules will not be considered unless you set tm_isdst to '-1'. -1 makes the call to MKTIME() figure out whether to apply DST based on the rule.

/*!
* \brief Overrides default library function __getzone to support different time
* zones and DST rules.
* \returns Pointer to a const string containing the timezone + dst rules
*
* This function supports all time zones and DST rules for the U.S. and Canada.
*
* \par IAR Notes
* The return value should be a string on the following form:
* \code
* :[XXX[:YYY[:NNN[:DST[:DST ...]]]]]
* \endcode
* \par
* Where \b XXX is the standard time-zone name, \b YYY is the daylight
* savings time-zone name, \b NNN is the time zone offset, and the DSTs
* are the daylight savings time rules. Daylight savings time will add
* one hour to the normal time.
* \par
* The time zone offset \b NNN is specified as a number relative to UTC,
* possibly negative (east is positive), on the format HHMM, where HH
* is hours and MM is minutes.
* \par
* The DSTs specifes a set of rules for how daylight savings time is
* applied. The rules must be sorted in increasing date order starting
* from the earliest date. The first rule for a specific year will
* enable DST, the next will disable it, and so on. Each rule is on
* the following form:
* \code
*   [(YYYY)]MMDD[HH][-W|+W]
* \endcode
*
* * \b (YYYY) is the first year the daylight savings rule was applied.
*      It is optional. If not specified it will default to the same
*      year as the previous rule or zero if no previous rule.
* * \b MM is the month number (1-12).
* * \b DD is the day of the month (1-31).
* * \b HH is the hour number in a 24-hour day (optional, defaults to 0).
* * \b +/-W specifies the day of the week the rule takes effect (where
*      Sunday = 0, Monday = 1, etc). +W means that the rule applies
*      to the first such day on or after the specified date and -W
*      strictly before the date. If this is not specified, the rule
*      will take effect on the exact date, regardless of the day of
*      the week.
*
* \par Example
* U.S. Eastern Standard time is UTC -5. Eastern Daylight time is UTC -4.
* Daylight time goes into affect on the second sunday of March at 2:00AM local
* time. Daylight time ends on the first sunday of November at 2:00AM local
* time. The law that defines this went into affect in 2007.
* Therefore here is how the DST string is constructed:
* | \| | STD Time | \| | DST Time | \| | UTC Offset | \| | DST Rule Year | Month DST Starts | Day DST Starts | Hour DST Starts | Day of Week | \| | Month DST Ends | Day DST Ends | Hour DST Ends | Day of Week |
* |----|----------|----|----------|----|------------|----|---------------|------------------|----------------|-----------------|-------------|----|----------------|--------------|---------------|-------------|
* | :  |  XXX     | :  | YYY      | :  | NNN        | :  | (YYYY)        | MM               | DD*            | HH              | +/-W**      | :  | MM             | DD           | HH            | +/-W        |
* | :  |  GMT-5   | :  | GMT-4    | :  | -0500      | :  | (2007)        | 03               | 08             | 02              | +0          | :  | 11             | 01           | 02            | +0          |
* - * An 8 for the day means that DST will start around the 8th day of the
*   month. Or that the +/-W parameter is relative to the 8th day of the month.
* - ** A + here means that the DST rule will start \b on or \b after the
*   previously specified day (the 8th). 0 means that it should happen on a
*   sunday. Therefore if the 8th is a sunday (and the 8th cannot be the first
*   sunday of the month) then the rule will take affect on that day - or it
*   will happen on the very next sunday.
* \par
* Result:
* \code
* :GMT-5:GMT-4:-0500:(2007)030802+0:110102+0
* \endcode
*
* \sa
* - time_zones - Supported time zones
*/
const char8_t * __getzone(void)
{
    const char8_t *current_zone = NULL;
    static const char8_t dst_time_zones[][50] =
    {
        // UTC time
        ":GMT+0:GMT+0:0000:0",
        // Newfoundland Standard Time UTC – 3:30
        ":GMT-3:GMT-2:-0330:(2007)030802+0:110102+0",
        // Atlantic Standard Time, UTC – 4
        ":GMT-4:GMT-3:-0400:(2007)030802+0:110102+0",
        // Eastern Standard Time, UTC – 5
        ":GMT-5:GMT-4:-0500:(2007)030802+0:110102+0",
        // Central Standard Time, UTC – 6
        ":GMT-6:GMT-5:-0600:(2007)030802+0:110102+0",
        // Mountain Standard Time, UTC – 7
        ":GMT-7:GMT-6:-0700:(2007)030802+0:110102+0",
        // Pacific Standard Time, UTC – 8
        ":GMT-8:GMT-7:-0800:(2007)030802+0:110102+0",
        // Alaska Standard Time, UTC – 9
        ":GMT-9:GMT-8:-0900:(2007)030802+0:110102+0",
        // Hawaii-Aleutian Standard Time, UTC – 10
        ":GMT-10:GMT-9:-1000:(2007)030802+0:110102+0"
    };

    static const char8_t std_time_zones[][20] =
    {
        // UTC time
        ":GMT+0:GMT+0:0000",
        // Newfoundland Standard Time UTC – 3:30
        ":GMT-3:GMT-2:-0330",
        // Atlantic Standard Time, UTC – 4
        ":GMT-4:GMT-3:-0400",
        // Eastern Standard Time, UTC – 5
        ":GMT-5:GMT-4:-0500",
        // Central Standard Time, UTC – 6
        ":GMT-6:GMT-5:-0600",
        // Mountain Standard Time, UTC – 7
        ":GMT-7:GMT-6:-0700",
        // Pacific Standard Time, UTC – 8
        ":GMT-8:GMT-7:-0800",
        // Alaska Standard Time, UTC – 9
        ":GMT-9:GMT-8:-0900",
        // Hawaii-Aleutian Standard Time, UTC – 10
        ":GMT-10:GMT-9:-1000"
    };

    switch(get_config()->time_zone)
    {
        case NST:
        {
            if(get_config()->b_dst)
            {
                current_zone = dst_time_zones[NST];
            }
            else
            {
                current_zone = std_time_zones[NST];
            }
        }
        break;

        case AST:
        {
            if(get_config()->b_dst)
            {
                current_zone = dst_time_zones[AST];
            }
            else
            {
                current_zone = std_time_zones[AST];
            }
        }
        break;

        case EST:
        {
            if(get_config()->b_dst)
            {
                current_zone = dst_time_zones[EST];
            }
            else
            {
                current_zone = std_time_zones[EST];
            }
        }
        break;

        case CST:
        {
            if(get_config()->b_dst)
            {
                current_zone = dst_time_zones[CST];
            }
            else
            {
                current_zone = std_time_zones[CST];
            }
        }
        break;

        case MST:
        {
            if(get_config()->b_dst)
            {
                current_zone = dst_time_zones[MST];
            }
            else
            {
                current_zone = std_time_zones[MST];
            }
        }
        break;

        case PST:
        {
            if(get_config()->b_dst)
            {
                current_zone = dst_time_zones[PST];
            }
            else
            {
                current_zone = std_time_zones[PST];
            }
        }
        break;

        case AKST:
        {
            if(get_config()->b_dst)
            {
                current_zone = dst_time_zones[AKST];
            }
            else
            {
                current_zone = std_time_zones[AKST];
            }
        }
        break;

        case HAST:
        {
            if(get_config()->b_dst)
            {
                current_zone = dst_time_zones[HAST];
            }
            else
            {
                current_zone = std_time_zones[HAST];
            }
        }
        break;

        case UTC:
        default:
            current_zone = std_time_zones[UTC];
        break;
    }

    return current_zone;
}

Altri suggerimenti

The Ukraine rule above is not quite right as it will not work with Sundays that fall on the 31st. If you read the rules in the getzone.c -W means the Sunday strictly before this date.

+/-W specifies the day of the week the rule takes effect (where
    Sunday = 0, Monday = 1, etc). +W means that the rule applies
    to the first such day on or after the specified date and -W
    strictly before the date. If this is not specified, the rule
    will take effect on the exact date, regardless of the day of
    the week.

The correct rule would be as follows

char const * __getzone()
{
    return ":GMT+2:GMT+3:0200:(1996)032503+0:102504+0";
  // Ukraine; rule: (last Sun (March | October) )
}
char const * __getzone()
{
        return ":GMT+2:GMT+3:0200:(1996)033103-0:103104-0";



    // Ukraine; rule: (last Sun (March | October) )
}

For Germany you can use:

char const * __getzone() {
  return ":GMT+1:GMT+2:0100:032502+0:102502+0";
}

Either the Ukraine one is wrong or mine, but i tested mine with an actual device and got the right results. Here my Tests:

//Forward
time_t ts = 1490489997L;//26.03.2017-01:59:57 3 seconds before dst
struct tm* pre = localtime(&ts);

time_t after = ts + 5L;//wait 5 seconds -> 26.03.2017-03:00:02
struct tm* post = localtime(&after);

//Backward
time_t ts = 1509238797L;//29.10.2017-02:59:57 3 seconds before dst
struct tm* pre = localtime(&ts);

time_t after = ts + 5L;//wait 5 seconds -> 29.10.2017-02:00:02
struct tm* post = localtime(&after);

Best regards Michael

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top