public inbox for newlib@sourceware.org
 help / color / mirror / Atom feed
* [PATCH] add tests for tzset(3)
@ 2022-04-07 15:58 jdoubleu
  2022-04-08 21:21 ` Jeff Johnston
  2022-04-10  8:43 ` Dimitar Dimitrov
  0 siblings, 2 replies; 35+ messages in thread
From: jdoubleu @ 2022-04-07 15:58 UTC (permalink / raw)
  To: newlib

[-- Attachment #1: Type: text/plain, Size: 478 bytes --]

Hi,

I've finally created tests for tzset(3). They test the POSIX timezone 
string compliance.

This patch is intended to be applied after Brian's tzset changes have 
been pushed (see other discussion).

You can also find the test vectors online 
(https://github.com/jdoubleu/newlib-posix-tzset-tests/blob/main/timezones.h) 
and run the tests on linux with glibc 
(https://github.com/jdoubleu/newlib-posix-tzset-tests/tree/main/host_test).

Cheers
---
🙎🏻‍♂️ jdoubleu

[-- Attachment #2: 0001-add-tests-for-tzset-3.patch --]
[-- Type: text/plain, Size: 8909 bytes --]

From 97fa4a760e67f4f553dbe18a3a75fa14e855916d Mon Sep 17 00:00:00 2001
From: jdoubleu <hi@jdoubleu.de>
Date: Thu, 7 Apr 2022 17:30:01 +0200
Subject: [PATCH] add tests for tzset(3)

---
 newlib/testsuite/newlib.time/time.exp |  12 ++
 newlib/testsuite/newlib.time/tzset.c  | 163 ++++++++++++++++++++++++++
 2 files changed, 175 insertions(+)
 create mode 100644 newlib/testsuite/newlib.time/time.exp
 create mode 100644 newlib/testsuite/newlib.time/tzset.c

diff --git a/newlib/testsuite/newlib.time/time.exp b/newlib/testsuite/newlib.time/time.exp
new file mode 100644
index 000000000..3fce2306e
--- /dev/null
+++ b/newlib/testsuite/newlib.time/time.exp
@@ -0,0 +1,12 @@
+# Copyright (C) 2022 jdoubleu. All rights reserved.
+#
+# Permission to use, copy, modify, and distribute this software
+# is freely granted, provided that this notice is preserved.
+#
+
+load_lib passfail.exp
+
+set exclude_list {
+}
+
+newlib_pass_fail_all -x $exclude_list
diff --git a/newlib/testsuite/newlib.time/tzset.c b/newlib/testsuite/newlib.time/tzset.c
new file mode 100644
index 000000000..0e5b196c6
--- /dev/null
+++ b/newlib/testsuite/newlib.time/tzset.c
@@ -0,0 +1,163 @@
+/* Test that valid POSIX timezone strings are correctly parsed by tzset(3). */
+#include <stdio.h>
+#include <stdlib.h>
+
+// BEGIN test vectors
+#include <time.h>
+#include <limits.h>
+
+#define IN_SECONDS(h, m, s) ((h) * 3600 + (m) * 60 + (s))
+#define NO_TIME INT_MIN
+
+struct tz_test {
+    const char* tzstr;
+    int offset_seconds;
+    int dst_offset_seconds;
+};
+
+extern struct tm winter_tm;
+extern struct tm summer_tm;
+extern const time_t winter_time;
+extern const time_t summer_time;
+extern struct tz_test test_timezones[];
+
+// winter time is March, 21st 2022 at 8:15pm and 20 seconds
+struct tm winter_tm = {
+    .tm_sec     = 20,
+    .tm_min     = 15,
+    .tm_hour    = 20,
+    .tm_mday    = 21,
+    .tm_mon     = 3 - 1,
+    .tm_year    = 2022 - 1900,
+    .tm_isdst   = 0
+};
+
+// summer time is July, 15th 2022 at 10:50am and 40 seconds
+struct tm summer_tm = {
+    .tm_sec     = 40,
+    .tm_min     = 50,
+    .tm_hour    = 10,
+    .tm_mday    = 15,
+    .tm_mon     = 7 - 1,
+    .tm_year    = 2022 - 1900,
+    .tm_isdst   = 1
+};
+
+// UTC unix time for the winter time
+const time_t winter_time = 1647893720;
+const time_t summer_time = 1657882240;
+
+struct tz_test test_timezones[] = {
+    /*
+     * creating test vectors based on the POSIX spec (https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03)
+     */
+    // normal std names
+    {"MAR1",         IN_SECONDS(1, 0, 0),    NO_TIME},
+    {"MAR-1",       -IN_SECONDS(1, 0, 0),    NO_TIME},
+    {"MAR+2",        IN_SECONDS(2, 0, 0),    NO_TIME},
+    {"MAR7",         IN_SECONDS(7, 0, 0),    NO_TIME},
+    {"MAR-7",       -IN_SECONDS(7, 0, 0),    NO_TIME},
+    {"MARS5",        IN_SECONDS(5, 0, 0),    NO_TIME},
+    {"MARSM5",       IN_SECONDS(5, 0, 0),    NO_TIME},
+    {"MARSMOON5",    IN_SECONDS(5, 0, 0),    NO_TIME},   // assuming TZNAME_MAX >= 8
+    {"MARS5:23:42",  IN_SECONDS(5, 23, 42),  NO_TIME},
+    {"SUN-7:14:24", -IN_SECONDS(7, 14, 24),  NO_TIME},
+    // with DST
+    {"MAR5SMAR",                IN_SECONDS(5, 0, 0), IN_SECONDS(4, 0, 0)},  // only DST name
+    {"MAR5SMAR2",               IN_SECONDS(5, 0, 0), IN_SECONDS(2, 0, 0)},  // DST name with offset
+    {"MAR3SMAR-3",              IN_SECONDS(3, 0, 0), -IN_SECONDS(3, 0, 0)},
+    {"MARSWINTER4MARSUMMER",    IN_SECONDS(4, 0, 0), IN_SECONDS(3, 0, 0)},
+    {"MARSWINTER4MARSUMMER3",   IN_SECONDS(4, 0, 0), IN_SECONDS(3, 0, 0)},
+    // with DST IN_SECONDSs
+    {"WMARS3SMARS,J80",                                 IN_SECONDS(3, 0, 0), IN_SECONDS(2, 0, 0)},
+    {"WMARS3SMARS,J80,J134",                            IN_SECONDS(3, 0, 0), IN_SECONDS(2, 0, 0)},
+    {"WMARS3SMARS,79",                                  IN_SECONDS(3, 0, 0), IN_SECONDS(2, 0, 0)},
+    {"WMARS3SMARS,76,134",                              IN_SECONDS(3, 0, 0), IN_SECONDS(2, 0, 0)},
+    {"WMARS3SMARS,76/02,134/03",                        IN_SECONDS(3, 0, 0), IN_SECONDS(2, 0, 0)},
+    {"WMARS3SMARS,76/02:15:45,134/03:40:20",            IN_SECONDS(3, 0, 0), IN_SECONDS(2, 0, 0)},
+    {"WMARS3SMARS,M3.4.1/02:15:45,M8.3.1/03:40:20",     IN_SECONDS(3, 0, 0), IN_SECONDS(2, 0, 0)},
+
+    // special std names
+    {"<UNK>-1",                                 -IN_SECONDS(1, 0, 0),     NO_TIME},
+    {"<UNKNOWN>-2",                             -IN_SECONDS(2, 0, 0),     NO_TIME},                  // require TZNAME_MAX >= 7 + 1
+    {"<003>3",                                   IN_SECONDS(3, 0, 0),     NO_TIME},
+    {"<+04>4",                                   IN_SECONDS(4, 0, 0),     NO_TIME},
+    {"<-05>-5",                                 -IN_SECONDS(5, 0, 0),     NO_TIME},
+    {"<A-5>6",                                   IN_SECONDS(6, 0, 0),     NO_TIME},
+    {"<+A5>-7",                                 -IN_SECONDS(7, 0, 0),     NO_TIME},
+    {"<0123456>8",                               IN_SECONDS(8, 0, 0),     NO_TIME},
+    {"<0A1B2C3>9",                               IN_SECONDS(9, 0, 0),     NO_TIME},
+    {"<RD-04>-4<RD+005>5",                      -IN_SECONDS(4, 0, 0),     IN_SECONDS(5, 0, 0)},
+    {"<WINT+03>3<SUM+02>",                       IN_SECONDS(3, 0, 0),     IN_SECONDS(2, 0, 0)},
+    {"<WINT+03>3<SUM+02>2",                      IN_SECONDS(3, 0, 0),     IN_SECONDS(2, 0, 0)},
+    {"<WINT+03>3:15<SUM+02>2:30:15",             IN_SECONDS(3, 15, 0),    IN_SECONDS(2, 30, 15)},
+    {"<H3M15>3:15<H2M30S15>2:30:15",             IN_SECONDS(3, 15, 0),    IN_SECONDS(2, 30, 15)},   // requires TZNAME_MAX >= 8 + 1
+    {"<+H6M20S12>6:20:12<-H4M40S14>-4:40:14",    IN_SECONDS(6, 20, 12),  -IN_SECONDS(4, 40, 14)},   // requires TZNAME_MAX >= 9 + 1
+    {"<+0123456789ABCDEF>3:33:33",               IN_SECONDS(3, 33, 33),   NO_TIME},                 // truncates the name (17 + 1)
+
+    /* 
+     * real-world test vectors.
+     * IN_SECONDSzones extracted from the tzdb (https://github.com/eggert/tz#2019e).
+     * The IN_SECONDSzone strings can also be obtained from https://raw.githubusercontent.com/nayarsystems/posix_tz_db/master/zones.csv.
+     */
+    { /* Etc/GMT-14 */              "<+14>-14",                        -IN_SECONDS(14, 0, 0),     NO_TIME},
+    { /* Etc/GMT+12 */              "<-12>12",                          IN_SECONDS(12, 0, 0),     NO_TIME},
+    { /* Africa/Casablanca */       "<+01>-1",                         -IN_SECONDS(1, 0, 0),      NO_TIME},
+    { /* America/Araguaina */       "<-03>3",                           IN_SECONDS(3, 0, 0),      NO_TIME},
+    { /* America/Asuncion */        "<-04>4<-03>,M10.1.0/0,M3.4.0/0",   IN_SECONDS(4, 0, 0),      IN_SECONDS(3, 0, 0)},
+    { /* America/Los_Angeles */     "PST8PDT,M3.2.0,M11.1.0",           IN_SECONDS(8, 0, 0),      IN_SECONDS(7, 0, 0)},
+    { /* America/New_York */        "EST5EDT,M3.2.0,M11.1.0",           IN_SECONDS(5, 0, 0),      IN_SECONDS(4, 0, 0)},
+    { /* America/Scoresbysund */    "<-01>1<+00>,M3.5.0/0,M10.5.0/1",   IN_SECONDS(1, 0, 0),      IN_SECONDS(0, 0, 0)},
+    { /* Asia/Colombo */            "<+0530>-5:30",                    -IN_SECONDS(5, 30, 0),     NO_TIME},
+    { /* Europe/Berlin */           "CET-1CEST,M3.5.0,M10.5.0/3",      -IN_SECONDS(1, 0, 0),     -IN_SECONDS(2, 0, 0)},
+
+    // END of list
+    {NULL, NO_TIME, NO_TIME}
+};
+
+// helper macros
+#define FOR_TIMEZONES(iter_name) for (struct tz_test* iter_name = test_timezones; iter_name->tzstr != NULL; ++iter_name)
+
+// END test vectors
+
+static int failed = 0;
+
+#define TEST_ASSERT_EQUAL_INT_MESSAGE(...) assert_equal(__VA_ARGS__)
+void assert_equal(int lhs, int rhs, const char* msg)
+{
+    if (lhs != rhs)
+    {
+        printf("Assertion failed! Expected %d to equal %d. %s\n", lhs, rhs, msg);
+        ++failed;
+    }
+}
+
+void test_TimezoneStrings(void)
+{
+    char buffer[128];
+
+    FOR_TIMEZONES(ptr)
+    {
+        setenv("TZ", ptr->tzstr, 1);
+        tzset();
+
+        snprintf(buffer, 128, "winter time, timezone = \"%s\"", ptr->tzstr);
+
+        struct tm winter_tm_copy = winter_tm; // copy
+        TEST_ASSERT_EQUAL_INT_MESSAGE(winter_time + ptr->offset_seconds, mktime(&winter_tm_copy), buffer);
+
+        if (ptr->dst_offset_seconds != NO_TIME)
+        {
+            snprintf(buffer, 128, "summer time, timezone = \"%s\"", ptr->tzstr);
+
+            struct tm summer_tm_copy = summer_tm; // copy
+            TEST_ASSERT_EQUAL_INT_MESSAGE(summer_time + ptr->dst_offset_seconds, mktime(&summer_tm_copy), buffer);
+        }
+    }
+}
+
+int main()
+{
+    test_TimezoneStrings();
+    exit (failed);
+}
\ No newline at end of file
-- 
2.35.1


^ permalink raw reply	[flat|nested] 35+ messages in thread
* Re: [PATCH] add tests for tzset(3)
@ 2022-04-29 15:46 Jeff Johnston
  2022-05-12 18:35 ` jdoubleu
  0 siblings, 1 reply; 35+ messages in thread
From: Jeff Johnston @ 2022-04-29 15:46 UTC (permalink / raw)
  To: Newlib

[-- Attachment #1: Type: text/plain, Size: 212 bytes --]

I have revised my tzset_r.c patch so that the tzrules are initialized so we
don't inherit the previous settings if not specified for a particular TZ.
As well, I defaulted them if TZ is not specified.

-- Jeff J.

[-- Attachment #2: 0001-Modify-tzset_r.c-to-handle-errors.patch --]
[-- Type: text/x-patch, Size: 4850 bytes --]

From d49236849c69255090fa533177ac71ae3878cd7e Mon Sep 17 00:00:00 2001
From: Jeff Johnston <jjohnstn@redhat.com>
Date: Wed, 27 Apr 2022 15:27:00 -0400
Subject: [PATCH] Modify tzset_r.c to handle errors

- change __tzset_r so errors end up setting the timezone to
  unnamed UTC
---
 newlib/libc/time/tzset_r.c | 60 +++++++++++++++++++++++++++++++++-------------
 1 file changed, 44 insertions(+), 16 deletions(-)

diff --git a/newlib/libc/time/tzset_r.c b/newlib/libc/time/tzset_r.c
index 9cb30b1..962652d 100644
--- a/newlib/libc/time/tzset_r.c
+++ b/newlib/libc/time/tzset_r.c
@@ -23,7 +23,9 @@ _tzset_unlocked_r (struct _reent *reent_ptr)
   unsigned short hh, mm, ss, m, w, d;
   int sign, n;
   int i, ch;
+  long offset0, offset1;
   __tzinfo_type *tz = __gettzinfo ();
+  struct __tzrule_struct default_tzrule = {'J', 0, 0, 0, 0, (time_t)0, 0L };
 
   if ((tzenv = _getenv_r (reent_ptr, "TZ")) == NULL)
       {
@@ -31,6 +33,8 @@ _tzset_unlocked_r (struct _reent *reent_ptr)
 	_daylight = 0;
 	_tzname[0] = "GMT";
 	_tzname[1] = "GMT";
+	tz->__tzrule[0] = default_tzrule;
+	tz->__tzrule[1] = default_tzrule;
 	free(prev_tzenv);
 	prev_tzenv = NULL;
 	return;
@@ -44,6 +48,14 @@ _tzset_unlocked_r (struct _reent *reent_ptr)
   if (prev_tzenv != NULL)
     strcpy (prev_tzenv, tzenv);
 
+  /* default to unnamed UTC in case of error */
+  _timezone = 0;
+  _daylight = 0;
+  _tzname[0] = "";
+  _tzname[1] = "";
+  tz->__tzrule[0] = default_tzrule;
+  tz->__tzrule[1] = default_tzrule;
+
   /* ignore implementation-specific format specifier */
   if (*tzenv == ':')
     ++tzenv;  
@@ -85,8 +97,7 @@ _tzset_unlocked_r (struct _reent *reent_ptr)
   if (sscanf (tzenv, "%hu%n:%hu%n:%hu%n", &hh, &n, &mm, &n, &ss, &n) < 1)
     return;
   
-  tz->__tzrule[0].offset = sign * (ss + SECSPERMIN * mm + SECSPERHOUR * hh);
-  _tzname[0] = __tzname_std;
+  offset0 = sign * (ss + SECSPERMIN * mm + SECSPERHOUR * hh);
   tzenv += n;
 
   /* allow POSIX angle bracket < > quoted signed alphanumeric tz abbr e.g. <MESZ+0330> */
@@ -95,12 +106,16 @@ _tzset_unlocked_r (struct _reent *reent_ptr)
       ++tzenv;
 
       /* quit if no items, too few or too many chars, or no close quote '>' */
-      if (sscanf (tzenv, "%10[-+0-9A-Za-z]%n", __tzname_dst, &n) <= 0
-		|| n < TZNAME_MIN || TZNAME_MAX < n || '>' != tzenv[n])
+      if (sscanf (tzenv, "%10[-+0-9A-Za-z]%n", __tzname_dst, &n) <= 0 && tzenv[0] == '>')
 	{ /* No dst */
-	  _tzname[1] = _tzname[0];
-	  _timezone = tz->__tzrule[0].offset;
-	  _daylight = 0;
+          _tzname[0] = __tzname_std;
+          _tzname[1] = _tzname[0];
+          tz->__tzrule[0].offset = offset0;
+          _timezone = offset0;
+	  return;
+        }
+      else if (n < TZNAME_MIN || TZNAME_MAX < n || '>' != tzenv[n])
+	{ /* error */
 	  return;
 	}
 
@@ -109,17 +124,20 @@ _tzset_unlocked_r (struct _reent *reent_ptr)
   else
     {
       /* allow POSIX unquoted alphabetic tz abbr e.g. MESZ */
-      if (sscanf (tzenv, "%10[A-Za-z]%n", __tzname_dst, &n) <= 0
-				|| n < TZNAME_MIN || TZNAME_MAX < n)
+      if (sscanf (tzenv, "%10[A-Za-z]%n", __tzname_dst, &n) <= 0)
 	{ /* No dst */
-	  _tzname[1] = _tzname[0];
-	  _timezone = tz->__tzrule[0].offset;
-	  _daylight = 0;
+          _tzname[0] = __tzname_std;
+          _tzname[1] = _tzname[0];
+          tz->__tzrule[0].offset = offset0;
+          _timezone = offset0;
+	  return;
+        }
+      else if (n < TZNAME_MIN || TZNAME_MAX < n)
+	{ /* error */
 	  return;
 	}
     }
 
-  _tzname[1] = __tzname_dst;
   tzenv += n;
 
   /* otherwise we have a dst name, look for the offset */
@@ -138,9 +156,9 @@ _tzset_unlocked_r (struct _reent *reent_ptr)
   
   n  = 0;
   if (sscanf (tzenv, "%hu%n:%hu%n:%hu%n", &hh, &n, &mm, &n, &ss, &n) <= 0)
-    tz->__tzrule[1].offset = tz->__tzrule[0].offset - 3600;
+    offset1 = offset0 - 3600;
   else
-    tz->__tzrule[1].offset = sign * (ss + SECSPERMIN * mm + SECSPERHOUR * hh);
+    offset1 = sign * (ss + SECSPERMIN * mm + SECSPERHOUR * hh);
 
   tzenv += n;
 
@@ -211,13 +229,23 @@ _tzset_unlocked_r (struct _reent *reent_ptr)
       n = 0;
       
       if (*tzenv == '/')
-	sscanf (tzenv, "/%hu%n:%hu%n:%hu%n", &hh, &n, &mm, &n, &ss, &n);
+	if (sscanf (tzenv, "/%hu%n:%hu%n:%hu%n", &hh, &n, &mm, &n, &ss, &n) <= 0)
+	  {
+	    /* error in time format, restore tz rules to default and return */
+	    tz->__tzrule[0] = default_tzrule;
+	    tz->__tzrule[1] = default_tzrule;
+            return;
+          }
 
       tz->__tzrule[i].s = ss + SECSPERMIN * mm + SECSPERHOUR  * hh;
       
       tzenv += n;
     }
 
+  tz->__tzrule[0].offset = offset0;
+  tz->__tzrule[1].offset = offset1;
+  _tzname[0] = __tzname_std;
+  _tzname[1] = __tzname_dst;
   __tzcalc_limits (tz->__tzyear);
   _timezone = tz->__tzrule[0].offset;  
   _daylight = tz->__tzrule[0].offset != tz->__tzrule[1].offset;
-- 
1.8.3.1


^ permalink raw reply	[flat|nested] 35+ messages in thread

end of thread, other threads:[~2022-05-27 15:47 UTC | newest]

Thread overview: 35+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-04-07 15:58 [PATCH] add tests for tzset(3) jdoubleu
2022-04-08 21:21 ` Jeff Johnston
2022-04-10  8:43 ` Dimitar Dimitrov
2022-04-10 17:55   ` jdoubleu
2022-04-10 21:00     ` Dimitar Dimitrov
2022-04-11 11:17       ` jdoubleu
2022-04-11 17:27         ` Dimitar Dimitrov
2022-04-12 11:19           ` jdoubleu
2022-04-12 18:33             ` Brian Inglis
2022-04-07 23:34               ` [PATCH v2 0/2] add tzset/_r POSIX angle bracket <> support in TZ env var Brian Inglis
2022-04-07 23:34                 ` [PATCH v2 1/2] newlib/libc/time/tzset.c: doc update POSIX angle bracket <> support Brian Inglis
2022-04-07 23:34                 ` [PATCH v2 2/2] newlib/libc/time/tzset_r.c(_tzset_unlocked_r): " Brian Inglis
2022-04-08 19:11                 ` [PATCH v2 0/2] add tzset/_r POSIX angle bracket <> support in TZ env var Jeff Johnston
2022-04-13 17:53                 ` [PATCH] add tests for tzset(3) Brian Inglis
2022-04-13 20:33                   ` Jeff Johnston
2022-04-13 22:19                     ` Brian Inglis
2022-04-14  8:59                       ` jdoubleu
2022-04-14 16:31                         ` Brian Inglis
2022-04-14 19:23                           ` Jeff Johnston
2022-04-15 10:10                             ` jdoubleu
2022-04-27 19:30                               ` Jeff Johnston
2022-05-14 14:39                         ` jdoubleu
2022-05-16 16:05                           ` Dimitar Dimitrov
2022-05-16 17:38                             ` Jeff Johnston
2022-05-17  8:45                           ` [PATCH] update tzset tests jdoubleu
2022-05-18 18:48                             ` Dimitar Dimitrov
2022-05-18 20:56                               ` Keith Packard
2022-05-19  8:47                                 ` jdoubleu
2022-05-22  9:51                                   ` [PATCH v2] " jdoubleu
2022-05-22 21:02                                     ` Dimitar Dimitrov
2022-05-27 15:46                                       ` Jeff Johnston
2022-04-13 22:21               ` [PATCH] add tests for tzset(3) Brian Inglis
2022-04-29 15:46 Jeff Johnston
2022-05-12 18:35 ` jdoubleu
2022-05-16 17:49   ` Jeff Johnston

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).