On Nov 13, 2020, at 2:51 PM, Guy Harris <gharris@sonic.net> wrote:
On Nov 13, 2020, at 5:17 AM, Michael H Deckers <michael.h.deckers@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.