Allow ISO 8601 style dates to include a timezone offset.  Like
the '@' format these dates aren't relative to the user's current
timezone and shouldn't be subject to DST adjustment.

- The implementation uses the strptime() '%z' format specifier.
  This an extension which may not be available so the use of
  timezones is a configuration option.

- The 'touch' applet has been updated to respect whether DST
  adjustment is required, matching 'date'.

function                                             old     new   delta
parse_datestr                                        624     730    +106
static.fmt_str                                       106     136     +30
touch_main                                           388     392      +4
date_main                                            818     819      +1
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 4/0 up/down: 141/0)             Total: 141 bytes

Signed-off-by: Ron Yorston <[email protected]>
---
 coreutils/date.c             |  7 ++++---
 coreutils/touch.c            |  6 ++++--
 include/libbb.h              |  2 +-
 libbb/Config.src             | 11 +++++++++++
 libbb/time.c                 | 32 ++++++++++++++++++++++++++++----
 testsuite/date/date-timezone | 32 ++++++++++++++++++++++++++++++++
 6 files changed, 80 insertions(+), 10 deletions(-)
 create mode 100644 testsuite/date/date-timezone

diff --git a/coreutils/date.c b/coreutils/date.c
index 7061f1719..abcc37c33 100644
--- a/coreutils/date.c
+++ b/coreutils/date.c
@@ -266,6 +266,7 @@ int date_main(int argc UNUSED_PARAM, char **argv)
 
        /* If date string is given, update tm_time, and maybe set date */
        if (date_str != NULL) {
+               int check_dst = 1;
                /* Zero out fields - take her back to midnight! */
                tm_time.tm_sec = 0;
                tm_time.tm_min = 0;
@@ -276,12 +277,12 @@ int date_main(int argc UNUSED_PARAM, char **argv)
                        if (strptime(date_str, fmt_str2dt, &tm_time) == NULL)
                                bb_error_msg_and_die(bb_msg_invalid_date, 
date_str);
                } else {
-                       parse_datestr(date_str, &tm_time);
+                       check_dst = parse_datestr(date_str, &tm_time);
                }
 
                /* Correct any day of week and day of year etc. fields */
-               /* Be sure to recheck dst (but not if date is time_t format) */
-               if (date_str[0] != '@')
+               /* Be sure to recheck dst (but not if date is UTC) */
+               if (check_dst)
                        tm_time.tm_isdst = -1;
                ts.tv_sec = validate_tm_time(date_str, &tm_time);
                ts.tv_nsec = 0;
diff --git a/coreutils/touch.c b/coreutils/touch.c
index 78100ba1d..7e13a27be 100644
--- a/coreutils/touch.c
+++ b/coreutils/touch.c
@@ -140,15 +140,17 @@ int touch_main(int argc UNUSED_PARAM, char **argv)
        if (opts & (OPT_d|OPT_t)) {
                struct tm tm_time;
                time_t t;
+               int check_dst;
 
                //memset(&tm_time, 0, sizeof(tm_time));
                /* Better than memset: makes "HH:MM" dates meaningful */
                time(&t);
                localtime_r(&t, &tm_time);
-               parse_datestr(date_str, &tm_time);
+               check_dst = parse_datestr(date_str, &tm_time);
 
                /* Correct any day of week and day of year etc. fields */
-               tm_time.tm_isdst = -1;  /* Be sure to recheck dst */
+               if (check_dst)
+                       tm_time.tm_isdst = -1;  /* recheck dst unless date is 
UTC */
                t = validate_tm_time(date_str, &tm_time);
 
                timebuf[1].tv_sec = timebuf[0].tv_sec = t;
diff --git a/include/libbb.h b/include/libbb.h
index 7d6ab4a93..1ec8d2d3b 100644
--- a/include/libbb.h
+++ b/include/libbb.h
@@ -690,7 +690,7 @@ struct BUG_too_small {
 };
 
 
-void parse_datestr(const char *date_str, struct tm *ptm) FAST_FUNC;
+int parse_datestr(const char *date_str, struct tm *ptm) FAST_FUNC;
 time_t validate_tm_time(const char *date_str, struct tm *ptm) FAST_FUNC;
 char *strftime_HHMMSS(char *buf, unsigned len, time_t *tp) FAST_FUNC;
 char *strftime_YYYYMMDDHHMMSS(char *buf, unsigned len, time_t *tp) FAST_FUNC;
diff --git a/libbb/Config.src b/libbb/Config.src
index f97de8ef7..58c5fad50 100644
--- a/libbb/Config.src
+++ b/libbb/Config.src
@@ -395,3 +395,14 @@ config FEATURE_HWIB
        default y
        help
        Support for printing infiniband addresses in network applets.
+
+config FEATURE_TIMEZONE
+       bool "Allow timezone in dates"
+       default y
+       depends on DESKTOP
+       help
+       Permit the use of timezones when parsing user-provided data
+       strings, e.g. '1996-04-09 12:45:00 -0500'.
+
+       This requires support for the '%z' extension to strptime() which
+       may not be available in all implementations.
diff --git a/libbb/time.c b/libbb/time.c
index 365b1df02..e4d108c83 100644
--- a/libbb/time.c
+++ b/libbb/time.c
@@ -8,7 +8,9 @@
  */
 #include "libbb.h"
 
-void FAST_FUNC parse_datestr(const char *date_str, struct tm *ptm)
+/* Returns 0 if the time structure contains an absolute UTC time which
+ * should not be subject to DST adjustment by the caller. */
+int FAST_FUNC parse_datestr(const char *date_str, struct tm *ptm)
 {
        char end = '\0';
 #if ENABLE_DESKTOP
@@ -27,6 +29,10 @@ void FAST_FUNC parse_datestr(const char *date_str, struct tm 
*ptm)
                "%b %d %T %Y" "\0"      /* month_name d HH:MM:SS YYYY */
                "%Y-%m-%d %R" "\0"      /* yyyy-mm-dd HH:MM */
                "%Y-%m-%d %T" "\0"      /* yyyy-mm-dd HH:MM:SS */
+#if ENABLE_FEATURE_TIMEZONE
+               "%Y-%m-%d %R %z" "\0"   /* yyyy-mm-dd HH:MM TZ */
+               "%Y-%m-%d %T %z" "\0"   /* yyyy-mm-dd HH:MM:SS TZ */
+#endif
                "%Y-%m-%d %H" "\0"      /* yyyy-mm-dd HH */
                "%Y-%m-%d" "\0"         /* yyyy-mm-dd */
                /* extra NUL */;
@@ -38,8 +44,25 @@ void FAST_FUNC parse_datestr(const char *date_str, struct tm 
*ptm)
        fmt = fmt_str;
        while (*fmt) {
                endp = strptime(date_str, fmt, ptm);
-               if (endp && *endp == '\0')
-                       return;
+               if (endp && *endp == '\0') {
+#if ENABLE_FEATURE_TIMEZONE
+                       if (strchr(fmt, 'z')) {
+                               time_t t;
+                               struct tm *utm;
+
+                               /* we have timezone offset: obtain Unix time_t 
*/
+                               ptm->tm_sec -= ptm->tm_gmtoff;
+                               ptm->tm_isdst = 0;
+                               t = timegm(ptm);
+                               /* convert Unix time_t to struct tm in user's 
locale */
+                               if (t == (time_t)-1 || (utm = localtime(&t)) == 
NULL)
+                                       break;
+                               *ptm = *utm;
+                               return 0;
+                       }
+#endif
+                       return 1;
+               }
                *ptm = save;
                while (*++fmt)
                        continue;
@@ -124,7 +147,7 @@ void FAST_FUNC parse_datestr(const char *date_str, struct 
tm *ptm)
                        struct tm *lt = localtime(&t);
                        if (lt) {
                                *ptm = *lt;
-                               return;
+                               return 0;
                        }
                }
                end = '1';
@@ -241,6 +264,7 @@ void FAST_FUNC parse_datestr(const char *date_str, struct 
tm *ptm)
        if (end != '\0') {
                bb_error_msg_and_die(bb_msg_invalid_date, date_str);
        }
+       return 1;
 }
 
 time_t FAST_FUNC validate_tm_time(const char *date_str, struct tm *ptm)
diff --git a/testsuite/date/date-timezone b/testsuite/date/date-timezone
new file mode 100644
index 000000000..8628aa1d7
--- /dev/null
+++ b/testsuite/date/date-timezone
@@ -0,0 +1,32 @@
+# FEATURE: CONFIG_FEATURE_TIMEZONE
+
+# 'Z' is UTC
+dt=$(TZ=UTC0 busybox date -d '1999-1-2 3:4:5Z')
+dt=$(echo "$dt" | cut -b1-19)
+test x"$dt" = x"Sat Jan  2 03:04:05"
+
+# '+0600' is six hours ahead of UTC
+dt=$(TZ=UTC0 busybox date -d '1999-1-2 3:4:5 +0600')
+dt=$(echo "$dt" | cut -b1-19)
+test x"$dt" = x"Fri Jan  1 21:04:05"
+
+# '-0600' is six hours behind UTC
+dt=$(TZ=UTC0 busybox date -d '1999-1-2 3:4:5 -0600')
+dt=$(echo "$dt" | cut -b1-19)
+test x"$dt" = x"Sat Jan  2 09:04:05"
+
+# before dst is switched on
+dt=$(TZ=GMT0BST,M3.5.0/1,M10.5.0/2 busybox date -d '2021-03-28 00:59:59 +0000')
+test x"$dt" = x"Sun Mar 28 00:59:59 GMT 2021"
+
+# after dst is switched on
+dt=$(TZ=GMT0BST,M3.5.0/1,M10.5.0/2 busybox date -d '2021-03-28 01:00:01 +0000')
+test x"$dt" = x"Sun Mar 28 02:00:01 BST 2021"
+
+# before dst is switched off
+dt=$(TZ=GMT0BST,M3.5.0/1,M10.5.0/2 busybox date -d '2021-10-31 00:00:01 +0000')
+test x"$dt" = x"Sun Oct 31 01:00:01 BST 2021"
+
+# after dst is switched off: back to 01:00:01 but with different TZ
+dt=$(TZ=GMT0BST,M3.5.0/1,M10.5.0/2 busybox date -d '2021-10-31 01:00:01 +0000')
+test x"$dt" = x"Sun Oct 31 01:00:01 GMT 2021"
-- 
2.31.1

_______________________________________________
busybox mailing list
[email protected]
http://lists.busybox.net/mailman/listinfo/busybox

Reply via email to