From 6cd263f6551abc883ccb98c8d9e8b28992c01ecf Mon Sep 17 00:00:00 2001 From: "Daniel P. Berrange" Date: Thu, 9 Jan 2020 22:37:31 +0000 Subject: [PATCH 1/2] gtimezone: fix inverted ignore_leap param parsing julian day The callers of parse_tz_boundary inverted the value passed for the ignore_leap parameter. Fortunately the method impl also had an inverted test cancelling out the first bug. Signed-off-by: Daniel P. Berrange --- glib/gtimezone.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/glib/gtimezone.c b/glib/gtimezone.c index 5d2efcbf3..dd4948e62 100644 --- a/glib/gtimezone.c +++ b/glib/gtimezone.c @@ -1234,7 +1234,7 @@ parse_julian_boundary (gchar** pos, TimeZoneDate *boundary, boundary->mday = (int) g_date_get_day (&date); boundary->wday = 0; - if (!ignore_leap && day >= 59) + if (ignore_leap && day >= 59) boundary->mday++; return TRUE; @@ -1258,13 +1258,13 @@ parse_tz_boundary (const gchar *identifier, else if (*pos == 'J') { ++pos; - if (!parse_julian_boundary (&pos, boundary, FALSE)) + if (!parse_julian_boundary (&pos, boundary, TRUE)) return FALSE ; } /* Julian date which counts Feb 29 in leap years */ else if (*pos >= '0' && '9' >= *pos) { - if (!parse_julian_boundary (&pos, boundary, TRUE)) + if (!parse_julian_boundary (&pos, boundary, FALSE)) return FALSE; } else From 3c0685ec4a7c08d529bb620312d2ec9dfd6ab125 Mon Sep 17 00:00:00 2001 From: "Daniel P. Berrange" Date: Thu, 9 Jan 2020 22:19:00 +0000 Subject: [PATCH 2/2] gtimezone: fix parsing of Julian day in POSIX TZ format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 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 Fixes: #1999 Signed-off-by: Daniel P. Berrange --- 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