Le 13/05/2017 à 08:09, Willy Tarreau a écrit : > Hi Benoît, > > On Sat, May 13, 2017 at 07:32:10AM +0200, Benoît GARNIER wrote: >> Le 12/05/2017 à 15:54, Willy Tarreau a écrit : >>> Hi Benoît, >>> >>> On Thu, May 04, 2017 at 08:50:33AM +0200, Benoît GARNIER wrote: >>> (...) >>>> If you do the following operation : time_t => localtime() => struct tm >>>> => timegm() => time_t, your result will be shift by the timezone time >>>> offset (but without any DST applied). >>>> >>>> Technically, if you live in Great Britain, the operation will succeed >>>> during winter (but will offset the result by 1 hour during summer, since >>>> DST is applied here). >>> So in short you're saying that we should always use mktime() instead ? >>> >>> Willy >> No, not at all !!! To sum up, these are the basic functions to work with >> time : >> >> - time() return a time_t which is timezone agnostic since it's just a >> precise moment in time (it represents the same moment for everybody) >> >> - localtime() takes this time_t and build a representation of this time >> in the current time zone (struct tm) >> >> - mktime() take a struct tm representing a specific time in the current >> timezone et return a time_t >> >> gmtime() and timegm() are the same as localtime() and mktime() but will >> ignore the timezone and DST: they only work with UTC time. >> >> So you can use timegm() on a struct tm only if you know that struct tm >> represents a GMT time (for example if it was build with gmtime()). >> >> Similarly, using mktime() is only valid if this struct tm represents the >> time in the current time zone (i.e. if it was build with localtime() >> with the same timezone). >> >> For example if you parse a log file with GMT time in it you'll use >> timegm() to build a time_t representing the precise time of the log. >> >> If you parse a file with local time in it, you'll use mktime() but >> you'll also have to know what was the timezone used to build it. >> >> 1) Time zone agnostic: time() >> 2) Current time zone: localtime() and mktime() >> 3) UTC time: gmtime() and timegm() >> >> As a rule of thumb, you cannot mix functions in categories 2 and 3. > OK thank you, that's perfectly clear now and it makes sense. Thierry > told me that he purposely used timegm() because he wanted UTC. Man > pages recommend not to use it because it's obsolete and suggest to > use setenv(TZ)+tzset()+mktime() !!! It's amazing to read something > that stupid in man pages written 10 years after everyone started to > write threaded applications! I found that in practice many people > use a hand-written timegm() function which does all the computation > by hand, just like those of us who have known MS-DOS used to do 30 > years ago, so I think we'll have to go down that route for a more > portable implementation :-/ At least this will also save us from > accidently using implementations where timegm() is a wrapper on > setenv+tzset+mktime()... Time handling is not easy. I hate to say that, but POSIX and glibc manage to make it even harder. Especially the timezone global handling which is not thread-safe as you pinpointed. Anyway, free conding a simple timegm() is not very hard, since you don't have to take any timezone into account, only leap years.
But beware that real timegm() (and mktime()) perform some time normalization on tm_mday (day of the month) or tm_mon (month). For example, they will happily work with fake dates like "March 31st 2017", "February 59th 2017" or even "1st day of 18th month of 2016" and will convert them to "April 1st 2017" internally. It's very handy when you want to compute date in the future or in the past, you just add/substract values to the corresponding field (day or month) and let mktime() of timegm() do their magic trick. Benoît