[tz] What's "right"?
Guy Harris
gharris at sonic.net
Sat Nov 14 00:20:30 UTC 2020
On Nov 13, 2020, at 2:51 PM, Guy Harris <gharris at sonic.net> wrote:
> On Nov 13, 2020, at 5:17 AM, Michael H Deckers <michael.h.deckers at googlemail.com> wrote:
>
>> The computing interfaces for tzdb data typically allow for the
>> conversion between datetimes of UTC and datetimes of any tzdb Zone
>> time.
>
> The computing interfaces, with the "posix" values, allow for the conversion of a value expressed as POSIX "seconds since the Epoch" - i.e., as a count of seconds, *excluding* leap seconds, that have elapsed since the Epoch - to a date and time, expressed as year/month/day/hour/minute/second, either as UTC (gmtime()) or as local time in any tzdb region (localtime()).
If you want to convert between year/month/day/hour/minute/second as UTC and year/month/day/hour/minute/second as local time, do:
struct tm *
utc_to_local(struct tm *utc)
{
time_t tmp;
tmp = {do the calculation based on the values in *utc};
return localtime(&tmp);
}
to go from UTC to local time and
struct tm *
local_to_utc(struct tm *local)
{
time_t tmp;
tmp = mktime(local);
if (tmp == -1)
return NULL; /* no can do */
return gmtime(&tmp);
}
to go from local time to UTC.
Note that these will work correctly for UTC times with second = 60 - i.e., for leap seconds - only if 1) you are using the "right" tzdb files and 2) {do the calculation based on the values in *utc} involves using timegm(), if available, using the leap second information from the "right" tzdb files. Otherwise, Weird Stuff may happen if you try to convert, for example, 2016-12-31 23:59:60 UTC to local time.
It *also* requires that
For example, on Ubuntu 20.04, which has the "right" values, the following program:
/*
* Yes, I want all the APIs.
*/
#define _DEFAULT_SOURCE
#define _XOPEN_SOURCE
#include <stdio.h>
#include <time.h>
static struct tm *
utc_to_local(struct tm *utc)
{
time_t tmp;
tmp = timegm(utc);
return localtime(&tmp);
}
static struct tm *
local_to_utc(struct tm *local)
{
time_t tmp;
tmp = mktime(local);
if (tmp == -1)
return NULL; /* no can do */
return gmtime(&tmp);
}
int
main(int argc, char **argv)
{
struct tm input;
struct tm *output;
char bufin[1024+1], bufout[1024+1];
if (argc != 2) {
fprintf(stderr, "Usage: timetest <time>\n");
return 1;
}
if (strptime(argv[1], "%Y-%m-%d %H:%M:%S", &input) == NULL) {
fprintf(stderr, "timetest: \"%s\" is not a valid time\n",
argv[1]);
return 2;
}
input.tm_isdst = -1;
strftime(bufin, sizeof bufin, "%Y-%m-%d %H:%M:%S", &input);
printf("%s is converted to %04d-%02d-%02d %02d:%02d:%02d\n",
argv[1],
input.tm_year + 1900,
input.tm_mon + 1,
input.tm_mday,
input.tm_hour,
input.tm_min,
input.tm_sec);
output = utc_to_local(&input);
strftime(bufin, sizeof bufin, "%Y-%m-%d %H:%M:%S", &input);
strftime(bufout, sizeof bufout, "%Y-%m-%d %H:%M:%S", output);
printf("%s in UTC is %s in local time\n", bufin, bufout);
if (strptime(argv[1], "%Y-%m-%d %H:%M:%S", &input) == NULL) {
fprintf(stderr, "timetest: \"%s\" is not a valid time\n",
argv[1]);
return 2;
}
input.tm_isdst = -1;
strftime(bufin, sizeof bufin, "%Y-%m-%d %H:%M:%S", &input);
printf("%s is converted to %04d-%02d-%02d %02d:%02d:%02d\n",
argv[1],
input.tm_year + 1900,
input.tm_mon + 1,
input.tm_mday,
input.tm_hour,
input.tm_min,
input.tm_sec);
output = local_to_utc(&input);
strftime(bufin, sizeof bufin, "%Y-%m-%d %H:%M:%S", &input);
strftime(bufout, sizeof bufout, "%Y-%m-%d %H:%M:%S", output);
printf("%s in local time is %s in UTC\n", bufin, bufout);
return 0;
}
produces the following output with the "posix" files, in my time zone:
$ ./timetest "2016-12-31 23:59:60"
2016-12-31 23:59:60 is converted to 2016-12-31 23:59:60
2017-01-01 00:00:00 in UTC is 2016-12-31 16:00:00 in local time
2016-12-31 23:59:60 is converted to 2016-12-31 23:59:60
2017-01-01 00:00:00 in local time is 2017-01-01 08:00:00 in UTC
and the following output with the "right" files, in my time zone:
$ TZ=right/America/Los_Angeles ./timetest "2016-12-31 23:59:60"
2016-12-31 23:59:60 is converted to 2016-12-31 23:59:60
2016-12-31 23:59:60 in UTC is 2016-12-31 15:59:60 in local time
2016-12-31 23:59:60 is converted to 2016-12-31 23:59:60
2017-01-01 00:00:00 in local time is 2017-01-01 08:00:00 in UTC
Note that either gmtime() and localtime(), if the "posix" files are being used, will "fix" the members of the input struct tm, but will *not* do so if the "right" files are being used.
More information about the tz
mailing list