|
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
|
|
|