summaryrefslogtreecommitdiffstats
path: root/src/lib/utc-mktime.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/utc-mktime.c')
-rw-r--r--src/lib/utc-mktime.c79
1 files changed, 79 insertions, 0 deletions
diff --git a/src/lib/utc-mktime.c b/src/lib/utc-mktime.c
new file mode 100644
index 0000000..e67c687
--- /dev/null
+++ b/src/lib/utc-mktime.c
@@ -0,0 +1,79 @@
+/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "utc-mktime.h"
+
+static int tm_cmp(const struct tm *tm1, const struct tm *tm2)
+{
+ int diff;
+
+ if ((diff = tm1->tm_year - tm2->tm_year) != 0)
+ return diff;
+ if ((diff = tm1->tm_mon - tm2->tm_mon) != 0)
+ return diff;
+ if ((diff = tm1->tm_mday - tm2->tm_mday) != 0)
+ return diff;
+ if ((diff = tm1->tm_hour - tm2->tm_hour) != 0)
+ return diff;
+ if ((diff = tm1->tm_min - tm2->tm_min) != 0)
+ return diff;
+ return tm1->tm_sec - tm2->tm_sec;
+}
+
+static inline void adjust_leap_second(struct tm *tm)
+{
+ if (tm->tm_sec == 60)
+ tm->tm_sec = 59;
+}
+
+#ifdef HAVE_TIMEGM
+/* Normalization done by timegm is considered a failure here, since it means
+ * the timestamp is not valid as-is. Leap second 60 is adjusted to 59 before
+ * this though. */
+time_t utc_mktime(const struct tm *tm)
+{
+ struct tm leap_adj_tm = *tm;
+ adjust_leap_second(&leap_adj_tm);
+ struct tm tmp = leap_adj_tm;
+ time_t t;
+
+ t = timegm(&tmp);
+ if (tm_cmp(&leap_adj_tm, &tmp) != 0)
+ return (time_t)-1;
+ return t;
+}
+#else
+time_t utc_mktime(const struct tm *tm)
+{
+ struct tm leap_adj_tm = *tm;
+ adjust_leap_second(&leap_adj_tm);
+ const struct tm *try_tm;
+ time_t t;
+ int bits, dir;
+
+ /* we'll do a binary search across the entire valid time_t range.
+ when gmtime()'s output matches the tm parameter, we've found the
+ correct time_t value. this also means that if tm contains invalid
+ values, -1 is returned. */
+#ifdef TIME_T_SIGNED
+ t = 0;
+#else
+ t = (time_t)1 << (TIME_T_MAX_BITS - 1);
+#endif
+ for (bits = TIME_T_MAX_BITS - 2;; bits--) {
+ try_tm = gmtime(&t);
+ dir = tm_cmp(&leap_adj_tm, try_tm);
+ if (dir == 0)
+ return t;
+ if (bits < 0)
+ break;
+
+ if (dir < 0)
+ t -= (time_t)1 << bits;
+ else
+ t += (time_t)1 << bits;
+ }
+
+ return (time_t)-1;
+}
+#endif