]> granicus.if.org Git - postgresql/commitdiff
Prefer timezone name "UTC" over alternative spellings.
authorAndrew Gierth <rhodiumtoad@postgresql.org>
Sat, 15 Jun 2019 17:15:23 +0000 (18:15 +0100)
committerAndrew Gierth <rhodiumtoad@postgresql.org>
Sat, 15 Jun 2019 17:15:23 +0000 (18:15 +0100)
tzdb 2019a made "UCT" a link to the "UTC" zone rather than a separate
zone with its own abbreviation. Unfortunately, our code for choosing a
timezone in initdb has an arbitrary preference for names earlier in
the alphabet, and so it would choose the spelling "UCT" over "UTC"
when the system is running on a UTC zone.

Commit 23bd3cec6 was backpatched in order to address this issue, but
that code helps only when /etc/localtime exists as a symlink, and does
nothing to help on systems where /etc/localtime is a copy of a zone
file (as is the standard setup on FreeBSD and probably some other
platforms too) or when /etc/localtime is simply absent (giving UTC as
the default).

Accordingly, add a preference for the spelling "UTC", such that if
multiple zone names have equally good content matches, we prefer that
name before applying the existing arbitrary rules. Also add a slightly
lower preference for "Etc/UTC"; lower because that preserves the
previous behaviour of choosing the shorter name, but letting us still
choose "Etc/UTC" over "Etc/UCT" when both exist but "UTC" does
not (not common, but I've seen it happen).

Backpatch all the way, because the tzdb change that sparked this issue
is in those branches too.

src/bin/initdb/findtimezone.c

index 3477a08efdc7233457da8122b4a0328effd7c3e5..f91fd3115e63c0cf1d7d7b99cf58aa78ecdfbdfc 100644 (file)
@@ -128,8 +128,11 @@ pg_load_tz(const char *name)
  * the C library's localtime() function.  The database zone that matches
  * furthest into the past is the one to use.  Often there will be several
  * zones with identical rankings (since the IANA database assigns multiple
- * names to many zones).  We break ties arbitrarily by preferring shorter,
- * then alphabetically earlier zone names.
+ * names to many zones).  We break ties by first checking for "preferred"
+ * names (such as "UTC"), and then arbitrarily by preferring shorter, then
+ * alphabetically earlier zone names.  (If we did not explicitly prefer
+ * "UTC", we would get the alias name "UCT" instead due to alphabetic
+ * ordering.)
  *
  * Many modern systems use the IANA database, so if we can determine the
  * system's idea of which zone it is using and its behavior matches our zone
@@ -602,6 +605,28 @@ check_system_link_file(const char *linkname, struct tztry *tt,
 #endif
 }
 
+/*
+ * Given a timezone name, determine whether it should be preferred over other
+ * names which are equally good matches. The output is arbitrary but we will
+ * use 0 for "neutral" default preference.
+ *
+ * Ideally we'd prefer the zone.tab/zone1970.tab names, since in general those
+ * are the ones offered to the user to select from. But for the moment, to
+ * minimize changes in behaviour, simply prefer UTC over alternative spellings
+ * such as UCT that otherwise cause confusion. The existing "shortest first"
+ * rule would prefer "UTC" over "Etc/UTC" so keep that the same way (while
+ * still preferring Etc/UTC over Etc/UCT).
+ */
+static int
+zone_name_pref(const char *zonename)
+{
+       if (strcmp(zonename, "UTC") == 0)
+               return 50;
+       if (strcmp(zonename, "Etc/UTC") == 0)
+               return 40;
+       return 0;
+}
+
 /*
  * Recursively scan the timezone database looking for the best match to
  * the system timezone behavior.
@@ -674,9 +699,13 @@ scan_available_timezones(char *tzdir, char *tzdirsub, struct tztry *tt,
                        else if (score == *bestscore)
                        {
                                /* Consider how to break a tie */
-                               if (strlen(tzdirsub) < strlen(bestzonename) ||
-                                       (strlen(tzdirsub) == strlen(bestzonename) &&
-                                        strcmp(tzdirsub, bestzonename) < 0))
+                               int             namepref = (zone_name_pref(tzdirsub) -
+                                                                       zone_name_pref(bestzonename));
+                               if (namepref > 0 ||
+                                       (namepref == 0 &&
+                                        (strlen(tzdirsub) < strlen(bestzonename) ||
+                                         (strlen(tzdirsub) == strlen(bestzonename) &&
+                                          strcmp(tzdirsub, bestzonename) < 0))))
                                        strlcpy(bestzonename, tzdirsub, TZ_STRLEN_MAX + 1);
                        }
                }