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