|
|
- From 3e21b8d25ad148ef4e6544f28a8b2305f9484a7b Mon Sep 17 00:00:00 2001
- From: Willy Tarreau <w@1wt.eu>
- Date: Wed, 19 Jul 2017 19:05:29 +0200
- Subject: [PATCH 08/18] MINOR: tools: add a portable timegm() alternative
- MIME-Version: 1.0
- Content-Type: text/plain; charset=UTF-8
- Content-Transfer-Encoding: 8bit
-
- timegm() is not provided everywhere and the documentation on how to
- replace it is bogus as it proposes an inefficient and non-thread safe
- alternative.
-
- Here we reimplement everything needed to compute the number of seconds
- since Epoch based on the broken down fields in struct tm. It is only
- guaranteed to return correct values for correct inputs. It was successfully
- tested with all possible 32-bit values of time_t converted to struct tm
- using gmtime() and back to time_t using the legacy timegm() and this
- function, and both functions always produced the same result.
-
- Thanks to Benoît Garnier for an instructive discussion and detailed
- explanations of the various time functions, leading to this solution.
- (cherry picked from commit cb1949b8b30b8db7e05546da2939eff2b5973321)
-
- Signed-off-by: Willy Tarreau <w@1wt.eu>
- ---
- include/common/standard.h | 21 ++++++++++++++++++
- src/standard.c | 54 +++++++++++++++++++++++++++++++++++++++++++++++
- 2 files changed, 75 insertions(+)
-
- diff --git a/include/common/standard.h b/include/common/standard.h
- index 87f90a65..c19c368b 100644
- --- a/include/common/standard.h
- +++ b/include/common/standard.h
- @@ -624,6 +624,27 @@ static inline void get_gmtime(const time_t now, struct tm *tm)
- gmtime_r(&now, tm);
- }
-
- +/* Counts a number of elapsed days since 01/01/0000 based solely on elapsed
- + * years and assuming the regular rule for leap years applies. It's fake but
- + * serves as a temporary origin. It's worth remembering that it's the first
- + * year of each period that is leap and not the last one, so for instance year
- + * 1 sees 366 days since year 0 was leap. For this reason we have to apply
- + * modular arithmetics which is why we offset the year by 399 before
- + * subtracting the excess at the end. No overflow here before ~11.7 million
- + * years.
- + */
- +static inline unsigned int days_since_zero(unsigned int y)
- +{
- + return y * 365 + (y + 399) / 4 - (y + 399) / 100 + (y + 399) / 400
- + - 399 / 4 + 399 / 100;
- +}
- +
- +/* Returns the number of seconds since 01/01/1970 0:0:0 GMT for GMT date <tm>.
- + * It is meant as a portable replacement for timegm() for use with valid inputs.
- + * Returns undefined results for invalid dates (eg: months out of range 0..11).
- + */
- +extern time_t my_timegm(const struct tm *tm);
- +
- /* This function parses a time value optionally followed by a unit suffix among
- * "d", "h", "m", "s", "ms" or "us". It converts the value into the unit
- * expected by the caller. The computation does its best to avoid overflows.
- diff --git a/src/standard.c b/src/standard.c
- index 8df1da6c..e1d414f3 100644
- --- a/src/standard.c
- +++ b/src/standard.c
- @@ -2841,6 +2841,60 @@ char *localdate2str_log(char *dst, time_t t, struct tm *tm, size_t size)
- return dst;
- }
-
- +/* Returns the number of seconds since 01/01/1970 0:0:0 GMT for GMT date <tm>.
- + * It is meant as a portable replacement for timegm() for use with valid inputs.
- + * Returns undefined results for invalid dates (eg: months out of range 0..11).
- + */
- +time_t my_timegm(const struct tm *tm)
- +{
- + /* Each month has 28, 29, 30 or 31 days, or 28+N. The date in the year
- + * is thus (current month - 1)*28 + cumulated_N[month] to count the
- + * sum of the extra N days for elapsed months. The sum of all these N
- + * days doesn't exceed 30 for a complete year (366-12*28) so it fits
- + * in a 5-bit word. This means that with 60 bits we can represent a
- + * matrix of all these values at once, which is fast and efficient to
- + * access. The extra February day for leap years is not counted here.
- + *
- + * Jan : none = 0 (0)
- + * Feb : Jan = 3 (3)
- + * Mar : Jan..Feb = 3 (3 + 0)
- + * Apr : Jan..Mar = 6 (3 + 0 + 3)
- + * May : Jan..Apr = 8 (3 + 0 + 3 + 2)
- + * Jun : Jan..May = 11 (3 + 0 + 3 + 2 + 3)
- + * Jul : Jan..Jun = 13 (3 + 0 + 3 + 2 + 3 + 2)
- + * Aug : Jan..Jul = 16 (3 + 0 + 3 + 2 + 3 + 2 + 3)
- + * Sep : Jan..Aug = 19 (3 + 0 + 3 + 2 + 3 + 2 + 3 + 3)
- + * Oct : Jan..Sep = 21 (3 + 0 + 3 + 2 + 3 + 2 + 3 + 3 + 2)
- + * Nov : Jan..Oct = 24 (3 + 0 + 3 + 2 + 3 + 2 + 3 + 3 + 2 + 3)
- + * Dec : Jan..Nov = 26 (3 + 0 + 3 + 2 + 3 + 2 + 3 + 3 + 2 + 3 + 2)
- + */
- + uint64_t extra =
- + ( 0ULL << 0*5) + ( 3ULL << 1*5) + ( 3ULL << 2*5) + /* Jan, Feb, Mar, */
- + ( 6ULL << 3*5) + ( 8ULL << 4*5) + (11ULL << 5*5) + /* Apr, May, Jun, */
- + (13ULL << 6*5) + (16ULL << 7*5) + (19ULL << 8*5) + /* Jul, Aug, Sep, */
- + (21ULL << 9*5) + (24ULL << 10*5) + (26ULL << 11*5); /* Oct, Nov, Dec, */
- +
- + unsigned int y = tm->tm_year + 1900;
- + unsigned int m = tm->tm_mon;
- + unsigned long days = 0;
- +
- + /* days since 1/1/1970 for full years */
- + days += days_since_zero(y) - days_since_zero(1970);
- +
- + /* days for full months in the current year */
- + days += 28 * m + ((extra >> (m * 5)) & 0x1f);
- +
- + /* count + 1 after March for leap years. A leap year is a year multiple
- + * of 4, unless it's multiple of 100 without being multiple of 400. 2000
- + * is leap, 1900 isn't, 1904 is.
- + */
- + if ((m > 1) && !(y & 3) && ((y % 100) || !(y % 400)))
- + days++;
- +
- + days += tm->tm_mday - 1;
- + return days * 86400ULL + tm->tm_hour * 3600 + tm->tm_min * 60 + tm->tm_sec;
- +}
- +
- /* This function check a char. It returns true and updates
- * <date> and <len> pointer to the new position if the
- * character is found.
- --
- 2.13.0
-
|