From 9e16eecc70020e9a603d637f6a8fdfc7c95c30e1 Mon Sep 17 00:00:00 2001
From: Ron Yorston <rmy@pobox.com>
Date: Fri, 17 Sep 2021 09:18:58 +0100
Subject: win32: changes to allow timezones in dates

Create mingw_strptime() to return timezone offset as a separate
argument (since Microsoft's struct tm doesn't have the required
member).

Import timegm() from musl.

Update parse_datestr() to use mingw_strptime().

Enable FEATURE_TIMEZONE in the default configuration.

GitHub issue #230.
---
 configs/mingw32_defconfig |   4 +-
 configs/mingw64_defconfig |   4 +-
 include/mingw.h           |   3 +
 libbb/time.c              |   9 ++
 win32/Kbuild              |   1 +
 win32/strptime.c          |  37 +++++++--
 win32/timegm.c            | 205 ++++++++++++++++++++++++++++++++++++++++++++++
 7 files changed, 254 insertions(+), 9 deletions(-)
 create mode 100644 win32/timegm.c

diff --git a/configs/mingw32_defconfig b/configs/mingw32_defconfig
index 0882741d5..195020151 100644
--- a/configs/mingw32_defconfig
+++ b/configs/mingw32_defconfig
@@ -1,7 +1,7 @@
 #
 # Automatically generated make config: don't edit
 # Busybox version: 1.35.0.git
-# Fri Sep 10 14:50:08 2021
+# Fri Sep 17 09:17:23 2021
 #
 CONFIG_HAVE_DOT_CONFIG=y
 # CONFIG_PLATFORM_POSIX is not set
@@ -147,7 +147,7 @@ CONFIG_FEATURE_COPYBUF_KB=4
 # CONFIG_MONOTONIC_SYSCALL is not set
 # CONFIG_IOCTL_HEX2STR_ERROR is not set
 # CONFIG_FEATURE_HWIB is not set
-# CONFIG_FEATURE_TIMEZONE is not set
+CONFIG_FEATURE_TIMEZONE=y
 
 #
 # Applets
diff --git a/configs/mingw64_defconfig b/configs/mingw64_defconfig
index 358d19d4e..aa704fe75 100644
--- a/configs/mingw64_defconfig
+++ b/configs/mingw64_defconfig
@@ -1,7 +1,7 @@
 #
 # Automatically generated make config: don't edit
 # Busybox version: 1.35.0.git
-# Fri Sep 10 14:50:08 2021
+# Fri Sep 17 09:17:23 2021
 #
 CONFIG_HAVE_DOT_CONFIG=y
 # CONFIG_PLATFORM_POSIX is not set
@@ -147,7 +147,7 @@ CONFIG_FEATURE_COPYBUF_KB=4
 # CONFIG_MONOTONIC_SYSCALL is not set
 # CONFIG_IOCTL_HEX2STR_ERROR is not set
 # CONFIG_FEATURE_HWIB is not set
-# CONFIG_FEATURE_TIMEZONE is not set
+CONFIG_FEATURE_TIMEZONE=y
 
 #
 # Applets
diff --git a/include/mingw.h b/include/mingw.h
index 03ef89029..d48ad3814 100644
--- a/include/mingw.h
+++ b/include/mingw.h
@@ -272,6 +272,8 @@ struct timespec {
 };
 #endif
 
+time_t timegm(struct tm *tm);
+
 int nanosleep(const struct timespec *req, struct timespec *rem);
 
 /*
@@ -402,6 +404,7 @@ pid_t mingw_wait3(pid_t pid, int *status, int options, struct rusage *rusage);
 struct tm *gmtime_r(const time_t *timep, struct tm *result);
 struct tm *localtime_r(const time_t *timep, struct tm *result);
 char *strptime(const char *s, const char *format, struct tm *tm);
+char *mingw_strptime(const char *s, const char *format, struct tm *tm, long *gmt);
 size_t mingw_strftime(char *buf, size_t max, const char *format, const struct tm *tm);
 
 #define strftime mingw_strftime
diff --git a/libbb/time.c b/libbb/time.c
index 41a69c754..ed4f50470 100644
--- a/libbb/time.c
+++ b/libbb/time.c
@@ -43,7 +43,12 @@ int FAST_FUNC parse_datestr(const char *date_str, struct tm *ptm)
 	save = *ptm;
 	fmt = fmt_str;
 	while (*fmt) {
+#if ENABLE_PLATFORM_MINGW32 && ENABLE_FEATURE_TIMEZONE
+		long gmtoff;
+		endp = mingw_strptime(date_str, fmt, ptm, &gmtoff);
+#else
 		endp = strptime(date_str, fmt, ptm);
+#endif
 		if (endp && *endp == '\0') {
 #if ENABLE_FEATURE_TIMEZONE
 			if (strchr(fmt, 'z')) {
@@ -51,7 +56,11 @@ int FAST_FUNC parse_datestr(const char *date_str, struct tm *ptm)
 				struct tm *utm;
 
 				/* we have timezone offset: obtain Unix time_t */
+#if ENABLE_PLATFORM_MINGW32
+				ptm->tm_sec -= gmtoff;
+#else
 				ptm->tm_sec -= ptm->tm_gmtoff;
+#endif
 				ptm->tm_isdst = 0;
 				t = timegm(ptm);
 				if (t == (time_t)-1)
diff --git a/win32/Kbuild b/win32/Kbuild
index 6ab0dc077..c38a9ef4d 100644
--- a/win32/Kbuild
+++ b/win32/Kbuild
@@ -26,5 +26,6 @@ lib-$(CONFIG_PLATFORM_MINGW32) += strndup.o
 lib-$(CONFIG_PLATFORM_MINGW32) += strptime.o
 lib-$(CONFIG_PLATFORM_MINGW32) += system.o
 lib-$(CONFIG_PLATFORM_MINGW32) += termios.o
+lib-$(CONFIG_FEATURE_TIMEZONE) += timegm.o
 lib-$(CONFIG_PLATFORM_MINGW32) += uname.o
 lib-$(CONFIG_PLATFORM_MINGW32) += winansi.o
diff --git a/win32/strptime.c b/win32/strptime.c
index 75e5e34f7..3205b95a2 100644
--- a/win32/strptime.c
+++ b/win32/strptime.c
@@ -19,6 +19,8 @@
  * File from gnulib (https://www.gnu.org/software/gnulib/), processed with
  *    coan source -U_LIBC -U_NL_CURRENT -UHAVE_TM_GMTOFF strptime.c
  * and lightly edited.
+ *
+ * A form of support for tm_gmtoff was later restored.
  */
 
 #include "libbb.h"
@@ -62,7 +64,7 @@ enum ptime_locale_status { not, loc, raw };
 #define recursive(new_fmt) \
   (*(new_fmt) != '\0'                                                         \
    && (rp = __strptime_internal (rp, (new_fmt), tm,                           \
-                                 decided, era_cnt)) != NULL)
+                                 decided, era_cnt, gmtoff)) != NULL)
 
 
 static char const weekday_name[][10] =
@@ -141,7 +143,8 @@ day_of_the_year (struct tm *tm)
 
 static char *
 __strptime_internal (const char *rp, const char *fmt, struct tm *tm,
-                     enum ptime_locale_status *decided, int era_cnt)
+                     enum ptime_locale_status *decided, int era_cnt,
+                     long *gmtoff)
 {
 
   int cnt;
@@ -426,16 +429,25 @@ __strptime_internal (const char *rp, const char *fmt, struct tm *tm,
         case 'z':
           /* We recognize two formats: if two digits are given, these
              specify hours.  If fours digits are used, minutes are
-             also specified.  */
+             also specified.  And 'Z'.
+
+             Three formats!  We recognize three formats...  */
           {
+            bool neg;
             int n;
 
             val = 0;
             while (*rp == ' ')
               ++rp;
+            if (*rp == 'Z') {
+              ++rp;
+              if (gmtoff)
+                *gmtoff = 0;
+              break;
+            }
             if (*rp != '+' && *rp != '-')
               return NULL;
-            ++rp;
+            neg = *rp++ == '-';
             n = 0;
             while (n < 4 && *rp >= '0' && *rp <= '9')
               {
@@ -456,6 +468,11 @@ __strptime_internal (const char *rp, const char *fmt, struct tm *tm,
               }
             if (val > 1200)
               return NULL;
+            if (gmtoff) {
+              *gmtoff = (val * 3600) / 100;
+              if (neg)
+                *gmtoff = -*gmtoff;
+            }
           }
           break;
         case 'E':
@@ -571,6 +588,16 @@ strptime (const char *buf, const char *format, struct tm *tm)
   enum ptime_locale_status decided;
 
   decided = raw;
-  return __strptime_internal (buf, format, tm, &decided, -1);
+  return __strptime_internal (buf, format, tm, &decided, -1, NULL);
+}
+
+char *
+mingw_strptime (const char *buf, const char *format, struct tm *tm,
+                long *gmtoff)
+{
+  enum ptime_locale_status decided;
+
+  decided = raw;
+  return __strptime_internal (buf, format, tm, &decided, -1, gmtoff);
 }
 
diff --git a/win32/timegm.c b/win32/timegm.c
new file mode 100644
index 000000000..abba2579f
--- /dev/null
+++ b/win32/timegm.c
@@ -0,0 +1,205 @@
+/*
+	timegm from musl (https://www.musl-libc.org/).
+
+	MIT licensed:
+
+----------------------------------------------------------------------
+Copyright © 2005-2020 Rich Felker, et al.
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+----------------------------------------------------------------------
+*/
+#include "libbb.h"
+
+static long long __year_to_secs(long long year, int *is_leap)
+{
+	int cycles, centuries, leaps, rem;
+
+	if (year-2ULL <= 136) {
+		int y = year;
+		leaps = (y-68)>>2;
+		if (!((y-68)&3)) {
+			leaps--;
+			if (is_leap) *is_leap = 1;
+		} else if (is_leap) *is_leap = 0;
+		return 31536000*(y-70) + 86400*leaps;
+	}
+
+	if (!is_leap) is_leap = &(int){0};
+	cycles = (year-100) / 400;
+	rem = (year-100) % 400;
+	if (rem < 0) {
+		cycles--;
+		rem += 400;
+	}
+	if (!rem) {
+		*is_leap = 1;
+		centuries = 0;
+		leaps = 0;
+	} else {
+		if (rem >= 200) {
+			if (rem >= 300) centuries = 3, rem -= 300;
+			else centuries = 2, rem -= 200;
+		} else {
+			if (rem >= 100) centuries = 1, rem -= 100;
+			else centuries = 0;
+		}
+		if (!rem) {
+			*is_leap = 0;
+			leaps = 0;
+		} else {
+			leaps = rem / 4U;
+			rem %= 4U;
+			*is_leap = !rem;
+		}
+	}
+
+	leaps += 97*cycles + 24*centuries - *is_leap;
+
+	return (year-100) * 31536000LL + leaps * 86400LL + 946684800 + 86400;
+}
+
+static int __month_to_secs(int month, int is_leap)
+{
+	static const int secs_through_month[] = {
+		0, 31*86400, 59*86400, 90*86400,
+		120*86400, 151*86400, 181*86400, 212*86400,
+		243*86400, 273*86400, 304*86400, 334*86400 };
+	int t = secs_through_month[month];
+	if (is_leap && month >= 2) t+=86400;
+	return t;
+}
+
+static long long __tm_to_secs(const struct tm *tm)
+{
+	int is_leap;
+	long long t;
+	long long year = tm->tm_year;
+	int month = tm->tm_mon;
+	if (month >= 12 || month < 0) {
+		int adj = month / 12;
+		month %= 12;
+		if (month < 0) {
+			adj--;
+			month += 12;
+		}
+		year += adj;
+	}
+	t = __year_to_secs(year, &is_leap);
+	t += __month_to_secs(month, is_leap);
+	t += 86400LL * (tm->tm_mday-1);
+	t += 3600LL * tm->tm_hour;
+	t += 60LL * tm->tm_min;
+	t += tm->tm_sec;
+	return t;
+}
+
+/* 2000-03-01 (mod 400 year, immediately after feb29 */
+#define LEAPOCH (946684800LL + 86400*(31+29))
+
+#define DAYS_PER_400Y (365*400 + 97)
+#define DAYS_PER_100Y (365*100 + 24)
+#define DAYS_PER_4Y   (365*4   + 1)
+
+static int __secs_to_tm(long long t, struct tm *tm)
+{
+	long long days, secs, years;
+	int remdays, remsecs, remyears;
+	int qc_cycles, c_cycles, q_cycles;
+	int months;
+	int wday, yday, leap;
+	static const char days_in_month[] = {31,30,31,30,31,31,30,31,30,31,31,29};
+
+	/* Reject time_t values whose year would overflow int */
+	if (t < INT_MIN * 31622400LL || t > INT_MAX * 31622400LL)
+		return -1;
+
+	secs = t - LEAPOCH;
+	days = secs / 86400;
+	remsecs = secs % 86400;
+	if (remsecs < 0) {
+		remsecs += 86400;
+		days--;
+	}
+
+	wday = (3+days)%7;
+	if (wday < 0) wday += 7;
+
+	qc_cycles = days / DAYS_PER_400Y;
+	remdays = days % DAYS_PER_400Y;
+	if (remdays < 0) {
+		remdays += DAYS_PER_400Y;
+		qc_cycles--;
+	}
+
+	c_cycles = remdays / DAYS_PER_100Y;
+	if (c_cycles == 4) c_cycles--;
+	remdays -= c_cycles * DAYS_PER_100Y;
+
+	q_cycles = remdays / DAYS_PER_4Y;
+	if (q_cycles == 25) q_cycles--;
+	remdays -= q_cycles * DAYS_PER_4Y;
+
+	remyears = remdays / 365;
+	if (remyears == 4) remyears--;
+	remdays -= remyears * 365;
+
+	leap = !remyears && (q_cycles || !c_cycles);
+	yday = remdays + 31 + 28 + leap;
+	if (yday >= 365+leap) yday -= 365+leap;
+
+	years = remyears + 4*q_cycles + 100*c_cycles + 400LL*qc_cycles;
+
+	for (months=0; days_in_month[months] <= remdays; months++)
+		remdays -= days_in_month[months];
+
+	if (months >= 10) {
+		months -= 12;
+		years++;
+	}
+
+	if (years+100 > INT_MAX || years+100 < INT_MIN)
+		return -1;
+
+	tm->tm_year = years + 100;
+	tm->tm_mon = months + 2;
+	tm->tm_mday = remdays + 1;
+	tm->tm_wday = wday;
+	tm->tm_yday = yday;
+
+	tm->tm_hour = remsecs / 3600;
+	tm->tm_min = remsecs / 60 % 60;
+	tm->tm_sec = remsecs % 60;
+
+	return 0;
+}
+
+time_t timegm(struct tm *tm)
+{
+	struct tm new;
+	long long t = __tm_to_secs(tm);
+	if (__secs_to_tm(t, &new) < 0) {
+		errno = EOVERFLOW;
+		return -1;
+	}
+	*tm = new;
+	tm->tm_isdst = 0;
+	return t;
+}
-- 
cgit v1.2.3-55-g6feb