On 05/24/2014 05:21 PM, Eric Blake wrote: > From: Laine Stump <laine@xxxxxxxxx> > > Since there isn't a single libc API to get this value, this patch > supplies one which gets the value by grabbing current time, then > converting that into a struct tm with gmtime_r(), then back to a > time_t using mktime. > > The returned value is the difference between UTC and localtime in > seconds. If localtime is ahead of UTC (east) the offset will be a > positive number, and if localtime is behind UTC (west) the offset will > be negative. > > This function should be POSIX-compliant, and is threadsafe, but not > async signal safe. If it was ever necessary to know this value in a > child process, we could cache it with a one-time init function when > libvirtd starts, then just supply the cached value, but that > complexity isn't needed for current usage; that would also have the > problem that it might not be accurate after a local daylight savings > boundary. > > (If it weren't for DST, we could simply replace this entire function > with "-timezone"; timezone contains the offset of the current timezone > (negated from what we want) but doesn't account for DST. And in spite > of being guaranteed by POSIX, it isn't available on older versions of > mingw.) > > Signed-off-by: Eric Blake <eblake@xxxxxxxxxx> > --- > > After some more playing around, I learned we don't need localtime_r > at all: time() and mktime() both honor the current timezone, so the > original and resulting value were always the same. I also figured > out how to force a timezone with a daylight savings different than > an hour; the code still works without having to hardcode any guess > based on tm_isdst. Actually this patch doesn't work as well as an initial test might indicate. When I first reviewed it on Saturday afternoon (UTC+3), it passed the tests, but when I went back and ran it again in the morning on Monday, it failed the tests that had DST set. In the interest of getting as much testing time as possible on the code (and with DV's agreement), I disabled the failing tests and pushed the patches, then began investigating in more detail. I ran some custom-written tests based on this patch on a Fedora guest while modifying the system date and found that at different times of the day the tests that involved DST failed, yet at some times they failed (the failure times of different tests were different, and as the clock ticked forward, TZs with larger offsets would begin to succeed). I spent a significant amount of time experimenting and found that the function as written results in a *lot* of failures over the course of a year (although there are certain times of certain days when it is successful for some or all of the tests). So I tried setting gmtimeinfo.tm_isdst = -1 prior to calling mktime and Yay! It seemed to succeed for all tests on all dates within a year. Then I added a few more tests, and changed my test script to change the system to a different timezone prior to calling the test - after seeing a pattern during a couple tests, to save time I changed the test to only run through the clock on Jan 1, May 31, and Dec 31, but to do that both with the shell environment set to TZ="MST07:00MDT06:00,0/00:00:00,366/23:59:59" and to TZ="EET-02:00EEDT-03:00,0/00:00:00,366/23:59:59". (in recognition of my timezone and Eric's timezone :-) I've found that in the both cases only three of the 4 new tests I'd added will fail - these two new tests: TEST_LOCALOFFSET("VIR05:00VID04:00,0/00:00:00,366/23:59:59", ((-4 * 60) + 0) * 60); TEST_LOCALOFFSET("VIR11:00VID10:00,0/00:00:00,366/23:59:59", ((-10 * 60) + 0) * 60); will fail for: Dec 31 18:00-18:59 for MST Jan 1 from 03:00 - 03:59 for EET and this test: TEST_LOCALOFFSET("VIR02:45VID00:45,0/00:00:00,366/23:59:59", -45 * 60); fails for Dec 31 18:00-19:59 for MST Jan 1 from 03:00 - 04:59 for EET (Note that the output of the date command *always* showed "MDT" or "EEDT", indicating that the trick of forcing DST for every time of every day was working properly, at least for the time in the shell). The failure in all cases is that the offset returned is the offset that would be correct if DST hadn't been in effect. Since the method of setting permanent DST appears to work correctly, and it's happening for an entire 1-2 hour stretch, I'm guessing that the problem isn't that time() is called while DST is off and then mktime called while it is on, but something more insidious. My conclusions from all this are: 1) *definitely* we need to set gmtimeinfo.tm_isdst = -1 in virTimeLocalOffsetFromUTC(), 2) I guess we can add all the net test cases I've added that contain DST, but we should comment out those that fail on Jan 1 / Dec 31. I'll send a patch in about an hour to do that. (P.S. if you're interested in the ugly shell script I'm using to flip through the dates, let me know :-) > > src/libvirt_private.syms | 1 + > src/util/virtime.c | 45 +++++++++++++++++++++++++++++++++++++++- > src/util/virtime.h | 5 +++-- > tests/virtimetest.c | 53 ++++++++++++++++++++++++++++++++++++++++++++++++ > 4 files changed, 101 insertions(+), 3 deletions(-) > > diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms > index 25dab2d..91f13a4 100644 > --- a/src/libvirt_private.syms > +++ b/src/libvirt_private.syms > @@ -1984,6 +1984,7 @@ virTimeFieldsNow; > virTimeFieldsNowRaw; > virTimeFieldsThen; > virTimeFieldsThenRaw; > +virTimeLocalOffsetFromUTC; > virTimeMillisNow; > virTimeMillisNowRaw; > virTimeStringNow; > diff --git a/src/util/virtime.c b/src/util/virtime.c > index caa4e24..3a56400 100644 > --- a/src/util/virtime.c > +++ b/src/util/virtime.c > @@ -1,7 +1,7 @@ > /* > * virtime.c: Time handling functions > * > - * Copyright (C) 2006-2012 Red Hat, Inc. > + * Copyright (C) 2006-2014 Red Hat, Inc. > * > * This library is free software; you can redistribute it and/or > * modify it under the terms of the GNU Lesser General Public > @@ -344,3 +344,46 @@ char *virTimeStringThen(unsigned long long when) > > return ret; > } > + > +/** > + * virTimeLocalOffsetFromUTC: > + * > + * This function is threadsafe, but is *not* async signal safe (due to > + * localtime_r()). > + * > + * @offset: pointer to time_t that will be set to the difference > + * between localtime and UTC in seconds (east of UTC is a > + * positive number, and west of UTC is a negative number. > + * > + * Returns 0 on success, -1 on error with error reported > + */ > +int > +virTimeLocalOffsetFromUTC(long *offset) > +{ > + struct tm gmtimeinfo; > + time_t current, utc; > + > + /* time() gives seconds since Epoch in current timezone */ > + if ((current = time(NULL)) == (time_t)-1) { > + virReportSystemError(errno, "%s", > + _("failed to get current system time")); > + return -1; > + } > + > + /* treat current as if it were in UTC */ > + if (!gmtime_r(¤t, &gmtimeinfo)) { > + virReportSystemError(errno, "%s", > + _("gmtime_r failed")); > + return -1; > + } > + right here is where I added: gmtimeinfo.tm_isdst = -1; 1, 0, and leaving it alone all caused more failures. > + /* mktime() also obeys current timezone rules */ > + if ((utc = mktime(&gmtimeinfo)) == (time_t)-1) { > + virReportSystemError(errno, "%s", > + _("mktime failed")); > + return -1; > + } > + > + *offset = current - utc; > + return 0; > +} > +#define TEST_LOCALOFFSET(tz, off) \ > + do { \ > + testTimeLocalOffsetData data = { \ > + .zone = tz, \ > + .offset = off, \ > + }; \ > + if (virtTestRun("Test localtime offset for " #tz, \ > + testTimeLocalOffset, &data) < 0) \ > + ret = -1; \ > + } while (0) > + > + TEST_LOCALOFFSET("VIR00:30", -30 * 60); > + TEST_LOCALOFFSET("VIR01:30", -90 * 60); > + TEST_LOCALOFFSET("UTC", 0); > + TEST_LOCALOFFSET("VIR-00:30", 30 * 60); > + TEST_LOCALOFFSET("VIR-01:30", 90 * 60); > + /* test DST processing with timezones that always > + * have DST in effect; what's more, cover a zone with > + * with an unusual DST different than a usual one hour > + */ > + TEST_LOCALOFFSET("VIR-00:30VID,0,365", 90 * 60); > + TEST_LOCALOFFSET("VIR-02:30VID,0,365", 210 * 60); > + TEST_LOCALOFFSET("VIR-02:30VID-04:30,0,365", 270 * 60); Here are my new tests: TEST_LOCALOFFSET("VIR-00:30VID,0/00:00:00,366/23:59:59",((1 * 60) + 30) * 60); TEST_LOCALOFFSET("VIR-02:30VID,0/00:00:00,366/23:59:59", ((3 * 60) + 30) * 60); TEST_LOCALOFFSET("VIR-02:30VID-04:30,0/00:00:00,366/23:59:59", ((4 * 60) + 30) * 60); TEST_LOCALOFFSET("VIR-02:30VID-04:30,0/00:00:00,366/23:59:59", ((4 * 60) + 30) * 60); * TEST_LOCALOFFSET("VIR02:45VID00:45,0/00:00:00,366/23:59:59", -45 * 60); TEST_LOCALOFFSET("VIR-12:00VID-13:00,0/00:00:00,366/23:59:59",((13 * 60) + 0) * 60); * TEST_LOCALOFFSET("VIR05:00VID04:00,0/00:00:00,366/23:59:59", ((-4 * 60) + 0) * 60); * TEST_LOCALOFFSET("VIR11:00VID10:00,0/00:00:00,366/23:59:59", ((-10 * 60) + 0) * 60); (those marked with "*" fail on Dec 31 or Jan 1, depending on TZ setting of the shell that is running virtimetest) -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list