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>
This commit is contained in:
Daniel P. Berrange 2020-01-09 22:19:00 +00:00 committed by Daniel P. Berrangé
parent 6cd263f655
commit 3c0685ec4a
2 changed files with 73 additions and 15 deletions

View File

@ -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;
}

View File

@ -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