Imported Upstream version 2.67.2
[platform/upstream/glib.git] / glib / tests / gdatetime.c
index 9afcf39..bc4eba9 100644 (file)
@@ -18,6 +18,7 @@
 
 #include "config.h"
 
+#include <math.h>
 #include <string.h>
 #include <time.h>
 #include <gi18n.h>
 #ifdef G_OS_WIN32
 #define WIN32_LEAN_AND_MEAN
 #include <windows.h>
+
+#ifndef NAN
+#define NAN HUGE_VAL * 0.0f
+#endif
 #endif
 
 #define ASSERT_DATE(dt,y,m,d) G_STMT_START { \
@@ -152,7 +157,7 @@ test_GDateTime_new_from_unix (void)
   g_date_time_unref (dt);
 }
 
-/* Check that trying to create a #GDateTime too far in the future reliably
+/* Check that trying to create a #GDateTime too far in the future (or past) reliably
  * fails. Previously, the checks for this overflowed and it silently returned
  * an incorrect #GDateTime. */
 static void
@@ -160,13 +165,19 @@ test_GDateTime_new_from_unix_overflow (void)
 {
   GDateTime *dt;
 
-  g_test_bug ("782089");
+  g_test_bug ("http://bugzilla.gnome.org/782089");
 
   dt = g_date_time_new_from_unix_utc (G_MAXINT64);
   g_assert_null (dt);
 
   dt = g_date_time_new_from_unix_local (G_MAXINT64);
   g_assert_null (dt);
+
+  dt = g_date_time_new_from_unix_utc (G_MININT64);
+  g_assert_null (dt);
+
+  dt = g_date_time_new_from_unix_local (G_MININT64);
+  g_assert_null (dt);
 }
 
 static void
@@ -174,7 +185,7 @@ test_GDateTime_invalid (void)
 {
   GDateTime *dt;
 
-  g_test_bug ("702674");
+  g_test_bug ("http://bugzilla.gnome.org/702674");
 
   dt = g_date_time_new_utc (2013, -2147483647, 31, 17, 15, 48);
   g_assert (dt == NULL);
@@ -228,7 +239,8 @@ test_GDateTime_equal (void)
   g_date_time_unref (dt2);
 
   /* UTC-0300 and not in DST */
-  tz = g_time_zone_new ("-03:00");
+  tz = g_time_zone_new_identifier ("-03:00");
+  g_assert_nonnull (tz);
   dt1 = g_date_time_new (tz, 2010, 5, 24,  8, 0, 0);
   g_time_zone_unref (tz);
   g_assert_cmpint (g_date_time_get_utc_offset (dt1) / G_USEC_PER_SEC, ==, (-3 * 3600));
@@ -241,10 +253,11 @@ test_GDateTime_equal (void)
 
   /* America/Recife is in UTC-0300 */
 #ifdef G_OS_UNIX
-  tz = g_time_zone_new ("America/Recife");
+  tz = g_time_zone_new_identifier ("America/Recife");
 #elif defined G_OS_WIN32
-  tz = g_time_zone_new ("E. South America Standard Time");
+  tz = g_time_zone_new_identifier ("E. South America Standard Time");
 #endif
+  g_assert_nonnull (tz);
   dt1 = g_date_time_new (tz, 2010, 5, 24,  8, 0, 0);
   g_time_zone_unref (tz);
   g_assert_cmpint (g_date_time_get_utc_offset (dt1) / G_USEC_PER_SEC, ==, (-3 * 3600));
@@ -435,7 +448,7 @@ test_GDateTime_new_from_timeval_overflow (void)
   GDateTime *dt;
   GTimeVal tv;
 
-  g_test_bug ("782089");
+  g_test_bug ("http://bugzilla.gnome.org/782089");
 
   tv.tv_sec = find_maximum_supported_tv_sec ();
   tv.tv_usec = G_USEC_PER_SEC - 1;
@@ -499,6 +512,9 @@ test_GDateTime_new_from_iso8601 (void)
   dt = g_date_time_new_from_iso8601 ("not a date", NULL);
   g_assert_null (dt);
 
+  dt = g_date_time_new_from_iso8601 (" +55", NULL);
+  g_assert_null (dt);
+
   /* Check common case */
   dt = g_date_time_new_from_iso8601 ("2016-08-24T22:10:42Z", NULL);
   ASSERT_DATE (dt, 2016, 8, 24);
@@ -731,6 +747,14 @@ test_GDateTime_new_from_iso8601 (void)
   dt = g_date_time_new_from_iso8601 ("--0824T22:10:42Z", NULL);
   g_assert_null (dt);
 
+  /* Seconds must be two digits. */
+  dt = g_date_time_new_from_iso8601 ("2016-08-10T22:10:4Z", NULL);
+  g_assert_null (dt);
+
+  /* Seconds must all be digits. */
+  dt = g_date_time_new_from_iso8601 ("2016-08-10T22:10:4aZ", NULL);
+  g_assert_null (dt);
+
   /* Check subseconds work */
   dt = g_date_time_new_from_iso8601 ("2016-08-24T22:10:42.123456Z", NULL);
   ASSERT_DATE (dt, 2016, 8, 24);
@@ -747,6 +771,28 @@ test_GDateTime_new_from_iso8601 (void)
   ASSERT_TIME (dt, 22, 10, 42, 123456);
   g_date_time_unref (dt);
 
+  /* Subseconds must all be digits. */
+  dt = g_date_time_new_from_iso8601 ("2016-08-10T22:10:42.5aZ", NULL);
+  g_assert_null (dt);
+
+  /* Subseconds can be an arbitrary length, but must not overflow.
+   * The ASSERT_TIME() comparisons are constrained by only comparing up to
+   * microsecond granularity. */
+  dt = g_date_time_new_from_iso8601 ("2016-08-10T22:10:09.222222222222222222Z", NULL);
+  ASSERT_DATE (dt, 2016, 8, 10);
+  ASSERT_TIME (dt, 22, 10, 9, 222222);
+  g_date_time_unref (dt);
+  dt = g_date_time_new_from_iso8601 ("2016-08-10T22:10:09.2222222222222222222Z", NULL);
+  g_assert_null (dt);
+
+  /* Small numerator, large divisor when parsing the subseconds. */
+  dt = g_date_time_new_from_iso8601 ("2016-08-10T22:10:00.0000000000000000001Z", NULL);
+  ASSERT_DATE (dt, 2016, 8, 10);
+  ASSERT_TIME (dt, 22, 10, 0, 0);
+  g_date_time_unref (dt);
+  dt = g_date_time_new_from_iso8601 ("2016-08-10T22:10:00.00000000000000000001Z", NULL);
+  g_assert_null (dt);
+
   /* We don't support times without minutes / seconds (valid ISO 8601) */
   dt = g_date_time_new_from_iso8601 ("2016-08-24T22Z", NULL);
   g_assert_null (dt);
@@ -788,6 +834,122 @@ test_GDateTime_new_from_iso8601 (void)
   /* Timezone hours two digits */
   dt = g_date_time_new_from_iso8601 ("2016-08-24T22-2Z", NULL);
   g_assert_null (dt);
+
+  /* Ordinal date (YYYYDDD), space separator, and then time as HHMMSS,SSS
+   * The interesting bit is that the seconds field is so long as to parse as
+   * NaN */
+  dt = g_date_time_new_from_iso8601 ("0005306 000001,666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666600080000-00", NULL);
+  g_assert_null (dt);
+}
+
+typedef struct {
+  gboolean success;
+  const gchar *in;
+
+  /* Expected result: */
+  guint year;
+  guint month;
+  guint day;
+  guint hour;
+  guint minute;
+  guint second;
+  guint microsecond;
+  GTimeSpan utc_offset;
+} Iso8601ParseTest;
+
+static void
+test_GDateTime_new_from_iso8601_2 (void)
+{
+  const Iso8601ParseTest tests[] = {
+    { TRUE, "1990-11-01T10:21:17Z", 1990, 11, 1, 10, 21, 17, 0, 0 },
+    { TRUE, "19901101T102117Z", 1990, 11, 1, 10, 21, 17, 0, 0 },
+    { TRUE, "1970-01-01T00:00:17.12Z", 1970, 1, 1, 0, 0, 17, 120000, 0 },
+    { TRUE, "1970-01-01T00:00:17.1234Z", 1970, 1, 1, 0, 0, 17, 123400, 0 },
+    { TRUE, "1970-01-01T00:00:17.123456Z", 1970, 1, 1, 0, 0, 17, 123456, 0 },
+    { TRUE, "1980-02-22T12:36:00+02:00", 1980, 2, 22, 12, 36, 0, 0, 2 * G_TIME_SPAN_HOUR },
+    { TRUE, "1990-12-31T15:59:60-08:00", 1990, 12, 31, 15, 59, 59, 0, -8 * G_TIME_SPAN_HOUR },
+    { FALSE, "   ", 0, 0, 0, 0, 0, 0, 0, 0 },
+    { FALSE, "x", 0, 0, 0, 0, 0, 0, 0, 0 },
+    { FALSE, "123x", 0, 0, 0, 0, 0, 0, 0, 0 },
+    { FALSE, "2001-10+x", 0, 0, 0, 0, 0, 0, 0, 0 },
+    { FALSE, "1980-02-22T", 0, 0, 0, 0, 0, 0, 0, 0 },
+    { FALSE, "2001-10-08Tx", 0, 0, 0, 0, 0, 0, 0, 0 },
+    { FALSE, "2001-10-08T10:11x", 0, 0, 0, 0, 0, 0, 0, 0 },
+    { FALSE, "Wed Dec 19 17:20:20 GMT 2007", 0, 0, 0, 0, 0, 0, 0, 0 },
+    { FALSE, "1980-02-22T10:36:00Zulu", 0, 0, 0, 0, 0, 0, 0, 0 },
+    { FALSE, "2T0+819855292164632335", 0, 0, 0, 0, 0, 0, 0, 0 },
+    { TRUE, "2018-08-03T14:08:05.446178377+01:00", 2018, 8, 3, 14, 8, 5, 446178, 1 * G_TIME_SPAN_HOUR },
+    { FALSE, "2147483648-08-03T14:08:05.446178377+01:00", 0, 0, 0, 0, 0, 0, 0, 0 },
+    { FALSE, "2018-13-03T14:08:05.446178377+01:00", 0, 0, 0, 0, 0, 0, 0, 0 },
+    { FALSE, "2018-00-03T14:08:05.446178377+01:00", 0, 0, 0, 0, 0, 0, 0, 0 },
+    { FALSE, "2018-08-00T14:08:05.446178377+01:00", 0, 0, 0, 0, 0, 0, 0, 0 },
+    { FALSE, "2018-08-32T14:08:05.446178377+01:00", 0, 0, 0, 0, 0, 0, 0, 0 },
+    { FALSE, "2018-08-03T24:08:05.446178377+01:00", 0, 0, 0, 0, 0, 0, 0, 0 },
+    { FALSE, "2018-08-03T14:60:05.446178377+01:00", 0, 0, 0, 0, 0, 0, 0, 0 },
+    { FALSE, "2018-08-03T14:08:63.446178377+01:00", 0, 0, 0, 0, 0, 0, 0, 0 },
+    { FALSE, "2018-08-03T14:08:05.446178377+100:00", 0, 0, 0, 0, 0, 0, 0, 0 },
+    { TRUE, "20180803T140805.446178377+0100", 2018, 8, 3, 14, 8, 5, 446178, 1 * G_TIME_SPAN_HOUR },
+    { FALSE, "21474836480803T140805.446178377+0100", 0, 0, 0, 0, 0, 0, 0, 0 },
+    { FALSE, "20181303T140805.446178377+0100", 0, 0, 0, 0, 0, 0, 0, 0 },
+    { FALSE, "20180003T140805.446178377+0100", 0, 0, 0, 0, 0, 0, 0, 0 },
+    { FALSE, "20180800T140805.446178377+0100", 0, 0, 0, 0, 0, 0, 0, 0 },
+    { FALSE, "20180832T140805.446178377+0100", 0, 0, 0, 0, 0, 0, 0, 0 },
+    { FALSE, "20180803T240805.446178377+0100", 0, 0, 0, 0, 0, 0, 0, 0 },
+    { FALSE, "20180803T146005.446178377+0100", 0, 0, 0, 0, 0, 0, 0, 0 },
+    { FALSE, "20180803T140863.446178377+0100", 0, 0, 0, 0, 0, 0, 0, 0 },
+    { FALSE, "20180803T140805.446178377+10000", 0, 0, 0, 0, 0, 0, 0, 0 },
+    { FALSE, "-0005-01-01T00:00:00Z", 0, 0, 0, 0, 0, 0, 0, 0 },
+    { FALSE, "2018-08-06", 0, 0, 0, 0, 0, 0, 0, 0 },
+    /* This is not accepted by g_time_val_from_iso8601(), but is accepted by g_date_time_new_from_iso8601():
+    { FALSE, "2018-08-06 13:51:00Z", 0, 0, 0, 0, 0, 0, 0, 0 },
+    * */
+    { TRUE, "20180803T140805,446178377+0100", 2018, 8, 3, 14, 8, 5, 446178, 1 * G_TIME_SPAN_HOUR },
+    { TRUE, "2018-08-03T14:08:05.446178377-01:00", 2018, 8, 3, 14, 8, 5, 446178, -1 * G_TIME_SPAN_HOUR },
+    { FALSE, "2018-08-03T14:08:05.446178377 01:00", 0, 0, 0, 0, 0, 0, 0, 0 },
+    { TRUE, "1990-11-01T10:21:17", 1990, 11, 1, 10, 21, 17, 0, 0 },
+    /* These are accepted by g_time_val_from_iso8601(), but not by g_date_time_new_from_iso8601():
+    { TRUE, "19901101T102117+5", 1990, 11, 1, 10, 21, 17, 0, 5 * G_TIME_SPAN_HOUR },
+    { TRUE, "19901101T102117+3:15", 1990, 11, 1, 10, 21, 17, 0, 3 * G_TIME_SPAN_HOUR + 15 * G_TIME_SPAN_MINUTE },
+    { TRUE, "  1990-11-01T10:21:17Z  ", 1990, 11, 1, 10, 21, 17, 0, 0 },
+    { FALSE, "2018-08-03T14:08:05.446178377+01:60", 0, 0, 0, 0, 0, 0, 0, 0 },
+    { FALSE, "20180803T140805.446178377+0160", 0, 0, 0, 0, 0, 0, 0, 0 },
+    { TRUE, "+1980-02-22T12:36:00+02:00", 1980, 2, 22, 12, 36, 0, 0, 2 * G_TIME_SPAN_HOUR },
+    { TRUE, "1990-11-01T10:21:17     ", 1990, 11, 1, 10, 21, 17, 0, 0 },
+    */
+    { FALSE, "1719W462 407777-07", 0, 0, 0, 0, 0, 0, 0, 0 },
+    { FALSE, "4011090 260528Z", 0, 0, 0, 0, 0, 0, 0, 0 },
+    { FALSE, "0000W011 228214-22", 0, 0, 0, 0, 0, 0, 0, 0 },
+  };
+  GTimeZone *tz = NULL;
+  GDateTime *dt = NULL;
+  gsize i;
+
+  g_test_summary ("Further parser tests for g_date_time_new_from_iso8601(), "
+                  "checking success and failure using test vectors.");
+
+  tz = g_time_zone_new_utc ();
+
+  for (i = 0; i < G_N_ELEMENTS (tests); i++)
+    {
+      g_test_message ("Vector %" G_GSIZE_FORMAT ": %s", i, tests[i].in);
+
+      dt = g_date_time_new_from_iso8601 (tests[i].in, tz);
+      if (tests[i].success)
+        {
+          g_assert_nonnull (dt);
+          ASSERT_DATE (dt, tests[i].year, tests[i].month, tests[i].day);
+          ASSERT_TIME (dt, tests[i].hour, tests[i].minute, tests[i].second, tests[i].microsecond);
+          g_assert_cmpint (g_date_time_get_utc_offset (dt), ==, tests[i].utc_offset);
+        }
+      else
+        {
+          g_assert_null (dt);
+        }
+
+      g_clear_pointer (&dt, g_date_time_unref);
+    }
+
+  g_time_zone_unref (tz);
 }
 
 static void
@@ -1055,10 +1217,11 @@ test_GDateTime_new_full (void)
   g_date_time_unref (dt);
 
 #ifdef G_OS_UNIX
-  tz = g_time_zone_new ("America/Tijuana");
+  tz = g_time_zone_new_identifier ("America/Tijuana");
 #elif defined G_OS_WIN32
-  tz = g_time_zone_new ("Pacific Standard Time");
+  tz = g_time_zone_new_identifier ("Pacific Standard Time");
 #endif
+  g_assert_nonnull (tz);
   dt = g_date_time_new (tz, 2010, 11, 24, 8, 4, 0);
 
   dt_tz = g_date_time_get_timezone (dt);
@@ -1151,6 +1314,18 @@ test_GDateTime_new_full (void)
   g_date_time_unref (dt);
   dt = g_date_time_new_utc (2016, 12, 32, 22, 10, 42);
   g_assert_null (dt);
+
+  /* Seconds limits. */
+  dt = g_date_time_new_utc (2020, 12, 9, 14, 49, NAN);
+  g_assert_null (dt);
+  dt = g_date_time_new_utc (2020, 12, 9, 14, 49, -0.1);
+  g_assert_null (dt);
+  dt = g_date_time_new_utc (2020, 12, 9, 14, 49, 60.0);
+  g_assert_null (dt);
+
+  /* Year limits */
+  dt = g_date_time_new_utc (10000, 1, 1, 0, 0, 0);
+  dt = g_date_time_new_utc (0, 1, 1, 0, 0, 0);
 }
 
 static void
@@ -1404,12 +1579,6 @@ GDateTime *__dt = g_date_time_new_local (2009, 10, 24, 0, 0, 0);\
   get_localtime_tm (t, &tt);
   strftime (dst, sizeof(dst), "%Z", &tt);
 
-  /* get current time_t for 20090924 in the local timezone */
-  tt.tm_sec = 0;
-  tt.tm_min = 0;
-  tt.tm_hour = 0;
-  t = mktime (&tt);
-
   TEST_PRINTF ("%a", "Sat");
   TEST_PRINTF ("%A", "Saturday");
   TEST_PRINTF ("%b", "Oct");
@@ -1417,6 +1586,7 @@ GDateTime *__dt = g_date_time_new_local (2009, 10, 24, 0, 0, 0);\
   TEST_PRINTF ("%d", "24");
   TEST_PRINTF_DATE (2009, 1, 1, "%d", "01");
   TEST_PRINTF ("%e", "24"); // fixme
+  TEST_PRINTF_TIME (10, 10, 1.001, "%f", "001000");
   TEST_PRINTF ("%h", "Oct");
   TEST_PRINTF ("%H", "00");
   TEST_PRINTF_TIME (15, 0, 0, "%H", "15");
@@ -1659,7 +1829,7 @@ test_month_names (void)
 {
   gchar *oldlocale;
 
-  g_test_bug ("749206");
+  g_test_bug ("http://bugzilla.gnome.org/749206");
 
   /* If running uninstalled (G_TEST_BUILDDIR is set), skip this test, since we
    * need the translations to be installed. We can’t mess around with
@@ -1818,10 +1988,11 @@ test_GDateTime_dst (void)
 
   /* this date has the DST state set for Europe/London */
 #ifdef G_OS_UNIX
-  tz = g_time_zone_new ("Europe/London");
+  tz = g_time_zone_new_identifier ("Europe/London");
 #elif defined G_OS_WIN32
-  tz = g_time_zone_new ("GMT Standard Time");
+  tz = g_time_zone_new_identifier ("GMT Standard Time");
 #endif
+  g_assert_nonnull (tz);
   dt1 = g_date_time_new (tz, 2009, 8, 15, 3, 0, 1);
   g_assert (g_date_time_is_daylight_savings (dt1));
   g_assert_cmpint (g_date_time_get_utc_offset (dt1) / G_USEC_PER_SEC, ==, 3600);
@@ -2001,9 +2172,10 @@ test_z (void)
   GDateTime *dt;
   gchar *p;
 
-  g_test_bug ("642935");
+  g_test_bug ("http://bugzilla.gnome.org/642935");
 
-  tz = g_time_zone_new ("-08:00");
+  tz = g_time_zone_new_identifier ("-08:00");
+  g_assert_nonnull (tz);
   dt = g_date_time_new (tz, 1, 1, 1, 0, 0, 0);
 
   p = g_date_time_format (dt, "%z");
@@ -2025,7 +2197,8 @@ test_z (void)
   g_date_time_unref (dt);
   g_time_zone_unref (tz);
 
-  tz = g_time_zone_new ("+00:00");
+  tz = g_time_zone_new_identifier ("+00:00");
+  g_assert_nonnull (tz);
   dt = g_date_time_new (tz, 1, 1, 1, 0, 0, 0);
   p = g_date_time_format (dt, "%:::z");
   g_assert_cmpstr (p, ==, "+00");
@@ -2033,7 +2206,8 @@ test_z (void)
   g_date_time_unref (dt);
   g_time_zone_unref (tz);
 
-  tz = g_time_zone_new ("+08:23");
+  tz = g_time_zone_new_identifier ("+08:23");
+  g_assert_nonnull (tz);
   dt = g_date_time_new (tz, 1, 1, 1, 0, 0, 0);
   p = g_date_time_format (dt, "%:::z");
   g_assert_cmpstr (p, ==, "+08:23");
@@ -2041,7 +2215,8 @@ test_z (void)
   g_date_time_unref (dt);
   g_time_zone_unref (tz);
 
-  tz = g_time_zone_new ("+08:23:45");
+  tz = g_time_zone_new_identifier ("+08:23:45");
+  g_assert_nonnull (tz);
   dt = g_date_time_new (tz, 1, 1, 1, 0, 0, 0);
   p = g_date_time_format (dt, "%:::z");
   g_assert_cmpstr (p, ==, "+08:23:45");
@@ -2049,7 +2224,8 @@ test_z (void)
   g_date_time_unref (dt);
   g_time_zone_unref (tz);
 
-  tz = g_time_zone_new ("-00:15");
+  tz = g_time_zone_new_identifier ("-00:15");
+  g_assert_nonnull (tz);
   dt = g_date_time_new (tz, 1, 1, 1, 0, 0, 0);
 
   p = g_date_time_format (dt, "%z");
@@ -2073,6 +2249,44 @@ test_z (void)
 }
 
 static void
+test_6_days_until_end_of_the_month (void)
+{
+  GTimeZone *tz;
+  GDateTime *dt;
+  gchar *p;
+
+  g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/2215");
+
+#ifdef G_OS_UNIX
+  /* This is the footertz string from `Europe/Paris` from tzdata 2020b. It’s
+   * used by GLib when the tzdata file was compiled with `zic -b slim`, which is
+   * the default in tzcode ≥2020b.
+   *
+   * The `M10.5.0` part indicates that the summer time end transition happens on
+   * the Sunday (`0`) in the last week (`5`) of October (`10`). That’s 6 days
+   * before the end of the month, and hence was triggering issue #2215.
+   *
+   * References:
+   *  - https://tools.ietf.org/id/draft-murchison-tzdist-tzif-15.html#rfc.section.3.3
+   *  - https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03
+   */
+  tz = g_time_zone_new_identifier ("CET-1CEST,M3.5.0,M10.5.0/3");
+#elif defined (G_OS_WIN32)
+  tz = g_time_zone_new_identifier ("Romance Standard Time");
+#endif
+  g_assert_nonnull (tz);
+  dt = g_date_time_new (tz, 2020, 10, 5, 1, 1, 1);
+
+  p = g_date_time_format (dt, "%Y-%m-%d %H:%M:%S%z");
+  /* Incorrect output is  "2020-10-05 01:01:01+0100" */
+  g_assert_cmpstr (p, ==, "2020-10-05 01:01:01+0200");
+  g_free (p);
+
+  g_date_time_unref (dt);
+  g_time_zone_unref (tz);
+}
+
+static void
 test_format_iso8601 (void)
 {
   GTimeZone *tz = NULL;
@@ -2094,6 +2308,14 @@ test_format_iso8601 (void)
   g_free (p);
   g_date_time_unref (dt);
   g_time_zone_unref (tz);
+
+  tz = g_time_zone_new_utc ();
+  dt = g_date_time_new (tz, 2020, 8, 5, 12, 30, 55.000001);
+  p = g_date_time_format_iso8601 (dt);
+  g_assert_cmpstr (p, ==, "2020-08-05T12:30:55.000001Z");
+  g_free (p);
+  g_date_time_unref (dt);
+  g_time_zone_unref (tz);
 }
 
 #pragma GCC diagnostic push
@@ -2155,10 +2377,11 @@ test_find_interval (void)
   gint i1, i2;
 
 #ifdef G_OS_UNIX
-  tz = g_time_zone_new ("America/Toronto");
+  tz = g_time_zone_new_identifier ("America/Toronto");
 #elif defined G_OS_WIN32
-  tz = g_time_zone_new ("Eastern Standard Time");
+  tz = g_time_zone_new_identifier ("Eastern Standard Time");
 #endif
+  g_assert_nonnull (tz);
   dt = g_date_time_new_utc (2010, 11, 7, 1, 30, 0);
   u = g_date_time_to_unix (dt);
 
@@ -2188,10 +2411,11 @@ test_adjust_time (void)
   gint i1, i2;
 
 #ifdef G_OS_UNIX
-  tz = g_time_zone_new ("America/Toronto");
+  tz = g_time_zone_new_identifier ("America/Toronto");
 #elif defined G_OS_WIN32
-  tz = g_time_zone_new ("Eastern Standard Time");
+  tz = g_time_zone_new_identifier ("Eastern Standard Time");
 #endif
+  g_assert_nonnull (tz);
   dt = g_date_time_new_utc (2010, 11, 7, 1, 30, 0);
   u = g_date_time_to_unix (dt);
   u2 = u;
@@ -2213,6 +2437,7 @@ test_adjust_time (void)
   g_date_time_unref (dt);
 
   i1 = g_time_zone_adjust_time (tz, G_TIME_TYPE_DAYLIGHT, &u2);
+  g_assert_cmpint (i1, >=, 0);
   g_assert (u == u2);
 
   g_time_zone_unref (tz);
@@ -2223,7 +2448,9 @@ test_no_header (void)
 {
   GTimeZone *tz;
 
+  G_GNUC_BEGIN_IGNORE_DEPRECATIONS
   tz = g_time_zone_new ("blabla");
+  G_GNUC_END_IGNORE_DEPRECATIONS
 
   g_assert_cmpstr (g_time_zone_get_identifier (tz), ==, "UTC");
   g_assert_cmpstr (g_time_zone_get_abbreviation (tz, 0), ==, "UTC");
@@ -2234,13 +2461,25 @@ test_no_header (void)
 }
 
 static void
+test_no_header_identifier (void)
+{
+  GTimeZone *tz;
+
+  tz = g_time_zone_new_identifier ("blabla");
+
+  g_assert_null (tz);
+}
+
+static void
 test_posix_parse (void)
 {
   GTimeZone *tz;
   GDateTime *gdt1, *gdt2;
 
   /* Check that an unknown zone name falls back to UTC. */
+  G_GNUC_BEGIN_IGNORE_DEPRECATIONS
   tz = g_time_zone_new ("nonexistent");
+  G_GNUC_END_IGNORE_DEPRECATIONS
   g_assert_cmpstr (g_time_zone_get_identifier (tz), ==, "UTC");
   g_assert_cmpstr (g_time_zone_get_abbreviation (tz, 0), ==, "UTC");
   g_assert_cmpint (g_time_zone_get_offset (tz, 0), ==, 0);
@@ -2248,7 +2487,9 @@ test_posix_parse (void)
   g_time_zone_unref (tz);
 
   /* An existent zone name should not fall back to UTC. */
+  G_GNUC_BEGIN_IGNORE_DEPRECATIONS
   tz = g_time_zone_new ("PST8");
+  G_GNUC_END_IGNORE_DEPRECATIONS
   g_assert_cmpstr (g_time_zone_get_identifier (tz), ==, "PST8");
   g_assert_cmpstr (g_time_zone_get_abbreviation (tz, 0), ==, "PST");
   g_assert_cmpint (g_time_zone_get_offset (tz, 0), ==, - 8 * 3600);
@@ -2258,7 +2499,8 @@ test_posix_parse (void)
 /* This fails rules_from_identifier on Unix (though not on Windows)
  * but passes anyway because PST8PDT is a zone name.
  */
-  tz = g_time_zone_new ("PST8PDT");
+  tz = g_time_zone_new_identifier ("PST8PDT");
+  g_assert_nonnull (tz);
   g_assert_cmpstr (g_time_zone_get_identifier (tz), ==, "PST8PDT");
   g_assert_cmpstr (g_time_zone_get_abbreviation (tz, 0), ==, "PST");
   g_assert_cmpint (g_time_zone_get_offset (tz, 0), ==, - 8 * 3600);
@@ -2268,8 +2510,9 @@ test_posix_parse (void)
   g_assert (g_time_zone_is_dst (tz, 1));
   g_time_zone_unref (tz);
 
-  tz = g_time_zone_new ("PST8PDT6:32:15");
+  tz = g_time_zone_new_identifier ("PST8PDT6:32:15");
 #ifdef G_OS_WIN32
+  g_assert_nonnull (tz);
   g_assert_cmpstr (g_time_zone_get_identifier (tz), ==, "PST8PDT6:32:15");
   g_assert_cmpstr (g_time_zone_get_abbreviation (tz, 0), ==, "PST");
   g_assert_cmpint (g_time_zone_get_offset (tz, 0), ==, - 8 * 3600);
@@ -2286,14 +2529,12 @@ test_posix_parse (void)
   g_date_time_unref (gdt1);
   g_date_time_unref (gdt2);
 #else
-  g_assert_cmpstr (g_time_zone_get_identifier (tz), ==, "UTC");
-  g_assert_cmpstr (g_time_zone_get_abbreviation (tz, 0), ==, "UTC");
-  g_assert_cmpint (g_time_zone_get_offset (tz, 0), ==, 0);
-  g_assert (!g_time_zone_is_dst (tz, 0));
+  g_assert_null (tz);
 #endif
-  g_time_zone_unref (tz);
+  g_clear_pointer (&tz, g_time_zone_unref);
 
-  tz = g_time_zone_new ("NZST-12:00:00NZDT-13:00:00,M10.1.0,M3.3.0");
+  tz = g_time_zone_new_identifier ("NZST-12:00:00NZDT-13:00:00,M10.1.0,M3.3.0");
+  g_assert_nonnull (tz);
   g_assert_cmpstr (g_time_zone_get_identifier (tz), ==, "NZST-12:00:00NZDT-13:00:00,M10.1.0,M3.3.0");
   g_assert_cmpstr (g_time_zone_get_abbreviation (tz, 0), ==, "NZST");
   g_assert_cmpint (g_time_zone_get_offset (tz, 0), ==, 12 * 3600);
@@ -2319,8 +2560,9 @@ 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_identifier ("NZST-12:00:00NZDT-13:00:00,279,76");
+  g_assert_nonnull (tz);
+  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));
@@ -2345,7 +2587,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,J279,J76");
+  tz = g_time_zone_new_identifier ("NZST-12:00:00NZDT-13:00:00,J279,J76");
+  g_assert_nonnull (tz);
   g_assert_cmpstr (g_time_zone_get_identifier (tz), ==, "NZST-12:00:00NZDT-13:00:00,J279,J76");
   g_assert_cmpstr (g_time_zone_get_abbreviation (tz, 0), ==, "NZST");
   g_assert_cmpint (g_time_zone_get_offset (tz, 0), ==, 12 * 3600);
@@ -2371,7 +2614,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,M10.1.0/07:00,M3.3.0/07:00");
+  tz = g_time_zone_new_identifier ("NZST-12:00:00NZDT-13:00:00,M10.1.0/07:00,M3.3.0/07:00");
+  g_assert_nonnull (tz);
   g_assert_cmpstr (g_time_zone_get_identifier (tz), ==, "NZST-12:00:00NZDT-13:00:00,M10.1.0/07:00,M3.3.0/07:00");
   g_assert_cmpstr (g_time_zone_get_abbreviation (tz, 0), ==, "NZST");
   g_assert_cmpint (g_time_zone_get_offset (tz, 0), ==, 12 * 3600);
@@ -2420,6 +2664,53 @@ test_posix_parse (void)
   g_date_time_unref (gdt1);
   g_date_time_unref (gdt2);
   g_time_zone_unref (tz);
+
+  tz = g_time_zone_new_identifier ("VIR-00:30");
+  g_assert_nonnull (tz);
+  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_identifier ("VIR-00:30VID,0/00:00:00,365/23:59:59");
+  g_assert_nonnull (tz);
+  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_identifier ("VIR-02:30VID,0/00:00:00,365/23:59:59");
+  g_assert_nonnull (tz);
+  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_identifier ("VIR-02:30VID-04:30,0/00:00:00,365/23:59:59");
+  g_assert_nonnull (tz);
+  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_identifier ("VIR-12:00VID-13:00,0/00:00:00,365/23:59:59");
+  g_assert_nonnull (tz);
+  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
@@ -2428,9 +2719,10 @@ test_GDateTime_floating_point (void)
   GDateTime *dt;
   GTimeZone *tz;
 
-  g_test_bug ("697715");
+  g_test_bug ("http://bugzilla.gnome.org/697715");
 
-  tz = g_time_zone_new ("-03:00");
+  tz = g_time_zone_new_identifier ("-03:00");
+  g_assert_nonnull (tz);
   g_assert_cmpstr (g_time_zone_get_identifier (tz), ==, "-03:00");
   dt = g_date_time_new (tz, 2010, 5, 24,  8, 0, 1.000001);
   g_time_zone_unref (tz);
@@ -2452,7 +2744,8 @@ test_identifier (void)
   const char *recife_tz = "America/Recife";
 #endif
 
-  tz = g_time_zone_new ("UTC");
+  tz = g_time_zone_new_identifier ("UTC");
+  g_assert_nonnull (tz);
   g_assert_cmpstr (g_time_zone_get_identifier (tz), ==, "UTC");
   g_time_zone_unref (tz);
 
@@ -2460,24 +2753,30 @@ test_identifier (void)
   g_assert_cmpstr (g_time_zone_get_identifier (tz), ==, "UTC");
   g_time_zone_unref (tz);
 
+  G_GNUC_BEGIN_IGNORE_DEPRECATIONS
   tz = g_time_zone_new ("some rubbish");
+  G_GNUC_END_IGNORE_DEPRECATIONS
   g_assert_cmpstr (g_time_zone_get_identifier (tz), ==, "UTC");
   g_time_zone_unref (tz);
 
-  tz = g_time_zone_new ("Z");
+  tz = g_time_zone_new_identifier ("Z");
+  g_assert_nonnull (tz);
   g_assert_cmpstr (g_time_zone_get_identifier (tz), ==, "Z");
   g_time_zone_unref (tz);
 
-  tz = g_time_zone_new ("+03:15");
+  tz = g_time_zone_new_identifier ("+03:15");
+  g_assert_nonnull (tz);
   g_assert_cmpstr (g_time_zone_get_identifier (tz), ==, "+03:15");
   g_time_zone_unref (tz);
 
   /* System timezone. We can’t change this, but we can at least assert that
-   * the identifier is non-NULL and doesn’t start with a slash. */
+   * the identifier is non-NULL and non-empty. */
+  G_GNUC_BEGIN_IGNORE_DEPRECATIONS
   tz = g_time_zone_new (NULL);
+  G_GNUC_END_IGNORE_DEPRECATIONS
+  g_test_message ("System time zone identifier: %s", g_time_zone_get_identifier (tz));
   g_assert_nonnull (g_time_zone_get_identifier (tz));
   g_assert_cmpstr (g_time_zone_get_identifier (tz), !=, "");
-  g_assert_true (*g_time_zone_get_identifier (tz) != '/');
   g_time_zone_unref (tz);
 
   /* Local timezone tests. */
@@ -2537,6 +2836,99 @@ test_new_offset (void)
     }
 }
 
+static void
+test_time_zone_parse_rfc8536 (void)
+{
+  const gchar *test_files[] =
+    {
+      /* Generated with `zic -b slim`; see
+       * https://gitlab.gnome.org/GNOME/glib/-/merge_requests/1533#note_842235 */
+      "Amsterdam-slim",
+      /* Generated with `zic -b fat` */
+      "Amsterdam-fat",
+    };
+  gsize i;
+
+  g_test_summary ("Test parsing time zone files in RFC 8536 version 3 format");
+  g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/2129");
+
+  for (i = 0; i < G_N_ELEMENTS (test_files); i++)
+    {
+      gchar *path = NULL;
+      GTimeZone *tz = NULL;
+
+      path = g_test_build_filename (G_TEST_DIST, "time-zones", test_files[i], NULL);
+      g_assert_true (g_path_is_absolute (path));
+      tz = g_time_zone_new_identifier (path);
+      g_assert_nonnull (tz);
+      g_time_zone_unref (tz);
+      g_free (path);
+    }
+}
+
+/* Check GTimeZone instances are cached. */
+static void
+test_time_zone_caching (void)
+{
+  GTimeZone *tz1 = NULL, *tz2 = NULL;
+
+  g_test_summary ("GTimeZone instances are cached");
+
+  /* Check a specific (arbitrary) timezone. These are only cached while third
+   * party code holds a ref to at least one instance. */
+#ifdef G_OS_UNIX
+  tz1 = g_time_zone_new_identifier ("Europe/London");
+  g_assert_nonnull (tz1);
+  tz2 = g_time_zone_new_identifier ("Europe/London");
+  g_assert_nonnull (tz2);
+  g_time_zone_unref (tz1);
+  g_time_zone_unref (tz2);
+#elif defined G_OS_WIN32
+  tz1 = g_time_zone_new_identifier ("GMT Standard Time");
+  g_assert_nonnull (tz1);
+  tz2 = g_time_zone_new_identifier ("GMT Standard Time");
+  g_assert_nonnull (tz2);
+  g_time_zone_unref (tz1);
+  g_time_zone_unref (tz2);
+#endif
+
+  /* Only compare pointers */
+  g_assert_true (tz1 == tz2);
+
+  /* Check the default timezone, local and UTC. These are cached internally in
+   * GLib, so should persist even after the last third party reference is
+   * dropped.
+   *
+   * The default timezone could be NULL on some platforms (FreeBSD) if
+   * `/etc/localtime` is not set correctly. */
+  tz1 = g_time_zone_new_identifier (NULL);
+  if (tz1 != NULL)
+    {
+      g_assert_nonnull (tz1);
+      g_time_zone_unref (tz1);
+      tz2 = g_time_zone_new_identifier (NULL);
+      g_assert_nonnull (tz2);
+      g_time_zone_unref (tz2);
+
+      g_assert_true (tz1 == tz2);
+    }
+
+  tz1 = g_time_zone_new_utc ();
+  g_time_zone_unref (tz1);
+  tz2 = g_time_zone_new_utc ();
+  g_time_zone_unref (tz2);
+
+  g_assert_true (tz1 == tz2);
+
+  tz1 = g_time_zone_new_local ();
+  g_time_zone_unref (tz1);
+  tz2 = g_time_zone_new_local ();
+  g_time_zone_unref (tz2);
+
+  g_assert_true (tz1 == tz2);
+}
+
+
 gint
 main (gint   argc,
       gchar *argv[])
@@ -2546,7 +2938,6 @@ main (gint   argc,
   g_unsetenv ("LANGUAGE");
 
   g_test_init (&argc, &argv, NULL);
-  g_test_bug_base ("http://bugzilla.gnome.org/");
 
   /* GDateTime Tests */
   bind_textdomain_codeset ("glib20", "UTF-8");
@@ -2581,8 +2972,10 @@ main (gint   argc,
   g_test_add_func ("/GDateTime/new_from_timeval_utc", test_GDateTime_new_from_timeval_utc);
   g_test_add_func ("/GDateTime/new_from_timeval/overflow", test_GDateTime_new_from_timeval_overflow);
   g_test_add_func ("/GDateTime/new_from_iso8601", test_GDateTime_new_from_iso8601);
+  g_test_add_func ("/GDateTime/new_from_iso8601/2", test_GDateTime_new_from_iso8601_2);
   g_test_add_func ("/GDateTime/new_full", test_GDateTime_new_full);
   g_test_add_func ("/GDateTime/now", test_GDateTime_now);
+  g_test_add_func ("/GDateTime/test-6-days-until-end-of-the-month", test_6_days_until_end_of_the_month);
   g_test_add_func ("/GDateTime/printf", test_GDateTime_printf);
   g_test_add_func ("/GDateTime/non_utf8_printf", test_non_utf8_printf);
   g_test_add_func ("/GDateTime/format_unrepresentable", test_format_unrepresentable);
@@ -2602,10 +2995,13 @@ main (gint   argc,
   g_test_add_func ("/GTimeZone/find-interval", test_find_interval);
   g_test_add_func ("/GTimeZone/adjust-time", test_adjust_time);
   g_test_add_func ("/GTimeZone/no-header", test_no_header);
+  g_test_add_func ("/GTimeZone/no-header-identifier", test_no_header_identifier);
   g_test_add_func ("/GTimeZone/posix-parse", test_posix_parse);
   g_test_add_func ("/GTimeZone/floating-point", test_GDateTime_floating_point);
   g_test_add_func ("/GTimeZone/identifier", test_identifier);
   g_test_add_func ("/GTimeZone/new-offset", test_new_offset);
+  g_test_add_func ("/GTimeZone/parse-rfc8536", test_time_zone_parse_rfc8536);
+  g_test_add_func ("/GTimeZone/caching", test_time_zone_caching);
 
   return g_test_run ();
 }