[glib: 2/3] gtimezone: fix parsing of Julian day in POSIX TZ format



commit 3c0685ec4a7c08d529bb620312d2ec9dfd6ab125
Author: Daniel P. Berrange <berrange redhat com>
Date:   Thu Jan 9 22:19:00 2020 +0000

    gtimezone: fix parsing of Julian day in POSIX TZ format
    
    The timezone(3) man page on Fedora 31 describes the start/end
    field in the POSIX TZ format as follows:
    
    [quote]
       The start field specifies when daylight  saving  time  goes
       into  effect and the end field specifies when the change is
       made back to standard time.  These fields may have the fol‐
       lowing formats:
    
       Jn     This  specifies  the Julian day with n between 1 and
              365.  Leap days are not counted.   In  this  format,
              February 29 can't be represented; February 28 is day
              59, and March 1 is always day 60.
    
       n      This specifies the  zero-based  Julian  day  with  n
              between  0  and 365.  February 29 is counted in leap
              years.
    
       Mm.w.d This specifies day d (0 <= d <= 6) of week w (1 <= w
              <=  5)  of  month  m  (1 <= m <= 12).  Week 1 is the
              first week in which day d occurs and week 5  is  the
              last week in which day d occurs.  Day 0 is a Sunday.
    [/quote]
    
    The GTimeZone code does not correctly parse the 'n' syntax,
    treating it as having the range 1-365, the same as the 'Jn'
    syntax. This is semantically broken as it makes it impossible
    to represent the 366th day, which is the purpose of the 'n'
    syntax.
    
    There is a code comment saying this was done because the Linux
    semantics are different from zOS and BSD. This is not correct,
    as GLibC does indeed use the same 0-365 range as other operating
    systems. It is believed that the original author was mislead by
    a bug in old versions of the Linux libc timezone(3) man pages
    which was fixed in
    
      commit 5a554f8e525faa98354c1b95bfe4aca7125a3657
      Author: Peter Schiffer <pschiffe redhat com>
      Date:   Sat Mar 24 16:08:10 2012 +1300
    
        tzset.3: Correct description for Julian 'n' date format
    
        The Julian 'n' date format counts atrting from 0, not 1.
    
        Signed-off-by: Michael Kerrisk <mtk manpages gmail com>
    
    Fixes: #1999
    
    Signed-off-by: Daniel P. Berrange <berrange redhat com>

 glib/gtimezone.c       | 42 +++++++++++++++++++++++++++++-------------
 glib/tests/gdatetime.c | 46 ++++++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 73 insertions(+), 15 deletions(-)
---
diff --git a/glib/gtimezone.c b/glib/gtimezone.c
index dd4948e62..0155a5841 100644
--- a/glib/gtimezone.c
+++ b/glib/gtimezone.c
@@ -1203,14 +1203,21 @@ parse_mwd_boundary (gchar **pos, TimeZoneDate *boundary)
   return TRUE;
 }
 
-/* Different implementations of tzset interpret the Julian day field
-   differently. For example, Linux specifies that it should be 1-based
-   (1 Jan is JD 1) for both Jn and n formats, while zOS and BSD
-   specify that a Jn JD is 1-based while an n JD is 0-based. Rather
-   than trying to follow different specs, we will follow GDate's
-   practice thatIn order to keep it simple, we will follow Linux's
-   practice. */
-
+/*
+ * This parses two slightly different ways of specifying
+ * the Julian day:
+ *
+ * - ignore_leap == TRUE
+ *
+ *   Jn   This specifies the Julian day with n between 1 and 365. Leap days
+ *        are not counted. In this format, February 29 can't be represented;
+ *        February 28 is day 59, and March 1 is always day 60.
+ *
+ * - ignore_leap == FALSE
+ *
+ *   n   This specifies the zero-based Julian day with n between 0 and 365.
+ *       February 29 is counted in leap years.
+ */
 static gboolean
 parse_julian_boundary (gchar** pos, TimeZoneDate *boundary,
                        gboolean ignore_leap)
@@ -1224,8 +1231,20 @@ parse_julian_boundary (gchar** pos, TimeZoneDate *boundary,
       day += *(*pos)++ - '0';
     }
 
-  if (day < 1 || 365 < day)
-    return FALSE;
+  if (ignore_leap)
+    {
+      if (day < 1 || 365 < day)
+        return FALSE;
+      if (day >= 59)
+        day++;
+    }
+  else
+    {
+      if (day < 0 || 365 < day)
+        return FALSE;
+      /* GDate wants day in range 1->366 */
+      day++;
+    }
 
   g_date_clear (&date, 1);
   g_date_set_julian (&date, day);
@@ -1234,9 +1253,6 @@ parse_julian_boundary (gchar** pos, TimeZoneDate *boundary,
   boundary->mday = (int) g_date_get_day (&date);
   boundary->wday = 0;
 
-  if (ignore_leap && day >= 59)
-    boundary->mday++;
-
   return TRUE;
 }
 
diff --git a/glib/tests/gdatetime.c b/glib/tests/gdatetime.c
index 174a1df14..c88b411ff 100644
--- a/glib/tests/gdatetime.c
+++ b/glib/tests/gdatetime.c
@@ -2432,8 +2432,8 @@ test_posix_parse (void)
   g_date_time_unref (gdt2);
   g_time_zone_unref (tz);
 
-  tz = g_time_zone_new ("NZST-12:00:00NZDT-13:00:00,280,77");
-  g_assert_cmpstr (g_time_zone_get_identifier (tz), ==, "NZST-12:00:00NZDT-13:00:00,280,77");
+  tz = g_time_zone_new ("NZST-12:00:00NZDT-13:00:00,279,76");
+  g_assert_cmpstr (g_time_zone_get_identifier (tz), ==, "NZST-12:00:00NZDT-13:00:00,279,76");
   g_assert_cmpstr (g_time_zone_get_abbreviation (tz, 0), ==, "NZST");
   g_assert_cmpint (g_time_zone_get_offset (tz, 0), ==, 12 * 3600);
   g_assert (!g_time_zone_is_dst (tz, 0));
@@ -2533,6 +2533,48 @@ test_posix_parse (void)
   g_date_time_unref (gdt1);
   g_date_time_unref (gdt2);
   g_time_zone_unref (tz);
+
+  tz = g_time_zone_new ("VIR-00:30");
+  g_assert_cmpstr (g_time_zone_get_identifier (tz), ==, "VIR-00:30");
+  g_assert_cmpstr (g_time_zone_get_abbreviation (tz, 0), ==, "VIR");
+  g_assert_cmpint (g_time_zone_get_offset (tz, 0), ==, (30 * 60));
+  g_assert_false (g_time_zone_is_dst (tz, 0));
+
+  tz = g_time_zone_new ("VIR-00:30VID,0/00:00:00,365/23:59:59");
+  g_assert_cmpstr (g_time_zone_get_identifier (tz), ==, "VIR-00:30VID,0/00:00:00,365/23:59:59");
+  g_assert_cmpstr (g_time_zone_get_abbreviation (tz, 0), ==, "VIR");
+  g_assert_cmpint (g_time_zone_get_offset (tz, 0), ==, (30 * 60));
+  g_assert_false (g_time_zone_is_dst (tz, 0));
+  g_assert_cmpstr (g_time_zone_get_abbreviation (tz, 1), ==, "VID");
+  g_assert_cmpint (g_time_zone_get_offset (tz, 1), ==, (90 * 60));
+  g_assert_true (g_time_zone_is_dst (tz, 1));
+
+  tz = g_time_zone_new ("VIR-02:30VID,0/00:00:00,365/23:59:59");
+  g_assert_cmpstr (g_time_zone_get_identifier (tz), ==, "VIR-02:30VID,0/00:00:00,365/23:59:59");
+  g_assert_cmpstr (g_time_zone_get_abbreviation (tz, 0), ==, "VIR");
+  g_assert_cmpint (g_time_zone_get_offset (tz, 0), ==, (150 * 60));
+  g_assert_false (g_time_zone_is_dst (tz, 0));
+  g_assert_cmpstr (g_time_zone_get_abbreviation (tz, 1), ==, "VID");
+  g_assert_cmpint (g_time_zone_get_offset (tz, 1), ==, (210 * 60));
+  g_assert_true (g_time_zone_is_dst (tz, 1));
+
+  tz = g_time_zone_new ("VIR-02:30VID-04:30,0/00:00:00,365/23:59:59");
+  g_assert_cmpstr (g_time_zone_get_identifier (tz), ==, "VIR-02:30VID-04:30,0/00:00:00,365/23:59:59");
+  g_assert_cmpstr (g_time_zone_get_abbreviation (tz, 0), ==, "VIR");
+  g_assert_cmpint (g_time_zone_get_offset (tz, 0), ==, (150 * 60));
+  g_assert_false (g_time_zone_is_dst (tz, 0));
+  g_assert_cmpstr (g_time_zone_get_abbreviation (tz, 1), ==, "VID");
+  g_assert_cmpint (g_time_zone_get_offset (tz, 1), ==, (270 * 60));
+  g_assert_true (g_time_zone_is_dst (tz, 1));
+
+  tz = g_time_zone_new ("VIR-12:00VID-13:00,0/00:00:00,365/23:59:59");
+  g_assert_cmpstr (g_time_zone_get_identifier (tz), ==, "VIR-12:00VID-13:00,0/00:00:00,365/23:59:59");
+  g_assert_cmpstr (g_time_zone_get_abbreviation (tz, 0), ==, "VIR");
+  g_assert_cmpint (g_time_zone_get_offset (tz, 0), ==, (720 * 60));
+  g_assert_false (g_time_zone_is_dst (tz, 0));
+  g_assert_cmpstr (g_time_zone_get_abbreviation (tz, 1), ==, "VID");
+  g_assert_cmpint (g_time_zone_get_offset (tz, 1), ==, (780 * 60));
+  g_assert_true (g_time_zone_is_dst (tz, 1));
 }
 
 static void


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]