You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

129 lines
5.5 KiB

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